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并且是没有运行的,我们需要把它启动起来,并且做到参数传递。比如像早期的微信里边的一些功能:比如 摇一摇、漂流瓶、附近的人、等等其他功能,如下图所示:


Android 动态加载(五) - 借尸还魂之代理Activity模式_第1张图片
插件化架构简介.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即可:

效果如下图所示:
Android 动态加载(五) - 借尸还魂之代理Activity模式_第2张图片
图片.png

Android 动态加载(五) - 借尸还魂之代理Activity模式_第3张图片
图片.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: [email protected]
 * 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: [email protected]
 * Created by Novate 2018/4/30 17:44
 * Version 1.0
 * Params:
 * Description:   只是代理过检测的Activity
*/
public class ProxyActivity extends Activity{
}
4>:HookStartActivityUtil代码如下:
/**
 * Email: [email protected]
 * 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: [email protected]
 * 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文件如下:



    
        
            
                

                
            
        

        
    

以上代码就可以实现不需要在 清单文件中注册 TestActivity,直接通过代理过安检的 ProxyActivity 就可以 从MainActivity 跳转到 TestActivity,但是有一个问题,上边的 TestActivity是继承 自Activity的,如果让 TestActivity继承 AppCompatActivity就会报错,下边就来解决这个问题。

7. 解决 TestActivity继承自 AppCompatActivity绕不过去的问题


ActivityThread源码中的 getPackageManager()方法如下:


Android 动态加载(五) - 借尸还魂之代理Activity模式_第4张图片
图片.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 动态加载(五) - 借尸还魂之代理Activity模式)