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() {@Overridepublic 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);//占位的ActivityproxyIntent.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方法

@Overridepublic 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 {@Overridepublic 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() {@Overridepublic 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

@Overridepublic 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 {//获取自己的dexElementsPathClassLoader 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中的dexElementsFile 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

Activity启动流程:Hook实现启动未注册Activity相关推荐

  1. 【Android 插件化】Hook 插件化框架总结 ( 插件包管理 | Hook Activity 启动流程 | Hook 插件包资源加载 ) ★★★

    Android 插件化系列文章目录 [Android 插件化]插件化简介 ( 组件化与插件化 ) [Android 插件化]插件化原理 ( JVM 内存数据 | 类加载流程 ) [Android 插件 ...

  2. 【Android 插件化】Hook 插件化框架 ( Hook Activity 启动流程 | Hook 点分析 )

    Android 插件化系列文章目录 [Android 插件化]插件化简介 ( 组件化与插件化 ) [Android 插件化]插件化原理 ( JVM 内存数据 | 类加载流程 ) [Android 插件 ...

  3. androd hook acitivity 启动流程,替换启动的activity(Android Instrumentation)

    前言:如果程序想要知道有activity启动,如果想要拦截activity,然后跳转到指定的activity怎么办? 我们看下ActivityThread 里面: private Activity p ...

  4. activity启动流程_以AMS视角看Activity启动过程

    原文作者:Levi_wayne 原文地址:blog.csdn.net/u012551754/article/details/78822782 特别声明:本文转载自网络,版权归作者所有,如有侵权请联系删 ...

  5. kettle源码分析之1启动流程(IDEA启动carte调试)

    文章目录 启动 Spoon Carte Pan kitchen 启动 对于kettle来说,整个程序的入口是launcher.jar 调用反射,实现其他命令功能: public static void ...

  6. Android启动流程:上电到启动第一个APP的详细流程,

        1. 安卓启动大致如下图所示: 2. BootLoader (如果想了解跟详细关于bootloader请看:安卓bootloader) 从系统的角度上来讲,Android系统的启动过程可以分为 ...

  7. android zygote启动流程,Android zygote启动流程详解

    对zygote的理解 在Android系统中,zygote是一个native进程,是所有应用进程的父进程.而zygote则是Linux系统用户空间的第一个进程--init进程,通过fork的方式创建并 ...

  8. Android启动过程五个步骤,Android启动流程、app启动原理

    从头分析整理学习底层知识. Android 众多基于Linux内核的系统类似, 启动系统时, bootloader启动内核和init进程. init进程分裂出更多名为"daemons(守护进 ...

  9. linux uboot启动流程分析,uboot启动流程分析

    uboot版本为NXP维护的2016.03版本 下载地址为http://git.freescale.com/git/... 分析uboot的启动流程,需要编译一下uboot,然后打开链接脚本 u-bo ...

最新文章

  1. MySQL面试题 | 附答案解析(十六)
  2. Oracle存储过程创建及调用(转)
  3. About The FTP
  4. java并发编程实践(1)intro
  5. redis安装与基本配置
  6. Qt工作笔记- 解决cc1plus.exe: error: out of memory allocating
  7. 计算机组成原理 北理,北京理工大学计算机组成原理期末复习.pdf
  8. 公安部身份证阅读器模块SAM通讯协议
  9. 使用Timer的schedule()方法
  10. 【渝粤教育】国家开放大学2018年春季 0043-21T计算机文化 参考试题
  11. #QCon#北京2011大会“更有效地做测试”专题Slides资料
  12. oracle11g服务项及其启动顺序
  13. PeopleCert认证证书核验真伪(含ITIL、PRINCE2、DevOps、Scrum……等证书)
  14. 贝叶斯分析之利用线性回归模型理解并预测数据(三)
  15. 微信网页授权失败原因总结
  16. python 趣味编程课_Python趣味编程公益课开班,期待你的到来~
  17. Android版本手机怎么截屏,安卓手机怎么截屏的4种方法
  18. 2018 ICPC SouthEastern European 【Fishermen】
  19. COGS 2211. [BZOJ3653]谈笑风生
  20. HIFIVE音乐开放平台音乐api接口文档!

热门文章

  1. selenium 验证码识别_如何获取验证码?
  2. sql join on 多表连接_SQL 多表查询-交叉连接(笛卡尔积)
  3. 新网 云服务器,新网云服务器的优势包括什么?
  4. 控制div的大小自适应_可以漂移的电动轮椅,采用“自适应重心控制系统”,根本不怕翻车...
  5. python自己写包_Python将自己写的模块进行打包
  6. C++知识点50——虚函数与纯虚函数(上)
  7. rabbitmq实战:高效部署分布式消息队列_一文看懂消息队列中间件--AMQ及部署介绍...
  8. js高级程序设计笔记——DOM扩展
  9. LeetCode OJ:Pascal's TriangleII(帕斯卡三角II)
  10. linux下rpm包和命令使用简介