Implement a plug-in framework from scratch (2)

The last article talked about the concept of plug-inization and class loading mechanism, and realized the merging from the plug-in apk and loading a class. I don’t know if you still remember, to achieve plug-in, you only need to solve three problems:

  • How to load the classes in the plugin?
  • How to load the resources in the plugin?
    Of course, there is the most important question, how to call the four major components? The four major components need to be registered, and the components in the plug-in apk will obviously not be registered in advance on the host, so how to call it?
    The first question has been implemented in the previous article, today we will talk about the four major components in the plug-in-Activity startup. This article talks about more content, friends are ready!

Some students said: Since all the classes of the plug-in have been merged, can't I start the activity directly in the host?

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

The answer is of course not, because Activity needs to be registered and will be verified in AMS when it is started. We just merged the classes, the manifest will not be merged, and an unregistered exception will be thrown when started directly. How to solve this problem, is there a way to bypass this verification? Note: The following are examples of API28, each version of the API may be different

Activity start process

First look at the startup process of Activity:


When the startActivity method is called, the execStartActivity of Instrumentation will be called, and then it will be verified by a service (AMS, which is ActivityManagerService). After the verification is passed, it will return to the main thread to start normally. What we have to do is to secretly replace the activity to be started with a proxy activity in the host before the AMS verification. After the verification is passed, return to the main thread, and then replace the replaced Activity back, which plays a role of hiding the world ( This will be described in detail below).

Hook
first understand the concept of Hook, Hook Chinese means hook. Simply put, its role is to change the normal execution flow of the code

For example, when object A and object B call each other, we add a hook. At this time, when A calls B, it must go through the Hook layer, and then call B through the Hook. Similarly, B calls A. As mentioned above, we use Hook to modify the Activity in AMS, and after verification is completed, we use Hook to modify the Activity to be started. This way the idea came out.


Hook implementation method
So, what technology is used to implement Hook? There are two ways

Reflection
dynamic proxy
hook point
What is a hook point?

The hook point is the Hook point

The principle of finding Hook points?

Try to static variables or singleton objects.

Try to Hook public objects and methods.

Find Hook Point

With an idea, let's start looking for hook points from the source code. First look at the jump method of 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;
    }

You can see that ActivityManager.getService() is finally called to start the Activity. What object does it return? Continue to look down:

// 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;
        }
    }
}

It can be seen that the get() method of Singleton is called, and an IActivityManager is returned, then we can start from here, Hook live IActiviyManager's startActivity method (in short, it is to replace the mInstanse instance of Singleton). The use of dynamic proxy and reflection here is very simple, so I won't introduce more.

Code stage

When we found the Hook point, we started to code:

Proxy object

Because we want to replace the startActivity method in IActivityManager, we delegate this class:

First get the class object of IActivityManager, and then create a proxy object through Proxy.newProxyInstance. In fact, a new IActivityManager is returned here. You can see that there are 3 parameters:

传一个ClassLoader对象,这里直接使用当前线程的ClassLoader就可以了
要代理的类,这里是一个 数组,我们只需要代理IActivityManager的类就可以了
回调函数,每执行要代理的类中的方法的时候,都会走到这个回调函数,所以我们只需要判断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的手机上,其他版本需要单独适配,下一篇文章会讲到)


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()
    }
}

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

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


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

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

kotlin再传递可变长参数时,前面需要加上 * 标识,比如:

method.invoke(mInstance, *args);

注意

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

传送门
原文作者:Pan Geng
原文链接:https://blog.csdn.net/qq_22090073/java/article/details/104053249

Guess you like

Origin blog.csdn.net/AndroidAlvin/article/details/107532733