Android 动态加载(五) - 借尸还魂之代理Activity模式
前言
动态加载系列文章
Android 动态加载(一) - 基础篇(一)
Android 动态加载(二) - 基础篇(二)
Android 动态加载(三) - 类的加载流程源码分析
Android 动态加载(四) - 简单demo实现
Android 动态加载(五) - 借尸还魂之代理Activity模式
Android 动态加载(六) - 360开源框架DroidPlugin的使用介绍
1. 插件定义?
插件可以提供一种动态扩展的能力,让app在运行时候可以加载原本不属于该应用的功能,可以做到动态更新和替换;
2. 插件化定义?
把核心业务模块封装成独立的插件,根据不同业务需求进行不同组合,动态进行替换,可以对插件进行管理、更新;
3. 插件化架构主流框架?
1>:Small;
2>:DL动态加载框架;
3>:360的RePlugin;
3>:360的DroidPlugin;
4>:滴滴的VirtualAPK;
4. 插件化架构
插件化架构就是:点击一个 Button按钮,然后从服务器中下载一个 功能的apk,保存到本地,它是单独的一个apk并且是没有运行的,我们需要把它启动起来,并且做到参数传递。比如像早期的微信里边的一些功能:比如 摇一摇、漂流瓶、附近的人、等等其他功能,如下图所示:
插件化架构简介.png
这里我们为了演示方便,就直接 写一个 YaoYIYao的demo,然后运行把它打包,直接放到我们的手机存储目录中,就表示我们已经从服务器中下载了一个 摇一摇的apk,然后我们还需要做的就是:
1>:启动一个 Activity,这个 插件Activity是没有在 清单文件中注册的;
2>:
5. 拦截启动
进入 AndroidPluginDemo项目中,真正执行的类其实是 Singleton里边的 mInstance属性;
1>:获取 ActivityManagerNative里面的 gDefault;
2>:获取gDefault中的 mInstance属性;
3>:这个时候会报错,报错是因为TestActivity没有注册,这个时候我们重新写一个 ProxyActivity先占一个坑,待会就让 ProxyActivity代替 TestActivity去过检测,意思就是让 ProxyActivity在清单文件中代替 TestActivity注册,此时从MainActivity已经可以跳转过来到TestActivity;
4>:最后还需要换回来,hook ActivityManager里面的 mH是一个 Handler:
4.1>>:获取ActivityThread的实例;
4.2>>:获取ActivityThread中的mH;
4.3>>:hook 使用 handleLaunchActivity
流程就是:
从MainActivity中 点击跳转 然后跳转到 TestActivity界面,但是在 清单文件中不需要配置 TestActivity,直接使用hook启动流程 LaunchActivity即可:
效果如下图所示:
图片.png图片.png
6. 代码如下
1>:MainActivity代码如下:
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } public void click(View view){ Intent intent = new Intent(MainActivity.this , TestActivity.class) ; startActivity(intent); }}
2>:TestActivity代码如下:
/** * Email: 2185134304@qq.com * Created by Novate 2018/4/30 17:34 * Version 1.0 * Params: * Description:*/public class TestActivity extends Activity { @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_test); }}
3>:代理过安检的 ProxyActivity代码如下:
/** * Email: 2185134304@qq.com * Created by Novate 2018/4/30 17:44 * Version 1.0 * Params: * Description: 只是代理过检测的Activity*/public class ProxyActivity extends Activity{}
4>:HookStartActivityUtil代码如下:
/** * Email: 2185134304@qq.com * Created by Novate 2018/4/30 10:34 * Version 1.0 * Params: * Description: * * 对于 源码中的某个类 如果现实 {hide},就表示只能系统去new ,如果自己我们想要创建对象,只能通过下边方式获取 * Class<?> amnClass = Class.forName("android.app.ActivityManagerNative") ; forName("该类最上边的包名 + 类名")*/public class HookStartActivityUtil { private Context mContext ; private Class<?> mProxyClass ; private final String EXTER_ORIGIN_INTENT = "EXTER_ORIGIN_INTENT"; public HookStartActivityUtil(Context context , Class<?> proxyClass){ this.mContext = context.getApplicationContext() ; // 防止内存泄露 this.mProxyClass = proxyClass ; } public void hookLaunchActivity() throws Exception{ // 1:获取ActivityThread的实例; Class<?> atClass = Class.forName("android.app.ActivityThread") ; // 获取ActivityThread中的属性 Field scatThread = atClass.getDeclaredField("sCurrentActivityThread"); scatThread.setAccessible(true); Object sCurrentActivityThread = scatThread.get(null) ; // 静态的可以传递 null // 2:获取ActivityThread中的mH; Field mHField = atClass.getDeclaredField("mH"); mHField.setAccessible(true); Object mHandler = mHField.get(sCurrentActivityThread); // 3:hook 使用 handleLaunchActivity // 给handler设置 CallBack回调,也通过反射 Class<?> handlerClass = Class.forName("android.os.Handler") ; Field mCallBackField = handlerClass.getDeclaredField("mCallback"); mCallBackField.setAccessible(true); mCallBackField.set(mHandler , new HandlerCallBack()); } private class HandlerCallBack implements Handler.Callback{ @Override public boolean handleMessage(Message msg) { // 每发一次消息,都会执行一次这个CallBack方法 if (msg.what == 100){ // 根据Handler源码可知 handleLaunchMessage(msg) ; } return false; } } /** * 开始启动创建Activity拦截 */ private void handleLaunchMessage(Message msg) { try { Object record = msg.obj ; // 1. 从ActivityClientRecord中获取过安检的 intent Field intentField = record.getClass().getDeclaredField("intent") ; intentField.setAccessible(true); Intent safeIntent = (Intent) intentField.get(record); // 2. 获取到过安检的intent之后 ,从safeIntent中获取原来的 originIntent Intent originIntent = safeIntent.getParcelableExtra(EXTER_ORIGIN_INTENT) ; // 3. 重新设置回去 if (originIntent != null){ intentField.set(record , originIntent); } } catch (Exception e) { e.printStackTrace(); } } public void hookStartActivity() throws Exception{ // 1>:获取 ActivityManagerNative里面的 gDefault; Class<?> amnClass = Class.forName("android.app.ActivityManagerNative") ; // 通过 ActivityManagerNative 类 获取 gDefault属性 Field gDefaultField = amnClass.getDeclaredField("gDefault"); gDefaultField.setAccessible(true); // 设置权限 Object gDefault = gDefaultField.get(null) ; // 2>:获取gDefault中的 mInstance属性; Class<?> singletonClass = Class.forName("android.util.Singleton") ; Field mInstanceField = singletonClass.getDeclaredField("mInstance"); mInstanceField.setAccessible(true); Object iamInstance = mInstanceField.get(gDefault); Class<?> iamClass = Class.forName("android.app.IActivityManager") ; iamInstance = Proxy.newProxyInstance(HookStartActivityUtil.class.getClassLoader(), new Class[]{iamClass} , // InvocationHandler:必须有一个执行者,就是谁去执行这个方法 new StartActivityInvocationHandler(iamInstance)) ; // 3>:重新指定 mInstanceField.set(gDefault , iamInstance); } private class StartActivityInvocationHandler implements InvocationHandler{ // 这个才是方法的执行者 private Object mObject ; // 通过构造方法把mObject传递进来 public StartActivityInvocationHandler(Object object){ this.mObject = object ; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 在这里可以 hook到 IActivityManager中所有的方法 Log.e("TAG" , method.getName()) ; // 替换intent ,过AndroidManifest.xml 检测 if (method.getName().equals("startActivity")){ // 1. 首先获取原来的intent Intent originIntent = (Intent) args[2]; // 2. 创建一个安全的intent Intent safeIntent = new Intent(mContext , mProxyClass) ; // 3. 替换第二个参数 args[2] = safeIntent ; // 4. 绑定原来的Intent safeIntent.putExtra(EXTER_ORIGIN_INTENT , originIntent) ; } return method.invoke(mObject , args); } }}
5>:BaseApplication代码如下:
/** * Email: 2185134304@qq.com * Created by Novate 2018/4/30 8:41 * Version 1.0 * Params: * Description:*/public class BaseApplication extends Application { @Override public void onCreate() { super.onCreate(); HookStartActivityUtil hookStartActivityUtil = new HookStartActivityUtil(this , ProxyActivity.class) ; try { hookStartActivityUtil.hookStartActivity(); hookStartActivityUtil.hookLaunchActivity(); } catch (Exception e) { e.printStackTrace(); } }}
6>:AndroidManifest.xml文件如下:
<?xml version="1.0" encoding="utf-8"?>
以上代码就可以实现不需要在 清单文件中注册 TestActivity,直接通过代理过安检的 ProxyActivity 就可以 从MainActivity 跳转到 TestActivity,但是有一个问题,上边的 TestActivity是继承 自Activity的,如果让 TestActivity继承 AppCompatActivity就会报错,下边就来解决这个问题。
7. 解决 TestActivity继承自 AppCompatActivity绕不过去的问题
ActivityThread源码中的 getPackageManager()方法如下:
图片.png
思路就是:
因为上边的 getPackageManager()方法是静态的方法,可以在 开始启动创建Activity拦截 handleLaunchActivity方法中先调用一次,这个时候 sPackageManager就会有实例,那么我再次进来就获取的是已经实例好的 sPackageManager,代码如下:
/** * 兼容AppCompatActivity报错问题 */ Class<?> forName = Class.forName("android.app.ActivityThread"); Field field = forName.getDeclaredField("sCurrentActivityThread"); field.setAccessible(true); Object activityThread = field.get(null); // 我自己执行一次那么就会创建PackageManager,系统再获取的时候就是下面的iPackageManager Method getPackageManager = activityThread.getClass().getDeclaredMethod("getPackageManager"); Object iPackageManager = getPackageManager.invoke(activityThread); PackageManagerHandler handler = new PackageManagerHandler(iPackageManager); Class<?> iPackageManagerIntercept = Class.forName("android.content.pm.IPackageManager"); Object proxy = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class<?>[]{iPackageManagerIntercept}, handler); // 获取 sPackageManager 属性 Field iPackageManagerField = activityThread.getClass().getDeclaredField("sPackageManager"); iPackageManagerField.setAccessible(true); iPackageManagerField.set(activityThread, proxy);
class PackageManagerHandler implements InvocationHandler { private Object mActivityManagerObject; public PackageManagerHandler(Object iActivityManagerObject) { this.mActivityManagerObject = iActivityManagerObject; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // Log.e("TAG", "methodName = " + method.getName()); if (method.getName().startsWith("getActivityInfo")) { ComponentName componentName = new ComponentName(mContext, mProxyClass); args[0] = componentName; } return method.invoke(mActivityManagerObject, args); } }
下一节会讲解360开源框架DroidPlugin的使用介绍,代码会在下一节中上传至github。
更多相关文章
- 【Android NDK 开发】Ubuntu 函数库交叉编译 ( Android 动态库交
- 通过xml加载菜单Menus
- android之webView加载javascropt
- Android插件配置-Android Extension介绍
- android广播动态注册与发送流程分析
- Android ListView列表 刷新和加载更多
- PullToRefreshLayout +RecyclerView 实现上拉加载下拉刷新
- 图片加载的几种模式
- ImageView下载图片加载