Android simple implementation of the plug-in: Launch Activity unregistered

personal blog

http://www.milovetingting.cn

Android simple implementation of the plug-in: Launch Activity unregistered

Foreword

This article describes how to start Activity is not registered in AndroidManifest in the Android in a solution. The main need to have knowledge of the following points:

  1. reflection

  2. Class loader

  3. Activity of the boot process

  4. Resource loading process

Start Activity unregistered app

Activity in AndroidManifest default need to be registered, the application is not registered can not start. AMS when you start the application, it will detect if already registered. So, if you want to start Activity is not registered, then you need before Activity, replace Intent to start the application is already registered with the Activity, so you can create a new Activity, for placeholder. After detection by the real start and then replaced back Unregistered Activity needs to start before Activity.

Hook obtain a replacement point of Intent

After calling startActivity method, and finally calls the remote method in the AMS Instrumentation execStartActivity method of processing. Android6.0 and Android6.0 above and below, call AMS at execStartActivity the methods are different, and therefore needs to be done compatible processing.

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

AMS to get by calling ActivityManagerNative.getDefault ().

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

AMS to get by calling ActivityManager.getService ().

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

Hook get the point reduction of Intent

Android8.0 and below

Start Activity messages, calls back to dispatchMessage mH method of ActivityThread in, you can set a callBack by giving mH, in handleMessage callBack, and then replaced back to the real start of Intent, and then returns false, let handleMessage continue treatment.

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

Android8.0 and below, handleMessage methods ActivityThread of mH in the middle, will deal with LAUNCH_ACTIVITY type of message, here called handleLaunchActivity way to start the 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

And 8.0, as set callBack, then modify Intent.

In handleMessage ActivityThread mH to the method, the message will be processed EXECUTE_TRANSACTION type, called here a method 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 method calls 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);
            }
        }
    }

This method will call the execute method in the ClientTransactionItem. ClientTransactionItem in ActivityStackSupervisor added in 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));
            }

Thus, ClientTransactionItem particular class corresponding LaunchActivityItem, which corresponds to execute method

@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);
    }

In its method in turn calls the ClientTransactionHandler of handleLaunchActivity, and ClientTransactionHandler is defined in the ActivityThread

private final TransactionExecutor mTransactionExecutor = new TransactionExecutor(this);

ActivityThread inherited ClientTransactionHandler, then it will achieve handleLaunchActivity. The final start Activity In this method

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

            }
}

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

Hook is created when the Application

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

Here, you can enable Activity is not registered within the same application.

Activity in the plug-in application start

Activity in the non-start the same application, start compared to the Activity within the same application, we need a few more steps. Because not within an application, so it is necessary to load the APK plug-in, then Intent also need to be replaced before the AMS detected as a placeholder of Intent, in the post-test, before starting back to replace the need to start Activity Activity of Intent. In addition, due to the plug-in is dynamically loaded into, but also need to address the problem of resource loading.

Load plugins

The main plug is used to load the class loader

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

    }

Replace Intent

This process and the situation in the application is the same, not repeat them

Load Resources

The main method used to load the resource addAssetPath AssetManager, and to load through reflection

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

Source

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

Guess you like

Origin www.cnblogs.com/milovetingting/p/12458929.html
Recommended