插件化入门篇-如何启动一个未注册过的Activity

看我赵云在AMS七进七出

Posted by 夏敏的博客 on August 6, 2017

几乎所有的插件化都会要的一个需求,启动一个未注册的Activiy,即加载插件包中的Activity,并且主应用并不知道插件应用中会有什么Activity,这是各个插件化框架主力解决的问题之一。

今天我们学习一下占坑式插件化框架的启动Activity原理。

关于动态代理的知识,了解过Retrofit的源码的或者看过Java设计模式之代理模式的高级使用的,应该都了解了。本章不做介绍,主介绍hook+反射

Hook是什么?

Hook直白点说就是拦截方法,自己对其参数等进行修改,或者替换返回值,达到自己不可告人的目的的一件事。

寻找Hook点

对于启动Activity,老实说光startActivity便有很多要说,很多文章会带着你一直追到ActivityManagerService中的若干个方法,最后再调用本地的ActivityThread里面的方法去启动本进程的Activity。

所以光上面的流程我们看出,我们把要启动的Activity信息发给AMS,其做了各种检查各种操作后真正让Activity启动的还是我们的ActivityThread

startActivity流程

我们startActivity是context的方法,去找Context实现类class ContextImpl extends Context

    @Override
    public void startActivities(Intent[] intents) {
        warnIfCallingFromSystemProcess();
        startActivities(intents, null);
    }
        /** @hide */
    @Override
    public void startActivitiesAsUser(Intent[] intents, Bundle options, UserHandle userHandle) {
        if ((intents[0].getFlags()&Intent.FLAG_ACTIVITY_NEW_TASK) == 0) {
            throw new AndroidRuntimeException(
                    "Calling startActivities() from outside of an Activity "
                    + " context requires the FLAG_ACTIVITY_NEW_TASK flag on first Intent."
                    + " Is this really what you want?");
        }
        mMainThread.getInstrumentation().execStartActivitiesAsUser(
            getOuterContext(), mMainThread.getApplicationThread(), null,
            (Activity)null, intents, options, userHandle.getIdentifier());
    }

看到最后调用的是mMainThread.getInstrumentation().execStartActivitiesAsUser方法,不用着急,直接ctrl鼠标左击进去。是Instrumentation类。

    public void execStartActivitiesAsUser(Context who, IBinder contextThread,
            IBinder token, Activity target, Intent[] intents, Bundle options,
            int userId) {
        IApplicationThread whoThread = (IApplicationThread) contextThread;
        if (mActivityMonitors != null) {
            synchronized (mSync) {
                final int N = mActivityMonitors.size();
                for (int i=0; i<N; i++) {
                    final ActivityMonitor am = mActivityMonitors.get(i);
                    if (am.match(who, null, intents[0])) {
                        am.mHits++;
                        if (am.isBlocking()) {
                            return;
                        }
                        break;
                    }
                }
            }
        }
        try {
            String[] resolvedTypes = new String[intents.length];
            for (int i=0; i<intents.length; i++) {
                intents[i].migrateExtraStreamToClipData();
                intents[i].prepareToLeaveProcess();
                resolvedTypes[i] = intents[i].resolveTypeIfNeeded(who.getContentResolver());
            }
            int result = ActivityManagerNative.getDefault()
                .startActivities(whoThread, who.getBasePackageName(), intents, resolvedTypes,
                        token, options, userId);
            checkStartActivityResult(result, intents[0]);
        } catch (RemoteException e) {
        }
    }

这边我们看到了,是调用ActivityManagerNative的方法启动activity了。进去这个类我们只能看到一堆的binder通信,调用AMS的方法,不过此时我们不用关心了,因为我们知道接下来是 AMS的事情。AMS是活在另一个PID的玩意儿,我们只关心我们自己的pid,另一个进程的东西我们没权限干坏事。

不过这边我们需要注意,ActivityManagerNative居然是个单类,那么我们hook它会安全很多,毕竟这个对象是单类。

    static public IActivityManager getDefault() {
        return gDefault.get();
    }

    private static final Singleton<IActivityManager> gDefault = new Singleton<IActivityManager>() {
        protected IActivityManager create() {
            IBinder b = ServiceManager.getService("activity");
            if (false) {
                Log.v("ActivityManager", "default service binder = " + b);
            }
            IActivityManager am = asInterface(b);
            if (false) {
                Log.v("ActivityManager", "default service = " + am);
            }
            return am;
        }
    };

说是说AMS的事情不用关心,但是我们得关心AMS什么时候回调回来,让我们启动Activity。

ActivityThread看,一搜里面有个handleLaunchActivity方法,是在Handler里面被调用的,而且ActivityThread也是我们喜欢的对象,因为这个对象存在于整个应用生命周期中。

        public void handleMessage(Message msg) {
            if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
            switch (msg.what) {
                case LAUNCH_ACTIVITY: {   //这个值是常量100
                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
                    ActivityClientRecord r = (ActivityClientRecord)msg.obj;

                    r.packageInfo = getPackageInfoNoCheck(
                            r.activityInfo.applicationInfo, r.compatInfo);
                    handleLaunchActivity(r, null);
                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                } break;

看了这么多,我们可算是知道启动Activity的入口和出口了,下面我们需要进行欺骗。

实现欺骗

欺骗系统就欺骗两个地方,我们在AndroidManifest里面申明一个假Activity,然后在启动真实Activity的地方,将Intent里面的Activity替换成我们已经注册过的。再在ActivityThread launch Activity的时候,替换成我们需要启动的便实现了启动一个未注册过的Activity的效果。

代码实现

  • 写一个占坑Activity,在AndroidManifest注册
          /**
           * 占坑专用
           */
          public class TmpActivity extends Activity {
    
              @Override
              protected void onCreate(Bundle savedInstanceState) {
                  super.onCreate(savedInstanceState);
                  setContentView(R.layout.activity_tmp);
              }
          }
    
          <!--占坑专用Activity-->
          <activity android:name=".TmpActivity"/>
    
  • attachBaseContext中欺骗应用

    @Override
    protected void attachBaseContext(Context newBase) {
        super.attachBaseContext(newBase);

        try {
            /**
             * 欺骗ActivityManagerNative,将要启动的Activity替换成我们的占坑Activity
             */
            Class<?> activityManagerNativeClass = Class.forName("android.app" +
                    ".ActivityManagerNative");

            Field gDefaultField = activityManagerNativeClass.getDeclaredField("gDefault");
            gDefaultField.setAccessible(true);

            Object gDefault = gDefaultField.get(null);

            // gDefault是一个 android.util.Singleton对象; 我们取出这个单例里面的字段
            Class<?> singleton = Class.forName("android.util.Singleton");
            Field mInstanceField = singleton.getDeclaredField("mInstance");
            mInstanceField.setAccessible(true);

            // ActivityManagerNative 的gDefault对象里面原始的 IActivityManager对象
            final Object rawIActivityManager = mInstanceField.get(gDefault);

            // 创建一个这个对象的代理对象, 然后替换这个字段, 让我们的代理对象帮忙干活
            Class<?> iActivityManagerInterface = Class.forName("android.app.IActivityManager");
            Object proxy = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
                    new Class<?>[] {iActivityManagerInterface}, new InvocationHandler() {


                        @Override
                        public Object invoke(Object proxy, Method method, Object[] args) throws
                                                                                         Throwable {
                            if ("startActivity".equals(method.getName())) {
                                Intent raw;
                                int index = 0;

                                for (int i = 0; i < args.length; i++) {
                                    if (args[i] instanceof Intent) {
                                        index = i;
                                        break;
                                    }
                                }
                                raw = (Intent) args[index];

                                Intent newIntent = new Intent();

                                // 替身Activity的包名, 也就是我们自己的包名
                                String stubPackage = "com.jerey.activityplugin";

                                // 这里我们把启动的Activity临时替换为 StubActivity
                                ComponentName componentName = new ComponentName(stubPackage,
                                        TmpActivity.class
                                                .getName());
                                newIntent.setComponent(componentName);

                                // 把我们原始要启动的TargetActivity先存起来
                                newIntent.putExtra(EXTRA_TARGET_INTENT, raw);

                                // 替换掉Intent, 达到欺骗AMS的目的
                                args[index] = newIntent;

                                Log.d(TAG, "hook succes{s");
                                return method.invoke(rawIActivityManager, args);
                            }
                            return method.invoke(rawIActivityManager, args);
                        }
                    });
            mInstanceField.set(gDefault, proxy);


            /**
             * 欺骗ActivityThread
             */

            // 先获取到当前的ActivityThread对象
            Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
            Field currentActivityThreadField = activityThreadClass.getDeclaredField
                    ("sCurrentActivityThread");
            currentActivityThreadField.setAccessible(true);
            Object currentActivityThread = currentActivityThreadField.get(null);

            // 由于ActivityThread一个进程只有一个,我们获取这个对象的mH
            Field mHField = activityThreadClass.getDeclaredField("mH");
            mHField.setAccessible(true);
            final Handler mH = (Handler) mHField.get(currentActivityThread);

            // 设置它的回调, 根据源码:
            // 我们自己给他设置一个回调,就会替代之前的回调;

            //        public void dispatchMessage(Message msg) {
            //            if (msg.callback != null) {
            //                handleCallback(msg);
            //            } else {
            //                if (mCallback != null) {
            //                    if (mCallback.handleMessage(msg)) {
            //                        return;
            //                    }
            //                }
            //                handleMessage(msg);
            //            }
            //        }

            Field mCallBackField = Handler.class.getDeclaredField("mCallback");
            mCallBackField.setAccessible(true);

            mCallBackField.set(mH, new Handler.Callback() {
                @Override
                public boolean handleMessage(Message msg) {
                    if (msg.what == 100) {
                        Object obj = msg.obj;
                        // 根据源码:
                        // 这个对象是 ActivityClientRecord 类型
                        // 我们修改它的intent字段为我们原来保存的即可.
                        // switch (msg.what) {
                        //      case LAUNCH_ACTIVITY: {
                        //          Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
                        //          final ActivityClientRecord r = (ActivityClientRecord) msg.obj;

                        //          r.packageInfo = getPackageInfoNoCheck(
                        //                  r.activityInfo.applicationInfo, r.compatInfo);
                        //         handleLaunchActivity(r, null);
                        try {
                            // 把替身恢复成真身
                            Field intent = obj.getClass().getDeclaredField("intent");
                            intent.setAccessible(true);
                            Intent raw = (Intent) intent.get(obj);

                            Intent target = raw.getParcelableExtra(EXTRA_TARGET_INTENT);
                            raw.setComponent(target.getComponent());

                        } catch (NoSuchFieldException e) {
                            e.printStackTrace();
                        } catch (IllegalAccessException e) {
                            e.printStackTrace();
                        }
                        mH.handleMessage(msg);
                    }
                    return true;
                }
            });


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

上面的代码,我们先反射拿到ActivityManagerNative,然后动态代理IActivityManager,Hook其startActivity方法,在里面替换掉intent,并将真实的Intent存放在假Intent的参数里面。

在系统最后调用打开假Intent的时候,我们从Intent中取出参数,并打开真正想打开的Activity。

  • 打开Activity 我们和正常使用一样,startActivity就能打开我们未注册的Activity了。
    findViewById(R.id.btn).setOnClickListener(new View.OnClickListener() {
      @Override
      public void onClick(View v) {
          startActivity(new Intent(MainActivity.this, UnregisterActivity.class));
      }
    });
    

总结

上面只是一个Demo,不能支持support包的AppCompatActivity,真正的完整的插件化库任务是艰巨的! 还要支持其他组件,都是很麻烦的事情。


本文作者:Anderson/Jerey_Jobs

博客地址 : http://jerey.cn/
简书地址 : Anderson大码渣
github地址 : https://github.com/Jerey-Jobs

作者:Anderson大码渣,欢迎关注我的简书: Anderson大码渣