美文网首页技术进阶
从零开始实现一个插件化框架(二)

从零开始实现一个插件化框架(二)

作者: PanGeng | 来源:发表于2020-11-30 08:33 被阅读0次

    上一篇讲了插件化的概念和类加载机制,并实现了从插件apk中合并,并加载一个类。不知道大家还记不记得,实现插件化,只需解决三个问题即可:

    • 如何加载插件中的类?
    • 如何加载插件中的资源?
    • 当然还有最重要的一个问题,四大组件如何调用呢?四大组件是需要注册的,而插件apk中的组件显然不会在宿主提前注册,那么如何去调用它呢?

    第一个问题在上一篇文章中已经实现,今天我们就来讲一下插件中的四大组件 - Activity的启动。本篇讲的内容比较多,小伙伴们做好准备啦!

    有同学说:既然已经合并了插件的所有的类,那我直接在宿主中直接启动activity不就可以了吗?

    // 跳转插件 Activity
    val intent = Intent()
    // 简单的科普一下 , setComponent()和setClassName()是一样的
    // setClassName最终会调用setComponent方法
    intent.component = ComponentName("com.kangf.plugin", "com.kangf.plugin.MainActivity")
    startActivity(intent)
    

    答案是当然不行,因为Activity是需要注册的,启动的时候会在AMS中进行验证。我们只是合并了类,manifest是不会合并的,直接启动就会抛出没有注册的异常。这个问题怎么解决呢,有没有办法绕过这个验证呢?注意:以下内容都是拿API28举例,每个版本的API都有可能不同

    Activity的启动流程

    首先来看一下Activity的启动流程:

    image

    当调用了startActivity方法的时候,会调用到Instrumentation的execStartActivity,然后会通过一个服务(AMS, 也就是ActivityManagerService)去验证,验证通过之后回到主线程正常启动。我们要做的,就是在AMS验证之前偷偷把要启动的Activity替换成宿主中的一个代理Activity,验证通过之后,回到主线程,再将替换掉的Activity替换回来,起到了一个瞒天过海的作用(这里下面会详细介绍)。

    Hook

    先来了解一下Hook的概念,Hook 中文意思就是 钩子。简单说,它的作用就是改变代码的正常执行流程

    image

    比如对象A与对象B互相调用,我们加入一个钩子,这时候A调用B的时候 ,必须要经过Hook层,通过Hook再调用B,同理,B调用A也是一样。就像上面提到的,我们通过Hook去修改AMS中的Activity,验证完成后又通过Hook修改要启动的Activity。这样思路就出来了。

    image

    Hook实现方式

    那么,通过什么技术来实现Hook呢?有两种方式

    • 反射
    • 动态代理

    Hook点

    1. 什么是Hook点?

      挂钩子的地方就是Hook点

    2. 查找Hook点的原则?

      • 尽量静态变量或者单例对象。

      • 尽量 Hook public 的对象和方法。

    寻找Hook点

    有了思路,下面就开始从源码来寻找Hook点。首先来看Instrumentation的跳转方法

    // android.app.Instrumentation
    public ActivityResult execStartActivity(
                Context who, IBinder contextThread, IBinder token, Activity target,
                Intent intent, int requestCode, Bundle options) {
            IApplicationThread whoThread = (IApplicationThread) contextThread;
            Uri referrer = target != null ? target.onProvideReferrer() : null;
            if (referrer != null) {
                intent.putExtra(Intent.EXTRA_REFERRER, referrer);
            }
            if (mActivityMonitors != null) {
                synchronized (mSync) {
                    final int N = mActivityMonitors.size();
                    for (int i=0; i<N; i++) {
                        final ActivityMonitor am = mActivityMonitors.get(i);
                        ActivityResult result = null;
                        if (am.ignoreMatchingSpecificIntents()) {
                            result = am.onStartActivity(intent);
                        }
                        if (result != null) {
                            am.mHits++;
                            return result;
                        } else if (am.match(who, null, intent)) {
                            am.mHits++;
                            if (am.isBlocking()) {
                                return requestCode >= 0 ? am.getResult() : null;
                            }
                            break;
                        }
                    }
                }
            }
            try {
                intent.migrateExtraStreamToClipData();
                intent.prepareToLeaveProcess(who);
                // 调用AMS来启动Activity
                int result = ActivityManager.getService()
                    .startActivity(whoThread, who.getBasePackageName(), intent,
                            intent.resolveTypeIfNeeded(who.getContentResolver()),
                            token, target != null ? target.mEmbeddedID : null,
                            requestCode, 0, null, options);
                checkStartActivityResult(result, intent);
            } catch (RemoteException e) {
                throw new RuntimeException("Failure from system", e);
            }
            return null;
        }
    

    可以看到最终调用了ActivityManager.getService()来启动Activity,它返回的是一个什么对象呢?继续往下看:

    // android.app.ActivityManager
    public static IActivityManager getService() {
            return IActivityManagerSingleton.get();
    }
    
    private static final Singleton<IActivityManager> IActivityManagerSingleton =
                new Singleton<IActivityManager>() {
                    @Override
                    protected IActivityManager create() {
                        final IBinder b = ServiceManager.getService(Context.ACTIVITY_SERVICE);
                        final IActivityManager am = IActivityManager.Stub.asInterface(b);
                        return am;
                    }
                };
    
    // android.util.Singleton
    public abstract class Singleton<T> {
        private T mInstance;
    
        public Singleton() {
        }
    
        protected abstract T create();
    
        public T get() {
            synchronized(this) {
                if (this.mInstance == null) {
                    this.mInstance = this.create();
                }
    
                return this.mInstance;
            }
        }
    }
    

    可以看到,是调用了Singleton的get()方法,返回了一个IActivityManager,那么我们就可以从这里下手,Hook住IActiviyManager的startActivity方法(简单来说就是替换掉Singleton的mInstanse实例)。这里使用动态代理和反射,都很简单,就不多做介绍了。

    撸码阶段

    找到了Hook点,我们就开始撸码:

    代理对象

    因为要替换IActivityManager中的startActivity方法,所以就代理这个类:

    首先获取到IActivityManager的class对象,再通过Proxy.newProxyInstance创建一个代理对象,其实这里就返回了一个新的IActivityManager。可以看到有3个参数:

    1. 传一个ClassLoader对象,这里直接使用当前线程的ClassLoader就可以了
    2. 要代理的类,这里是一个 数组,我们只需要代理IActivityManager的类就可以了
    3. 回调函数,每执行要代理的类中的方法的时候,都会走到这个回调函数,所以我们只需要判断method.getName()如果是startActivity的话,就修改它的参数

    具体的步骤都在注释里面

    // 获取IActivityManager的Class
    Class<?> iActivityManagerClass = Class.forName("android.app.IActivityManager");
    // 代理IActivityManager
    Object newInstance = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
                        new Class[]{iActivityManagerClass},
                        new InvocationHandler() {
                            // 没执行一个方法,都会调用到这里
                            @Override
                            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    
                                Log.e("kangf", "old args === " + args.length);
                                // 如果是startActivity方法的话
                                if (method.getName().equals("startActivity")) {
                                    // startActivity方法有多个参数
                                    // 这里是索引,记录Intent参数的索引
                                    int index = 0;
                                    // 遍历所有的参数,获取Intent参数的索引
                                    for (int i = 0; i < args.length; i++) {
                                        if (args[i] instanceof Intent) {
                                            index = i;
                                            break;
                                        }
                                    }
                                    // 重新创建一个Intent
                                    Intent proxyIntent = new Intent();
                                    proxyIntent.setClassName("com.kangf.dynamic",
                                            "com.kangf.dynamic.ProxyActivity");
                                    // 并将原来的intent记录下来
                                    proxyIntent.putExtra("oldIntent", (Intent) args[index]);
                                    // 给Intent重新赋值,让它变成我们的代理Activity,这样验证就通过了
                                    args[index] = proxyIntent;
                                }
    
                                Log.e("kangf", "change args === " + args.length);
                                // 在这里还需要继续执行这个方法
                                return method.invoke(mInstance, args);
                            }
                        });
    

    注意,最后执行方法的时候,有个mInstance对象,说明执行的是mInstance对象里面的方法,为什么这么写呢?

    偷天换日,替换原来的Intent

    上面我们已经分析了,要替换的是Singleton对象里面的实例,所以这个mInstance其实就是IActivityManager实例,那么继续:

    // 获取ActivityManager的Class
    Class<?> activityManagerClass = Class.forName("android.app.ActivityManager");
    // 根据Class获取IActivityManagerSingleton私有字段
    Field iActivityManagerSingletonField = activityManagerClass.getDeclaredField("IActivityManagerSingleton");
    
    iActivityManagerSingletonField.setAccessible(true);
    
    // 根据iActivityManagerSingletonField字段获取Singleton对象
    Object singleTon = iActivityManagerSingletonField.get(null);
    
    Class<?> singleTonClass = Class.forName("android.util.Singleton");
    
    Field mInstanceField = singleTonClass.getDeclaredField("mInstance");
    mInstanceField.setAccessible(true);
    
    // 获取到真正的IActivityManger的实例对象
    final Object mInstance = mInstanceField.get(singleTon);
    

    这样就获取到了IActivityManger的实例对象。好,那我们来运行一下,看看是不是跳转到了代理的Activity呢?(注意:此方法只能运行在android8.0和android9.0的手机上,其他版本需要单独适配,下一篇文章会讲到)

    <img src="https://img-blog.csdnimg.cn/20200120142810920.gif" style="zoom:30%;" />

    ActivityThread

    上一步是在AMS检测之前,将原来的Intent替换掉了,让它检测宿主中的Activity,可我们要做的是跳转到插件,这样我们就需要在AMS检测完成之后,再讲Intent替换插件的intent。

    在AMS检测完成之后,会走到ActivityStackSupervisor,这个类中的realStartActivityLocked方法。先来看一下API 28的启动时序图:

    在这里插入图片描述

    那我们就从源码看起:

    // com.android.server.am.ActivityStackSupervisor.java 
    final boolean realStartActivityLocked(ActivityRecord r, ProcessRecord app,
                boolean andResume, boolean checkConfig) throws RemoteException {
    
            //  省略不重要的代码 ...
    
            try {
                //  省略不重要的代码 ...
                try {
                    //  省略不重要的代码 ...
                    // Create activity launch transaction.
                    final ClientTransaction clientTransaction = ClientTransaction.obtain(app.thread,
                            r.appToken);
                    clientTransaction.addCallback(LaunchActivityItem.obtain(new Intent(r.intent),
                            System.identityHashCode(r), r.info,
                            // TODO: Have this take the merged configuration instead of separate global
                            // and override configs.
                            mergedConfiguration.getGlobalConfiguration(),
                            mergedConfiguration.getOverrideConfiguration(), r.compat,
                            r.launchedFromPackage, task.voiceInteractor, app.repProcState, r.icicle,
                            r.persistentState, results, newIntents, mService.isNextTransitionForward(),
                            profilerInfo));
                    // Set desired final state.
                    final ActivityLifecycleItem lifecycleItem;
                    if (andResume) {
                        lifecycleItem = ResumeActivityItem.obtain(mService.isNextTransitionForward());
                    } else {
                        lifecycleItem = PauseActivityItem.obtain();
                    }
                    clientTransaction.setLifecycleStateRequest(lifecycleItem);
    
                    // Schedule transaction.
                    mService.getLifecycleManager().scheduleTransaction(clientTransaction);
                    // ...
    
                } catch (RemoteException e) {
                    // ...
                    throw e;
                }
            } finally {
                endDeferResume();
            }
    
           // ...
    
            return true;
        }
    

    代码片段太长,把与我们用到的无关的代码省略掉了,有兴趣的可以自己查看源码:com.android.server.am.ActivityStackSupervisor.java

    需要注意的是,ClientTransaction加入了一个callback: LaunchActivityItem,记住,这里是重点!

    可以看到,最终走到了

    mService.getLifecycleManager().scheduleTransaction(clientTransaction);

    继续跟踪到ClientLifecycleManager.java的scheduleTransaction方法:

    // ClientLifecycleManager.java
    void scheduleTransaction(ClientTransaction transaction) throws RemoteException {
            final IApplicationThread client = transaction.getClient();
            transaction.schedule();
            if (!(client instanceof Binder)) {
                transaction.recycle();
            }
        }
    

    发现调用了ClientTransaction的schedule()方法:

    // ClientTransaction.java
    public void schedule() throws RemoteException {
            mClient.scheduleTransaction(this);
    }
    

    mClient是IApplicationThread,调用了它的scheduleTransaction,这个IApplicationThread是不是很熟悉?没错,其实就是ActivityThread,继续往下看:

    // ActivityThread.java
    @Override
    public void scheduleTransaction(ClientTransaction transaction) throws RemoteException {
        ActivityThread.this.scheduleTransaction(transaction);
    }
    

    点进去看,好乱~

    // ClientTransactionHandler
    void scheduleTransaction(ClientTransaction transaction) {
        transaction.preExecute(this);
        sendMessage(ActivityThread.H.EXECUTE_TRANSACTION, transaction);
    }
    

    诶,发送了一个消息:ActivityThread.H.EXECUTE_TRANSACTION,这个消息在ActivityThread中接收:

    public static final int EXECUTE_TRANSACTION = 159;
     
    public void handleMessage(Message msg) {
                if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
                switch (msg.what) {
                        
                    // ...
                    case EXECUTE_TRANSACTION:
                        final ClientTransaction transaction = (ClientTransaction) msg.obj;
                        mTransactionExecutor.execute(transaction);
                        if (isSystem()) {
                            transaction.recycle();
                        }
                        break;
                    // ...
                }
                // ...
            }
    

    现在回到了主线程 ,它又调用了mTransactionExecutor的excute方法,并传入了一个transaction:

    // TransactionExecutor
    public void execute(ClientTransaction transaction) {
            final IBinder token = transaction.getActivityToken();
            log("Start resolving transaction for client: " + mTransactionHandler + ", token: " + token);
    
            executeCallbacks(transaction);
    
            executeLifecycleState(transaction);
            mPendingActions.clear();
            log("End resolving transaction");
    }
    
    public void executeCallbacks(ClientTransaction transaction) {
            final List<ClientTransactionItem> callbacks = transaction.getCallbacks();   
            // 。。。
            final int size = callbacks.size();
            for (int i = 0; i < size; ++i) {
                final ClientTransactionItem item = callbacks.get(i);
                // 。。。
    
                item.execute(mTransactionHandler, token, mPendingActions);
                item.postExecute(mTransactionHandler, token, mPendingActions);
                // 。。。
            }
        }
    

    最后调用了item.execute方法,这个item是ClientTransactionItem对象,ClientTransactionItem又是从callbacks集合中获取的,那这个callbacks又是什么呢?从上面可以看到,是transaction里面拿到的!还记得上面的addCallback吗?其实就是调用了LaunchActivityItem中的execute方法!

    @Override
        public void execute(ClientTransactionHandler client, IBinder token,
                PendingTransactionActions pendingActions) {
            Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
            ActivityClientRecord r = new ActivityClientRecord(token, mIntent, mIdent, mInfo,
                    mOverrideConfig, mCompatInfo, mReferrer, mVoiceInteractor, mState, mPersistentState,
                    mPendingResults, mPendingNewIntents, mIsForward,
                    mProfilerInfo, client);
            // 调用了handleLaunchActivity
            client.handleLaunchActivity(r, pendingActions, null /* customIntent */);
            Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
        }
    

    到这里一目了然了, 跳转Activity时构造了一个ActivityClientRecord对象,这个对象里面传入了mIntent, 这是个私有的成员变量,是不是思路就出来了?我们只需要拿到这个intent并修改它就可以了!

    既然时个私有变量,那Hook的时候,就需要得到LaunchActivityItem的实例对象,怎么获得呢?首先要知道LaunchActivityItem时怎么来的?我们还是从上面的addCallback方法入手, 继续往上跟源码:

    // ClientTransaction.java
    public void addCallback(ClientTransactionItem activityCallback) {
            if (mActivityCallbacks == null) {
                mActivityCallbacks = new ArrayList<>();
            }
            mActivityCallbacks.add(activityCallback);
    }
    

    到这里发现其实是把LaunchActivityItem放进了mActivityCallbacks集合里面,那么我们只需要获取到这个集合,遍历如果是LaunchActivityItem就可以了,ClientTransaction是什么就不用多说了吧?不就是ActvityThread中 msg接收的对象吗!

    有了思路,接下来就正式撸码~

    Hook Handler

    还有一步,首先要拦截到ActivityThread中的handleMessage方法,接收到159(public static final int EXECUTE_TRANSACTION = 159;)这个消息才进行替换intent的操作。那我们先来看一下Handler:

    public Handler() {
        this(null, false);
    }
    
    public Handler(Callback callback, boolean async) {
        // .....   
    }
    

    只看构造方法就可以了,在Handler构造的时候,传入了要给Callback,这个有什么用呢?搜一下mCallback看看:

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

    这里不难理解,接收消息首先会走dispatchMessage方法,如果callback不为空,就会走mCallback.handleMessage,并返回一个布尔值,如果返回true,那么拦截消息,否则继续走handleMessage方法。这样,我们就可以Hook住ActivityThread中的Handler,给它加上一个callback!

    ActivityThread中的Handler怎么拿到呢?

    final H mH = new H();
    private static volatile ActivityThread sCurrentActivityThread;
    

    H继承自Handler,它直接在全局定义了一个Handler,还有它本身的一个实例,这样拿到它就简单很多了~接下来看一下完整的hook代码!

    将代理的intent替换回来

    public static void hookHandler() {
    
            try {
                // 获取ActivityThread类
                final Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
                // 拿到ActivityThread对象
                Field activityThreadField = activityThreadClass.getDeclaredField("sCurrentActivityThread");
                activityThreadField.setAccessible(true);
                final Object activityThread = activityThreadField.get(null);
    
                // 通过activityThread对象获取Handler实例
                Field mHField = activityThreadClass.getDeclaredField("mH");
                mHField.setAccessible(true);
                Object mH = mHField.get(activityThread);
    
                
                Class<?> handlerClass = Class.forName("android.os.Handler");
                Field mCallbackField = handlerClass.getDeclaredField("mCallback");
                mCallbackField.setAccessible(true);
                // 给它的handler设置一个callback,并返回false,这里要让它继续往下走 ,不然就拦截掉了
                mCallbackField.set(mH, new Handler.Callback() {
    
                    @Override
                    public boolean handleMessage(Message msg) {
    
                        Log.e("kangf", "handling code = " + msg.what);
    
                        switch (msg.what) {
                            // 1. 接收到159的消息
                            case 159:
                                try {
                                    // 2. 获取mActivityCallbacks字段
                                    Field mActivityCallbacksField = msg.obj.getClass().getDeclaredField("mActivityCallbacks");
                                    mActivityCallbacksField.setAccessible(true);
                                    // 3. 通过字段获取mActivityCallbacks集合
                                    List<Object> mActivityCallbacks = (List<Object>) mActivityCallbacksField.get(msg.obj);
                                    // 4. 遍历这个集合,如果是LaunchActivityItem的话,进行hook
                                    for (int i = 0; i < mActivityCallbacks.size(); i++) {
                                        Class<?> itemClass = mActivityCallbacks.get(i).getClass();
                                        if (itemClass.getName().equals("android.app.servertransaction.LaunchActivityItem")) {
                                            // 开始hook
                                            // 5. 获取LaunchActivityItem中的intent字段
                                            Field intentField = itemClass.getDeclaredField("mIntent");
                                            
                                            intentField.setAccessible(true);
                                            // 6. 根据字段获取到代理的intent
                                            Intent proxyIntent = (Intent) intentField.get(mActivityCallbacks.get(i));
                                            // 7. 拿到保存过的intent
                                            Intent intent = proxyIntent.getParcelableExtra("oldIntent");
                                            // 8. 把原来的替换回来
                                            proxyIntent.setComponent(intent.getComponent());
                                            break;
                                        }
                                    }
                                } catch (Exception e) {
                                    e.printStackTrace();
                                }
                                break;
                        }
    
                        // 这里必须返回false
                        return false;
    
                    }
                });
    
    
            } catch (Exception e) {
                e.printStackTrace();
            }
    
        }
    

    上面每一步都在注释里写得很清楚了,这样就完成了插件Activity的跳转

    插件中的Activity这里先亮出来:

    class MainActivity : Activity() {
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            // setContentView(R.layout.activity_main)
    
            Toast.makeText(this, "插件的MainActivity", Toast.LENGTH_SHORT).show()
        }
    }
    
    

    加载资源需单独适配 ,这个留到下一节再讲,这里先土司一下.

    多说无益,我们来看效果:

    <img src="https://img-blog.csdnimg.cn/20200120160415320.gif" alt="在这里插入图片描述" style="zoom:50%;" />

    最后提一下,为什么以前用的kt, 现在用java呢? kt在hook IActivityManager时,会报一个错误,它的可变长度参数被java当成了一个array处理了,结果报以下错误,意思就是本来又10个参数,你只传了1个:

    java.lang.IllegalArgumentException: Wrong number of arguments; expected 10, got 1
    

    注意

    以上都是拿API28来举例,只能运行在Android8.0 和 Android9.0的手机上,每个版本的API都有可能是不一样的,适配会在下一篇提一下,github中已经做了处理,有兴趣的可以看一下:

    传送门

    相关文章

      网友评论

        本文标题:从零开始实现一个插件化框架(二)

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