Android9.0和10.0插件化原理实现

Activity的插件化解决的一个根本性问题就是插件中的Activity并没有在宿主的AndroidManifest.xml中进行注册,也就是说我们需要启动一个未注册的Activity,因此需要对Activity的启动过程有个了解,Android各个版本源码启动流程略有不同,但大致流程一样,这里给出9.0和10.0实现的二种方式。

1.继承Instrumentation的方式,这种方式比较简单在9.0和10.0都能用
  • 1.重写一个类继承Instrumentation
public class InstrumentationProxy extends Instrumentation {
    private Instrumentation mInstrumentation;

    public InstrumentationProxy(Instrumentation instrumentation) {
        mInstrumentation = instrumentation;
    }

    public ActivityResult execStartActivity(
            Context who, IBinder contextThread, IBinder token, Activity target,
            Intent intent, int requestCode, Bundle options) {
        List<ResolveInfo> resolveInfo = who.getPackageManager().queryIntentActivities(intent, PackageManager.MATCH_ALL);
        LogUtil.logE("execStartActivityexecStartActivity");
        //判断启动的插件Activity是否在AndroidManifest.xml中注册过
        if (resolveInfo.size() == 0) {
            //保存目标插件
            intent.putExtra(Const.INTENT_DATA, intent.getComponent().getClassName());
            //设置为占坑Activity
            intent.setClassName(who, "com.example.client.hook.ProxyActivity");
        }
        try {
            Method execStartActivity = getClass().getSuperclass().getDeclaredMethod("execStartActivity",
                    Context.class, IBinder.class, IBinder.class, Activity.class,
                    Intent.class, int.class, Bundle.class);
            return (ActivityResult) execStartActivity.invoke(mInstrumentation, who, contextThread, token, target, intent, requestCode, options);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
        return null;
    }

    @Override
    public Activity newActivity(ClassLoader cl, String className, Intent intent) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        String data = intent.getStringExtra(Const.INTENT_DATA);
        if (!TextUtils.isEmpty(data)) {
            return super.newActivity(cl, data, intent);
        }
        return super.newActivity(cl, className, intent);
    }
}
  • 2.在Application中onCreate方法中去替换我们自己的Instrumentation
 private void hook1() {
        try {
            Class<?> clazz = Class.forName("android.app.ActivityThread");
            //获取ActivityThread
            Field sCurrentActivityThreadFiled = clazz.getDeclaredField("sCurrentActivityThread");
            sCurrentActivityThreadFiled.setAccessible(true);
            Object sCurrentActivityThread = sCurrentActivityThreadFiled.get(null);
            //获取instrumentation
            Field instrumentationFiled = clazz.getDeclaredField("mInstrumentation");
            instrumentationFiled.setAccessible(true);
            Instrumentation instrumentation = (Instrumentation) instrumentationFiled.get(sCurrentActivityThread);
            //设置自己的instrumentation
            InstrumentationProxy instrumentationProxy = new InstrumentationProxy(instrumentation);
            instrumentationFiled.set(sCurrentActivityThread,instrumentationProxy);      
        } catch (Exception e) {       
        }
    }
  <application
        android:name=".hook.App"
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <!--代理的Activity-->
        <activity android:name=".ProxyActivity">

        </activity>
    </application>
2.使用动态代理的方式,拦截启动activity的方法替换intent

大体的时序图


a.jpg
 private void hook() {

        try {
            //1.获取IActivityManagerSingleton
            Object IActivityManagerSingleton = getSingletonByVersion();

            //2.获取mInstance
            Class<?> singletonclazz = Class.forName("android.util.Singleton");
            Field mInstanceField = singletonclazz.getDeclaredField("mInstance");
            mInstanceField.setAccessible(true);

            if (Build.VERSION.SDK_INT == 29) {
                //Q上需要动态执行create方法
                Method getMethod = singletonclazz.getMethod("get");
                getMethod.setAccessible(true);
                getMethod.invoke(IActivityManagerSingleton);

            }


            Object mInstance = mInstanceField.get(IActivityManagerSingleton);
            Logutils.LogE(mInstance+"");
            //3.动态代理设置自己的mInstance
            Object proxyInstance = Proxy.newProxyInstance(getClassLoader(),
                    mInstance.getClass().getInterfaces(),
                    new MyInvocationHandler(mInstance));

            //4.设置代理的proxyInstance

            mInstanceField.set(IActivityManagerSingleton, proxyInstance);

            //5.获取ActivityThread实例
            Class<?> ActivityThreadclass = Class.forName("android.app.ActivityThread");

            Field sCurrentActivityThreadFiled = ActivityThreadclass.getDeclaredField(
                    "sCurrentActivityThread");
            sCurrentActivityThreadFiled.setAccessible(true);
            Object sCurrentActivityThread = sCurrentActivityThreadFiled.get(null);

            //6.获取mH实例
            Field mHFiled = ActivityThreadclass.getDeclaredField("mH");
            mHFiled.setAccessible(true);
            Object mH = mHFiled.get(sCurrentActivityThread);

            Field mCallbackFiled = Handler.class.getDeclaredField("mCallback");
            mCallbackFiled.setAccessible(true);
            //7.设置进入我们自己的Callback

            mCallbackFiled.set(mH, new MyHandlerCallback((Handler) mH));

        } catch (Exception e) {
            e.printStackTrace();
            Log.e("TAG------", e.toString());
        }
    }

需要注意的是10.0在获取mInstance的时候必须动态执行下create方法,否则拿不到mInstance 的实例.

    private Object getSingletonByVersion() {
        try {
            if (Build.VERSION.SDK_INT == 28) {
                Class<?> clazz = Class.forName("android.app.ActivityManager");
                Field field = clazz.getDeclaredField("IActivityManagerSingleton");
                field.setAccessible(true);
                return field.get(null);
            } else if (Build.VERSION.SDK_INT == 29) {
                Class<?> clazz = Class.forName("android.app.ActivityTaskManager");
                Field field = clazz.getDeclaredField("IActivityTaskManagerSingleton");
                field.setAccessible(true);
                return field.get(null);
            }
        } catch (Exception e) {
            e.printStackTrace();
            Logutils.LogE(e.toString());
        }
        return null;
    }

10.0和9.0获取SingleTon的方法也略有不同,10.0把几个类修改了下,但大致流程相同

  private class MyInvocationHandler implements InvocationHandler {

        private Object mIActivityManager;

        public MyInvocationHandler(Object IActivityManager) {
            mIActivityManager = IActivityManager;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            if (method.getName().equals("startActivity")) {
                for (Object arg : args) {
                    if (arg instanceof Intent) {
                        Intent intent = (Intent) arg;
                        //把插件的Activity类名传入
                        intent.putExtra(INTENT_DATA, intent.getComponent().getClassName());
                        //设置代理的activity
                        intent.setClass(getApplicationContext(), ProxyActivity.class);
                    }

                }
            }
            return method.invoke(mIActivityManager, args);
        }
    }

使用动态让代理拦截startActivity方法替换intent里面的class绕过AndroidManifest的检测,并把我们设置的class当作参数传递。

 private class MyHandlerCallback implements Handler.Callback {
        private Handler mHandler;

        public MyHandlerCallback(Handler handler) {
            mHandler = handler;
        }

        @Override
        public boolean handleMessage(@NonNull Message msg) {
            if (msg.what == 159) {
                Object obj = msg.obj;
                try {
                    //获取ClientTransaction中的mActivityCallbacks集合
                    Class<?> clazz = Class.forName("android.app.servertransaction" +
                            ".ClientTransaction");
                    Field mActivityCallbacksFiled = clazz.getDeclaredField("mActivityCallbacks");
                    mActivityCallbacksFiled.setAccessible(true);
                    List list = (List) mActivityCallbacksFiled.get(obj);
                    if (list != null && list.size() > 0) {
                        //得到集合中的LaunchActivityItem
                        Object o = list.get(0);
                        //获取LaunchActivityItem中的mIntent
                        Class<?> LaunchActivityItemClazz = Class.forName("android.app" +
                                ".servertransaction.LaunchActivityItem");
                        Field mIntentFiled = LaunchActivityItemClazz.getDeclaredField("mIntent");
                        mIntentFiled.setAccessible(true);
                        Intent intent = (Intent) mIntentFiled.get(o);
                        //得到我们设置的class 替换进去
                        if (intent.getStringExtra(INTENT_DATA) != null) {
                            String className = intent.getStringExtra(INTENT_DATA);
                            intent.setClassName(getApplicationContext(), className);
                        }

                    }

                } catch (Exception e) {
                    e.printStackTrace();
                    Logutils.LogE(e.toString());
                }
            }

            mHandler.handleMessage(msg);
            return true;
        }
    }

在ActivityThread回调的handlemessage方法中在创建activity时来替换我们原来设置的Activity,从intent中获取我们设置的参数, 有了上面的代码后在Activity中可以正常运行但是在AppcompatActivity中还是报错需要再次Hook

private void hook1() {
        try {
            Class<?> ActivityThreadclass = Class.forName("android.app.ActivityThread");

            Field sCurrentActivityThreadFiled = ActivityThreadclass.getDeclaredField(
                    "sCurrentActivityThread");
            sCurrentActivityThreadFiled.setAccessible(true);
            Object sCurrentActivityThread = sCurrentActivityThreadFiled.get(null);

            Field sPackageManagerFiled = ActivityThreadclass.getDeclaredField(
                    "sPackageManager");
            sPackageManagerFiled.setAccessible(true);
            Object sPackageManager = sPackageManagerFiled.get(null);

            Object proxyInstance = Proxy.newProxyInstance(getClassLoader(),
                    sPackageManager.getClass().getInterfaces(),
                    new PackageManagerHandler(sPackageManager));

            sPackageManagerFiled.set(sCurrentActivityThread, proxyInstance);


        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private class PackageManagerHandler implements InvocationHandler {
        private Object IPackageManager;

        public PackageManagerHandler(Object IPackageManager) {
            this.IPackageManager = IPackageManager;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            if ("getActivityInfo".equals(method.getName())) {
                args[0] = new ComponentName(getPackageName(), ProxyActivity.class.getName());
            }
            return method.invoke(IPackageManager, args);
        }
    }

在getActivityInfo时候找不到我们的Activity,依然使用动态代理的方式替换我们代理的Activity。
看下最后效果


aa.gif
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,108评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,699评论 1 296
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,812评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,236评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,583评论 3 288
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,739评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,957评论 2 315
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,704评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,447评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,643评论 2 249
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,133评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,486评论 3 256
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,151评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,108评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,889评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,782评论 2 277
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,681评论 2 272

推荐阅读更多精彩内容