zoukankan      html  css  js  c++  java
  • Android中插件化的简单实现:启动未注册的Activity

    个人博客

    http://www.milovetingting.cn

    Android中插件化的简单实现:启动未注册的Activity

    前言

    本文介绍在Android中启动未在AndroidManifest中注册的Activity的一个解决方案。主要需要掌握以下知识点:

    1. 反射

    2. 类加载

    3. Activity的启动过程

    4. Resource加载过程

    启动应用内未注册的Activity

    Activity默认都需要在AndroidManifest中注册,未注册的应用无法启动。AMS在启动应用时,会检测是否已经注册。因此,如果想要启动未注册的Activity,那么需要在Activity前,替换启动应用的Intent为已经注册过的Activity,因此可以新建一个Activity,用于占位。在检测通过后,真正启动Activity前再替换回需要启动的未注册的Activity。

    获取替换Intent的Hook点

    调用startActivity方法后,最后都会在Instrumentation的execStartActivity方法中调用AMS的远程方法进行处理。Android6.0及以下和Android6.0以上,在execStartActivity中调用AMS的方法有所不同,因此需要做兼容处理。

    6.0

    public ActivityResult execStartActivity(
                Context who, IBinder contextThread, IBinder token, Activity target,
                Intent intent, int requestCode, Bundle options) {
            //...
            int result = ActivityManagerNative.getDefault()
                    .startActivity(whoThread, who.getBasePackageName(), intent,
                            intent.resolveTypeIfNeeded(who.getContentResolver()),
                            token, target != null ? target.mEmbeddedID : null,
                            requestCode, 0, null, options);
            //...
        }
    

    通过调用ActivityManagerNative.getDefault()来获取AMS。

    8.0

    public ActivityResult execStartActivity(
                Context who, IBinder contextThread, IBinder token, Activity target,
                Intent intent, int requestCode, Bundle options) {
            IApplicationThread whoThread = (IApplicationThread) contextThread;
            //...
            int result = ActivityManager.getService()
                    .startActivity(whoThread, who.getBasePackageName(), intent,
                            intent.resolveTypeIfNeeded(who.getContentResolver()),
                            token, target != null ? target.mEmbeddedID : null,
                            requestCode, 0, null, options);
            //...
        }
    

    通过调用ActivityManager.getService()来获取AMS。

    替换Intent

    public static void hookAMS() {
            try {
                Field singletonField;
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                    singletonField = getField(Class.forName("android.app.ActivityManager"), "IActivityManagerSingleton");
                } else {
                    singletonField = getField(Class.forName("android.app.ActivityManagerNative"), "gDefault");
                }
                Object singleton = singletonField.get(null);
    
                Field mInstanceField = getField(Class.forName("android.util.Singleton"), "mInstance");
                final Object mInstance = mInstanceField.get(singleton);
    
                final Object proxyInstance = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class[]{Class.forName("android.app.IActivityManager")}, new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        if ("startActivity".equals(method.getName())) {
                            int index = 0;
                            for (int i = 0; i < args.length; i++) {
                                if (args[i] instanceof Intent) {
                                    index = i;
                                    break;
                                }
                            }
                            Intent intent = (Intent) args[index];
    
                            Intent proxyIntent = new Intent(intent);
                            //占位的Activity
                            proxyIntent.setClassName("com.wangyz.plugindemo", "com.wangyz.plugindemo.ProxyActivity");
                            proxyIntent.putExtra("target_intent", intent);
    
                            args[index] = proxyIntent;
                        }
                        return method.invoke(mInstance, args);
                    }
                });
                mInstanceField.set(singleton, proxyInstance);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    

    获取还原Intent的Hook点

    Android8.0及以下

    启动Activity的消息,会回调到ActivityThread中的mH的dispatchMessage方法,可以通过给mH设置一个callBack,在callBack的handleMessage中,然后替换回真正要启动的Intent,然后返回false,让handleMessage再继续处理。

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

    Android8.0及以下,在ActivityThread的mH中的handleMessage方法中,会处理LAUNCH_ACTIVITY类型的消息,在这里调用了handleLaunchActivity方法来启动Activity。

    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);
                        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                    } break;
    

    Android9.0

    和8.0一样,设置callBack,然后修改Intent。

    在ActivityThread的mH中的handleMessage方法中,会处理EXECUTE_TRANSACTION类型的消息,在这里调用了TransactionExecutor.execute方法

    case EXECUTE_TRANSACTION:
                        final ClientTransaction transaction = (ClientTransaction) msg.obj;
                        mTransactionExecutor.execute(transaction);
                        if (isSystem()) {
                            // Client transactions inside system process are recycled on the client side
                            // instead of ClientLifecycleManager to avoid being cleared before this
                            // message is handled.
                            transaction.recycle();
                        }
                        // TODO(lifecycler): Recycle locally scheduled transactions.
                        break;
    

    execute方法中会调用executeCallbacks

    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();
            if (callbacks == null) {
                // No callbacks to execute, return early.
                return;
            }
            log("Resolving callbacks");
    
            final IBinder token = transaction.getActivityToken();
            ActivityClientRecord r = mTransactionHandler.getActivityClient(token);
    
            // In case when post-execution state of the last callback matches the final state requested
            // for the activity in this transaction, we won't do the last transition here and do it when
            // moving to final state instead (because it may contain additional parameters from server).
            final ActivityLifecycleItem finalStateRequest = transaction.getLifecycleStateRequest();
            final int finalState = finalStateRequest != null ? finalStateRequest.getTargetState()
                    : UNDEFINED;
            // Index of the last callback that requests some post-execution state.
            final int lastCallbackRequestingState = lastCallbackRequestingState(transaction);
    
            final int size = callbacks.size();
            for (int i = 0; i < size; ++i) {
                final ClientTransactionItem item = callbacks.get(i);
                log("Resolving callback: " + item);
                final int postExecutionState = item.getPostExecutionState();
                final int closestPreExecutionState = mHelper.getClosestPreExecutionState(r,
                        item.getPostExecutionState());
                if (closestPreExecutionState != UNDEFINED) {
                    cycleToPath(r, closestPreExecutionState);
                }
    
                item.execute(mTransactionHandler, token, mPendingActions);
                item.postExecute(mTransactionHandler, token, mPendingActions);
                if (r == null) {
                    // Launch activity request will create an activity record.
                    r = mTransactionHandler.getActivityClient(token);
                }
    
                if (postExecutionState != UNDEFINED && r != null) {
                    // Skip the very last transition and perform it by explicit state request instead.
                    final boolean shouldExcludeLastTransition =
                            i == lastCallbackRequestingState && finalState == postExecutionState;
                    cycleToPath(r, postExecutionState, shouldExcludeLastTransition);
                }
            }
        }
    

    这个方法里会调用ClientTransactionItem的execute方法。ClientTransactionItem是在ActivityStackSupervisor中的realStartActivityLocked中添加的

    final boolean realStartActivityLocked(ActivityRecord r, ProcessRecord app,
                boolean andResume, boolean checkConfig) throws RemoteException {
                    // 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));
                }
    

    因此,ClientTransactionItem对应的具体类为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);
            client.handleLaunchActivity(r, pendingActions, null /* customIntent */);
            Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
        }
    

    在它的方法里又调用了ClientTransactionHandler的handleLaunchActivity,而ClientTransactionHandler就是在ActivityThread中定义的

    private final TransactionExecutor mTransactionExecutor = new TransactionExecutor(this);
    

    ActivityThread继承了ClientTransactionHandler,那么它就会实现handleLaunchActivity。最终在这个方法里启动Activity

    public final class ActivityThread extends ClientTransactionHandler {
        @Override
        public Activity handleLaunchActivity(ActivityClientRecord r,
                PendingTransactionActions pendingActions, Intent customIntent) {
    
                }
    }
    

    还原Intent

    public static void hookHandler() {
            try {
                Field sCurrentActivityThreadThread = getField(Class.forName("android.app.ActivityThread"), "sCurrentActivityThread");
                Object activityThread = sCurrentActivityThreadThread.get(null);
    
                Field mHField = getField(Class.forName("android.app.ActivityThread"), "mH");
                Object mH = mHField.get(activityThread);
    
                Field mCallbackField = getField(Class.forName("android.os.Handler"), "mCallback");
                mCallbackField.set(mH, new Handler.Callback() {
                    @Override
                    public boolean handleMessage(Message msg) {
                        switch (msg.what) {
                            case 100: {
                                try {
                                    Field intentField = getField(msg.obj.getClass(), "intent");
                                    Intent proxyIntent = (Intent) intentField.get(msg.obj);
                                    Intent targetIntent = proxyIntent.getParcelableExtra("target_intent");
                                    if (targetIntent != null) {
    //                                    proxyIntent.setComponent(targetIntent.getComponent());
                                        intentField.set(msg.obj, targetIntent);
                                    }
                                } catch (Exception e) {
                                    e.printStackTrace();
                                }
    
                            }
                            break;
                            case 159: {
                                try {
                                    Field mActivityCallbacksField = getField(msg.obj.getClass(), "mActivityCallbacks");
                                    List mActivityCallbacks = (List) mActivityCallbacksField.get(msg.obj);
                                    for (int i = 0; i < mActivityCallbacks.size(); i++) {
                                        if (mActivityCallbacks.get(i).getClass().getName()
                                                .equals("android.app.servertransaction.LaunchActivityItem")) {
                                            Object launchActivityItem = mActivityCallbacks.get(i);
    
                                            Field mIntentField = getField(launchActivityItem.getClass(), "mIntent");
                                            Intent intent = (Intent) mIntentField.get(launchActivityItem);
                                            // 获取插件的
                                            Intent proxyIntent = intent.getParcelableExtra("target_intent");
                                            //替换
                                            if (proxyIntent != null) {
                                                mIntentField.set(launchActivityItem, proxyIntent);
                                            }
                                        }
                                    }
                                } catch (Exception e) {
                                    e.printStackTrace();
                                }
                            }
                            break;
                            default:
                                break;
                        }
                        return false;
                    }
                });
    
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    

    在Application创建时Hook

    @Override
        public void onCreate() {
            super.onCreate();
            //一般是从服务器下载回来,然后复制到应用的私有目录下,这里演示从sdcard复制到data目录下,6.0及以上需要申请动态权限。复制应该放在非UI线程上做,这里简化操作,放在UI线程上操作。
            String pluginPath = getDir("plugin", Context.MODE_PRIVATE).getAbsolutePath();
            pluginPath = pluginPath + "/plugin.apk";
            if (!new File(pluginPath).exists()) {
                FileUtil.copyFile(PLUGIN_PATH, pluginPath);
            }
            HookUtil.loadPlugin(this, pluginPath);
            HookUtil.hookAMS();
            HookUtil.hookHandler();
        }
    

    到这里,就可以启用同一应用内未注册的Activity。

    启动插件应用内的Activity

    启动非同一应用内的Activity,相比启动同一应用内的Activity,需要多几个步骤。由于不在一个应用内,所以需要把插件的APK先加载进来,然后同样也需要在AMS检测前替换Intent为占位的Intent,在检测后,启动Activity前替换回为需要启动Activity的Intent。另外,由于插件是动态加载进去的,也需要解决资源加载的问题。

    加载插件

    加载插件主要是用到类加载器

    public static void loadPlugin(Context context, String dexPath) {
    
            //判断dex是否存在
            File dex = new File(dexPath);
            if (!dex.exists()) {
                return;
            }
    
            try {
                //获取自己的dexElements
                PathClassLoader pathClassLoader = (PathClassLoader) context.getClassLoader();
    
                Field pathListField = getField(pathClassLoader.getClass(), "pathList");
                Object pathListObject = pathListField.get(pathClassLoader);
    
                Field dexElementsField = getField(pathListObject.getClass(), "dexElements");
                Object[] dexElementsObject = (Object[]) dexElementsField.get(pathListObject);
    
                //获取dex中的dexElements
                File odex = context.getDir("odex", Context.MODE_PRIVATE);
                DexClassLoader dexClassLoader = new DexClassLoader(dexPath, odex.getAbsolutePath(), null, pathClassLoader);
    
                Field pluginPathListField = getField(dexClassLoader.getClass(), "pathList");
                Object pluginPathListObject = pluginPathListField.get(dexClassLoader);
    
                Field pluginDexElementsField = getField(pluginPathListObject.getClass(), "dexElements");
                Object[] pluginDexElementsObject = (Object[]) pluginDexElementsField.get(pluginPathListObject);
    
                Class<?> elementClazz = dexElementsObject.getClass().getComponentType();
                Object newDexElements = Array.newInstance(elementClazz, pluginDexElementsObject.length + dexElementsObject.length);
                System.arraycopy(pluginDexElementsObject, 0, newDexElements, 0, pluginDexElementsObject.length);
                System.arraycopy(dexElementsObject, 0, newDexElements, pluginDexElementsObject.length, dexElementsObject.length);
    
                //设置
                dexElementsField.set(pathListObject, newDexElements);
    
            } catch (Exception e) {
                e.printStackTrace();
            }
    
        }
    

    替换Intent

    这个过程和应用内的情况是一样的,不再赘述

    加载资源

    加载资源主要用到AssetManager的addAssetPath方法,通过反射来加载

     private static Resources loadResource(Context context) {
            try {
                AssetManager assetManager = AssetManager.class.newInstance();
                Method addAssetPathField = assetManager.getClass().getDeclaredMethod("addAssetPath", String.class);
                addAssetPathField.setAccessible(true);
                addAssetPathField.invoke(assetManager, PATH);
                Resources resources = context.getResources();
                return new Resources(assetManager, resources.getDisplayMetrics(), resources.getConfiguration());
            } catch (Exception e) {
                e.printStackTrace();
            }
            return null;
        }
    

    源码

    https://github.com/milovetingting/Samples/tree/master/PluginDemo

  • 相关阅读:
    面试题29:数组中出现次数超过一半的数字
    面试题25:二叉树中和为某一值的路径
    Path Sum II
    面试题28:字符串的排列
    面试题24:二叉搜索树的后序遍历序列
    面试题23:从上往下打印二叉树
    面试题22:栈的压入、弹出序列
    面试题20:顺时针打印矩阵
    面试题18:树的子结构
    Linux 中使用 KVM
  • 原文地址:https://www.cnblogs.com/milovetingting/p/12458929.html
Copyright © 2011-2022 走看看