Android中插件化的简单实现:启动未注册的Activity
个人博客
http://www.milovetingting.cn
Android中插件化的简单实现:启动未注册的Activity
前言
本文介绍在Android中启动未在AndroidManifest中注册的Activity的一个解决方案。主要需要掌握以下知识点:
-
反射
-
类加载
-
Activity的启动过程
-
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
更多相关文章
- ANDROID STUDIO&&Eclipse Android项目缺少R文件解决方法(完解)
- 浅谈Android(安卓)java层ServiceManager
- android 中activity的启动模式是singleTask时清除activity的栈顶
- android中WebView的Java与JavaScript交互
- 《第一行代码》 6.3 SharedPreferences存储
- Android(java)同步方法synchronized
- Android——RecyclerView入门学习之LayoutManager
- Android(安卓)通用获取Ip的方法(判断手机是否联网的方法)!!!
- Android(安卓)存储设备管理 -- MountService