个人博客

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

更多相关文章

  1. ANDROID STUDIO&&Eclipse Android项目缺少R文件解决方法(完解)
  2. 浅谈Android(安卓)java层ServiceManager
  3. android 中activity的启动模式是singleTask时清除activity的栈顶
  4. android中WebView的Java与JavaScript交互
  5. 《第一行代码》 6.3 SharedPreferences存储
  6. Android(java)同步方法synchronized
  7. Android——RecyclerView入门学习之LayoutManager
  8. Android(安卓)通用获取Ip的方法(判断手机是否联网的方法)!!!
  9. Android(安卓)存储设备管理 -- MountService

随机推荐

  1. 新版 Android(安卓)Studio 编译输出中文
  2. Android——禁止设备休眠
  3. Android(安卓)仿秒拍,微信录制短视频
  4. 去掉android的屏幕上的title bar
  5. Android中使用log4j
  6. Android(安卓)API 中文 (52) ―― ZoomButt
  7. android 圆角和阴影效果
  8. Android(安卓)点击事件分发
  9. Android(安卓)USB/MTP相关实现
  10. android实现json数据的解析和把数据转换