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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM