美文网首页Android文章
Activity插件化原理第一种方案:Hook Instrume

Activity插件化原理第一种方案:Hook Instrume

作者: 爱读书的顾先生 | 来源:发表于2019-01-30 09:42 被阅读247次
    人生一切难题,知识给你答案

    温馨提示:阅读本文需要15-20分钟(一大波代码)
    公众号:顾林海(每天更新优质文章)


    今天,我们来解决一个问题:

    Activity插件化原理第一种方案:Hook Instrumentation

    人生一切难题,知识给你答案。


    Activity的插件化解决的一个根本性问题就是插件中的Activity并没有在宿主的AndroidManifest.xml中进行注册,也就是说我们需要启动一个未注册的Activity,因此需要对Activity的启动过程有个了解。

    启动Activity时会请求AMS创建Activity,这里的AMS指的是ActivityManagerService,AMS所属的进程与宿主(发起者)不属于同一个进程,AMS位于SystemServer进程中。

    未命名文件 (5).png

    应用程序进程与AMS之间的通信是通过Binder来实现的,AMS要管理所有APP的启动请求,因此我们不能在SystemServer进程中进行相应的Hook,那么我们只能在应用进程中进行相应的Hook。

    如果我们启动一个未注册的Activity,AMS会去检查AndroidManifest中是否注册了该Activity,如果未注册会报错。

    未命名文件 (6).png

    为了让AMS验证通过,需要启动一个预先在AndroidManifest中注册的Activity,我们称之为占坑,在启动插件Activity时替换为占坑Activity,达到一个欺上瞒下的作用,当AMS验证通过之后,需要将启动的占坑Activity替换为插件Activity。

    未命名文件 (8).png

    总结下来Activity的插件化需要做两件事:

    • 将请求启动的插件Activity替换为占坑Activity。
    • 绕过AMS验证后,将占坑Activity替换为插件Activity。

    什么时候将插件Activity替换为占坑Activity?又是什么时候还原插件Activity?这需要我们对Activity的启动流程有个相应的认识。

    我们在Activity中调用startActivity方法如下:

        @Override
        public void startActivity(Intent intent) {
            this.startActivity(intent, null);
        }
        
            @Override
        public void startActivity(Intent intent, @Nullable Bundle options) {
            if (options != null) {
               startActivityForResult(intent, -1, options);
            } else {
               startActivityForResult(intent, -1);
            }
        }
    

    调用startActivityForResult方法:

        public void startActivityForResult(@RequiresPermission Intent intent, int requestCode,
                @Nullable Bundle options) {
            if (mParent == null) {
                //Activity启动
                options = transferSpringboardActivityOptions(options);
                Instrumentation.ActivityResult ar =
                    mInstrumentation.execStartActivity(
                        this, mMainThread.getApplicationThread(), mToken, this,
                        intent, requestCode, options);
                if (ar != null) {
                    mMainThread.sendActivityResult(
                        mToken, mEmbeddedID, requestCode, ar.getResultCode(),
                        ar.getResultData());
                }
                if (requestCode >= 0) {
                    mStartedActivity = true;
                }
    
                cancelInputsAndStartExitTransition(options);
                windows.
            } else {
                if (options != null) {
                    mParent.startActivityFromChild(this, intent, requestCode, options);
                } else {
                    mParent.startActivityFromChild(this, intent, requestCode);
                }
            }
        }
    
    

    startActivityForResult方法中通过调用mInstrumentation的execStartActivity方法来启动Activity,这个mInstrumentation是Activity的成员变量,在ActivityThread的performLaunchActivity方法中通过Activity的attach方法传入,同时Activity的创建也是在performLaunchActivity方法中创建的,通过mInstrumentation.newActivity。

    //:/frameworks/base/core/java/android/app/ActivityThread.java
    private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
        ...
        try {
            java.lang.ClassLoader cl = appContext.getClassLoader();
            activity = mInstrumentation.newActivity(
                        cl, component.getClassName(), r.intent);
            StrictMode.incrementExpectedActivityCount(activity.getClass());
            r.intent.setExtrasClassLoader(cl);
            r.intent.prepareToEnterProcess();
            if (r.state != null) {
                r.state.setClassLoader(cl);
            }
        }
        ...
        activity.attach(appContext, this, getInstrumentation(), r.token,
                            r.ident, app, r.intent, r.activityInfo, title, r.parent,
                            r.embeddedID, r.lastNonConfigurationInstances, config,
                            r.referrer, r.voiceInteractor, window, r.configCallback);
        ...
    }
    

    综上所述Instrumentation提供了execStartActivity方法来启动Activity,newActivity方法来创建Activity。因此,第一种方案就是用代理Instrumentation来替代Activity的Instrumentation,并在代理Instrumentation的execStartActivity方法中替换为占坑Activity,在newActivity方法还原插件Activity。

    现在我们基于第一种方案Hook Instrumentation来实现Activity的插件化。

    首先创建占坑Activity:

    public class StubActivity extends AppCompatActivity {
    
        @Override
        protected void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_stub);
        }
    }
    

    创建插件Activity:

    public class TargetActivity extends AppCompatActivity {
    
        @Override
        protected void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_target);
        }
    }
    

    并在AndroidManifest.xml中注册占坑Activity:

    <?xml version="1.0" encoding="utf-8"?>
    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        package="com.glh.haiproject01">
    
        <application
            android:name=".MyApplication"
            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"
            tools:ignore="AllowBackup,GoogleAppIndexingWarning">
            <activity android:name=".MainActivity">
                <intent-filter>
                    <action android:name="android.intent.action.MAIN" />
    
                    <category android:name="android.intent.category.LAUNCHER" />
                </intent-filter>
            </activity>
            <activity android:name=".StubActivity" />
        </application>
    
    </manifest>
    

    在AndroidManifest.xml中没有注册插件Activity,这时如果启动插件Activity会报错。

    最后Hook Instrumentation,将ActivityThread中的成员变量Instrumentation替换成代理的Instrumentation。

    创建代理Instrumentation类:

    public class InstrumentationProxy extends Instrumentation {
    
        private Instrumentation mInstrumentation;
        private PackageManager mPackageManager;
    
        public InstrumentationProxy(Instrumentation instrumentation, PackageManager packageManager) {
            this.mInstrumentation = instrumentation;
            this.mPackageManager = packageManager;
        }
    
        public ActivityResult execStartActivity(
                Context who, IBinder contextThread, IBinder token, Activity target,
                Intent intent, int requestCode, Bundle options) {
    
            List<ResolveInfo> resolveInfo = mPackageManager.queryIntentActivities(intent, PackageManager.MATCH_ALL);
            //判断启动的插件Activity是否在AndroidManifest.xml中注册过
            if (null == resolveInfo || resolveInfo.size() == 0) {
                //保存目标插件
                intent.putExtra(HookHelper.REQUEST_TARGET_INTENT_NAME, intent.getComponent().getClassName());
                //设置为占坑Activity
                intent.setClassName(who, "com.glh.haiproject01.StubActivity");
            }
    
            try {
                Method execStartActivity = Instrumentation.class.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;
        }
    
        public Activity newActivity(ClassLoader cl, String className, Intent intent) throws InstantiationException,
                IllegalAccessException, ClassNotFoundException {
            String intentName=intent.getStringExtra(HookHelper.REQUEST_TARGET_INTENT_NAME);
            if(!TextUtils.isEmpty(intentName)){
                return super.newActivity(cl,intentName,intent);
            }
            return super.newActivity(cl,className,intent);
        }
    
    }
    

    代理类InstrumentationProxy的execStartActivity方法先判断插件Activity是否在AndroidManifest.xml中注册过,如果没有注册过就需要替换占坑的Activity,在newActivity方法中还原插件Activity。

    代理类InstrumentationProxy写完后,需要对ActivityThread的成员变量mInstrumentation进行替换。

    public class MyApplication extends Application {
    
        @Override
        protected void attachBaseContext(Context base) {
            super.attachBaseContext(base);
            hookActivityThreadInstrumentation();
        }
    
    
        private void hookActivityThreadInstrumentation(){
            try {
                Class<?> activityThreadClass=Class.forName("android.app.ActivityThread");
                Field activityThreadField=activityThreadClass.getDeclaredField("sCurrentActivityThread");
                activityThreadField.setAccessible(true);
                //获取ActivityThread对象sCurrentActivityThread
                Object activityThread=activityThreadField.get(null);
    
                Field instrumentationField=activityThreadClass.getDeclaredField("mInstrumentation");
                instrumentationField.setAccessible(true);
                //从sCurrentActivityThread中获取成员变量mInstrumentation
                Instrumentation instrumentation= (Instrumentation) instrumentationField.get(activityThread);
                //创建代理对象InstrumentationProxy
                InstrumentationProxy proxy=new InstrumentationProxy(instrumentation,getPackageManager());
                //将sCurrentActivityThread中成员变量mInstrumentation替换成代理类InstrumentationProxy
                instrumentationField.set(activityThread,proxy);
            } catch (NoSuchFieldException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
        }
    }
    

    这时我们在主界面点击跳转插件Activity:

    public class MainActivity extends AppCompatActivity {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            findViewById(R.id.btn_startActivity).setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    Intent intent=new Intent(MainActivity.this,TargetActivity.class);
                    startActivity(intent);
                }
            });
        }
    }
    

    运行效果:

    wq1.gif
    838794-506ddad529df4cd4.webp.jpg

    相关文章

      网友评论

        本文标题:Activity插件化原理第一种方案:Hook Instrume

        本文链接:https://www.haomeiwen.com/subject/wasqsqtx.html