Android插件化原理(一)Activity插件化
title: " Android插件化原理(一)Activity插件化"
date: 2018-5-28 00:16
photos:
- https://s2.ax1x.com/2019/05/31/VlkKJA.jpg
tag: - Android应用层
- Android插件化原理
categories: - Android应用层
本文首发于微信公众号「后场村刘皇叔」
关联系列
Android深入四大组件系列
Android解析AMS系列
Android解析ClassLoader系列
前言
四大组件的插件化是插件化技术的核心知识点,而Activity插件化更是重中之重,Activity插件化主要有三种实现方式,分别是反射实现、接口实现和Hook技术实现。反射实现会对性能有所影响,主流的插件化框架没有采用此方式,关于接口实现可以阅读dynamic-load-apk的源码,这里不做介绍,目前Hook技术实现是主流,因此本篇文章主要介绍Hook技术实现。
Hook技术实现主要有两种解决方案 ,一种是通过Hook IActivityManager来实现,另一种是Hook Instrumentation实现。在讲到这两个解决方案前,我们需要从整体上了解Activity的启动流程。
1.Activity的启动过程回顾
Activity的启动过程主要分为两种,一种是根Activity的启动过程,一种是普通Activity的启动过程。关于根Activity的启动过程在在介绍过,这里来简单回顾下,如下图所示。
图 1
首先Launcher进程向AMS请求创建根Activity,AMS会判断根Activity所需的应用程序进程是否存在并启动,如果不存在就会请求Zygote进程创建应用程序进程。应用程序进程启动后,AMS会请求应用程序进程创建并启动根Activity。
普通Activity和根Activity的启动过程大同小异,但是没有这么复杂,因为不涉及应用程序进程的创建,跟Laucher也没关系,如下图所示。
图2
上图抽象的给出了普通Acticity的启动过程。在应用程序进程中的Activity向AMS请求创建普通Activity(步骤1),AMS会对
这个Activty的生命周期管和栈进行管理,校验Activity等等。如果Activity满足AMS的校验,AMS就会请求应用程序进程中的ActivityThread去创建并启动普通Activity(步骤2)。
2.Hook IActivityManager方案实现
AMS是在SystemServer进程中,我们无法直接进行修改,只能在应用程序进程中做文章。可以采用预先占坑的方式来解决没有在AndroidManifest.xml中显示声明的问题,具体做法就是在上图步骤1之前使用一个在AndroidManifest.xml中注册的Activity来进行占坑,用来通过AMS的校验。
接着在步骤2之后用插件Activity替换占坑的Activity,接下来根据这个解决方案我们来实践一下。
2.1 注册Activity进行占坑
为了更好的讲解启动插件Activity的原理,这里省略了插件Activity的加载逻辑,直接创建一个TargetActivity来代表已经加载进来的插件Activity,接着我们再创建一个SubActivity用来占坑。在AndroidManifest.xml中注册SubActivity,如下所示。
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"package="com.example.liuwangshu.pluginactivity">S<application...<activity android:name=".StubActivity"/></application>
</manifest>
TargetActivity用来代表已经加载进来的插件Activity,因此不需要在AndroidManifest.xml进行注册。如果我们直接在MainActivity中启动TargetActivity肯定会报错(android.content.ActivityNotFoundException异常)。
2.2 使用占坑Activity通过AMS验证
为了防止报错,需要将启动的TargetActivity替换为SubActivity,用SubActivity来通过AMS的验证。在第六章时讲过Android 8.0与7.0的AMS家族有一些差别,主要是Android 8.0去掉了AMS的代理ActivityManagerProxy,代替它的是IActivityManager,直接采用AIDL来进行进程间通信。
Android7.0的Activity的启动会调用ActivityManagerNative的getDefault方法,如下所示。
frameworks/base/core/java/android/app/ActivityManagerNative.java
static public IActivityManager getDefault() {return gDefault.get();}private static final Singleton<IActivityManager> gDefault = new Singleton<IActivityManager>() {protected IActivityManager create() {IBinder b = ServiceManager.getService("activity");if (false) {Log.v("ActivityManager", "default service binder = " + b);}IActivityManager am = asInterface(b);if (false) {Log.v("ActivityManager", "default service = " + am);}return am;}};
getDefault方法返回了IActivityManager类型的对象,IActivityManager借助了Singleton类来实现单例,而且gDefault又是静态的,因此IActivityManager是一个比较好的Hook点。
Android8.0的Activity的启动会调用ActivityManager的getService方法,如下所示。
frameworks/base/core/java/android/app/ActivityManager.java
public static IActivityManager getService() {return IActivityManagerSingleton.get();}private static final Singleton<IActivityManager> IActivityManagerSingleton =new Singleton<IActivityManager>() {@Overrideprotected IActivityManager create() {final IBinder b = ServiceManager.getService(Context.ACTIVITY_SERVICE);final IActivityManager am = IActivityManager.Stub.asInterface(b);return am;}};
同样的,getService方法返回了了IActivityManager类型的对象,并且IActivityManager借助了Singleton类来实现单例,确定了无论是Android7.0还是Android8.0,IActivityManager都是比较好的Hook点。Singleton类如下所示,后面会用到。
frameworks/base/core/java/android/util/Singleton.java
public abstract class Singleton<T> {private T mInstance;protected abstract T create();public final T get() {synchronized (this) {if (mInstance == null) {mInstance = create();}return mInstance;}}
}
由于Hook需要多次对字段进行反射操作,先写一个字段工具类FieldUtil:
FieldUtil.java
public class FieldUtil {public static Object getField(Class clazz, Object target, String name) throws Exception {Field field = clazz.getDeclaredField(name);field.setAccessible(true);return field.get(target);}public static Field getField(Class clazz, String name) throws Exception{Field field = clazz.getDeclaredField(name);field.setAccessible(true);return field;}public static void setField(Class clazz, Object target, String name, Object value) throws Exception {Field field = clazz.getDeclaredField(name);field.setAccessible(true);field.set(target, value);}
其中setField方法不会马上用到,接着定义替换IActivityManager的代理类IActivityManagerProxy,如下所示。
public class IActivityManagerProxy implements InvocationHandler {private Object mActivityManager;private static final String TAG = "IActivityManagerProxy";public IActivityManagerProxy(Object activityManager) {this.mActivityManager = activityManager;}@Overridepublic Object invoke(Object o, Method method, Object[] args) throws Throwable {if ("startActivity".equals(method.getName())) {//1Intent intent = null;int index = 0;for (int i = 0; i < args.length; i++) {if (args[i] instanceof Intent) {index = i;break;}}intent = (Intent) args[index];Intent subIntent = new Intent();//2String packageName = "com.example.liuwangshu.pluginactivity";subIntent.setClassName(packageName,packageName+".StubActivity");//3subIntent.putExtra(HookHelper.TARGET_INTENT, intent);//4args[index] = subIntent;//5}return method.invoke(mActivityManager, args);}
}
Hook点IActivityManager是一个接口,建议采用动态代理。在注释1处拦截startActivity方法,接着获取参数args中第一个Intent对象,它是原本要启动插件TargetActivity的Intent。注释2、3处新建一个subIntent用来启动的StubActivity,注释4处将这个TargetActivity的Intent保存到subIntent中,便于以后还原TargetActivity。注释5处用subIntent赋值给参数args,这样启动的目标就变为了StubActivity,用来通过AMS的校验。
最后用代理类IActivityManagerProxy来替换IActivityManager,如下所示。
HookHelper.java
public class HookHelper {public static final String TARGET_INTENT = "target_intent";public static void hookAMS() throws Exception {Object defaultSingleton=null;if (Build.VERSION.SDK_INT >= 26) {//1Class<?> activityManageClazz = Class.forName("android.app.ActivityManager");//获取activityManager中的IActivityManagerSingleton字段defaultSingleton= FieldUtil.getField(activityManageClazz ,null,"IActivityManagerSingleton");} else {Class<?> activityManagerNativeClazz = Class.forName("android.app.ActivityManagerNative");//获取ActivityManagerNative中的gDefault字段defaultSingleton= FieldUtil.getField(activityManagerNativeClazz,null,"gDefault");}Class<?> singletonClazz = Class.forName("android.util.Singleton");Field mInstanceField= FieldUtil.getField(singletonClazz ,"mInstance");//2//获取iActivityManagerObject iActivityManager = mInstanceField.get(defaultSingleton);//3Class<?> iActivityManagerClazz = Class.forName("android.app.IActivityManager");Object proxy = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),new Class<?>[] { iActivityManagerClazz }, new IActivityManagerProxy(iActivityManager));mInstanceField.set(defaultSingleton, proxy);}
}
首先在注释1处对系统版本进行区分,最终获取的是 Singleton<IActivityManager>
类型的IActivityManagerSingleton或者gDefault字段。注释2处获取Singleton类中的mInstance字段,从前面Singleton类的代码可以得知mInstance字段的类型为T,在注释3处得到IActivityManagerSingleton或者gDefault字段中的T的类型,T的类型为IActivityManager。最后动态创建代理类IActivityManagerProxy,用IActivityManagerProxy来替换IActivityManager。
自定义一个Application,在其中调用HookHelper 的hookAMS方法,如下所示。
MyApplication.java
public class MyApplication extends Application {@Overridepublic void attachBaseContext(Context base) {super.attachBaseContext(base);try {HookHelper.hookAMS();} catch (Exception e) {e.printStackTrace();}}
}
在MainActivity中启动TargetActivity,如下所示。
MainActivity.java
public class MainActivity extends Activity {private Button bt_hook;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);bt_hook = (Button) this.findViewById(R.id.bt_hook);bt_hook.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View view) {Intent intent = new Intent(MainActivity.this, TargetActivity.class);startActivity(intent);}});}
}
点击Button时,启动的并不是TargetActivity而是SubActivity,同时Log中打印了"hook成功",说明我们已经成功用SubActivity通过了AMS的校验。
2.3 还原插件Activity
前面用占坑Activity通过了AMS的校验,但是我们要启动的是插件TargetActivity,还需要用插件TargetActivity来替换占坑的SubActivity,这一替换的时机就在图2的步骤2之后。
ActivityThread启动Activity的过程,如图3所示。
图3
ActivityThread会通过H将代码的逻辑切换到主线程中,H类是ActivityThread的内部类并继承自Handler,如下所示。
frameworks/base/core/java/android/app/ActivityThread.java
private class H extends Handler {
public static final int LAUNCH_ACTIVITY = 100;
public static final int PAUSE_ACTIVITY = 101;
...public void handleMessage(Message msg) {if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));switch (msg.what) {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, "LAUNCH_ACTIVITY");Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);} break;...}
...
}
H中重写的handleMessage方法会对LAUNCH_ACTIVITY类型的消息进行处理,最终会调用Activity的onCreate方法。那么在哪进行替换呢?接着来看Handler的dispatchMessage方法:
frameworks/base/core/java/android/os/Handler.java
public void dispatchMessage(Message msg) {if (msg.callback != null) {handleCallback(msg);} else {if (mCallback != null) {if (mCallback.handleMessage(msg)) {return;}}handleMessage(msg);}}
Handler的dispatchMessage用于处理消息,看到如果Handler的Callback类型的mCallback不为null,就会执行mCallback的handleMessage方法。因此,mCallback可以作为Hook点,我们可以用自定义的Callback来替换mCallback,自定义的Callback如下所示。
HCallback.java
public class HCallback implements Handler.Callback{public static final int LAUNCH_ACTIVITY = 100;Handler mHandler;public HCallback(Handler handler) {mHandler = handler;}@Overridepublic boolean handleMessage(Message msg) {if (msg.what == LAUNCH_ACTIVITY) {Object r = msg.obj;try {//得到消息中的Intent(启动SubActivity的Intent)Intent intent = (Intent) FieldUtil.getField(r.getClass(), r, "intent");//得到此前保存起来的Intent(启动TargetActivity的Intent)Intent target = intent.getParcelableExtra(HookHelper.TARGET_INTENT);//将启动SubActivity的Intent替换为启动TargetActivity的Intentintent.setComponent(target.getComponent());} catch (Exception e) {e.printStackTrace();}}mHandler.handleMessage(msg);return true;}
}
HCallback实现了Handler.Callback,并重写了handleMessage方法,当收到消息的类型为LAUNCH_ACTIVITY时,将启动SubActivity的Intent替换为启动TargetActivity的Intent。接着我们在HookHelper中定义一个hookHandler方法如下所示。
HookHelper.java
public static void hookHandler() throws Exception {Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");Object currentActivityThread= FieldUtil.getField(activityThreadClass ,null,"sCurrentActivityThread");//1Field mHField = FieldUtil.getField(activityThread,"mH");//2Handler mH = (Handler) mHField.get(currentActivityThread);//3FieldUtil.setField(Handler.class,mH,"mCallback",new HCallback(mH));}
ActivityThread类中有一个静态变量sCurrentActivityThread,用于表示当前的ActivityThread对象,因此在注释1处获取ActivityThread中定义的sCurrentActivityThread对象。注释2处获取ActivityThread类的mH字段,接着在注释3处获取当前ActivityThread对象中的mH对象,最后用HCallback来替换mH中的mCallback。
在MyApplication的attachBaseContext方法中调用HookHelper的hookHandler方法,运行程序,当我们点击启动插件按钮,发现启动的是插件TargetActivity。
2.4 插件Activity的生命周期
插件TargetActivity确实启动了,但是它有生命周期吗?这里从源码角度来进行分析,Activity的finish方法可以触发Activity的生命周期变化,和Activity的启动过程类似,finish方法如下所示。
frameworks/base/core/java/android/app/Activity.java
public void finish() {finish(DONT_FINISH_TASK_WITH_ACTIVITY);}private void finish(int finishTask) {if (mParent == null) {int resultCode;Intent resultData;synchronized (this) {resultCode = mResultCode;resultData = mResultData;}if (false) Log.v(TAG, "Finishing self: token=" + mToken);try {if (resultData != null) {resultData.prepareToLeaveProcess(this);}if (ActivityManager.getService().finishActivity(mToken, resultCode, resultData, finishTask)) {//1mFinished = true;}} catch (RemoteException e) {// Empty}} else {mParent.finishFromChild(this);}}
finish方法的调用链和Activity的启动过程是类似的,注释1处会调用的AMS的finishActivity方法,接着是AMS通过ApplicationThread调用ActivityThread,ActivityThread向H发送DESTROY_ACTIVITY类型的消息,H接收到这个消息会执行handleDestroyActivity方法,handleDestroyActivity方法又调用了performDestroyActivity方法,如下所示。
frameworks/base/core/java/android/app/ActivityThread.java
private ActivityClientRecord performDestroyActivity(IBinder token, boolean finishing,int configChanges, boolean getNonConfigInstance) {ActivityClientRecord r = mActivities.get(token);//1...try {r.activity.mCalled = false;mInstrumentation.callActivityOnDestroy(r.activity);//2...} catch (SuperNotCalledException e) {...}}mActivities.remove(token);StrictMode.decrementExpectedActivityCount(activityClass);return r;
注释1处通过IBinder类型的token来获取ActivityClientRecord,ActivityClientRecord用于描述应用进程中的Activity。在注释2处调用Instrumentation的callActivityOnDestroy方法来调用Activity的OnDestroy方法,并传入了r.activity。前面的例子我们用SubActivity替换了TargetActivity通过了AMS的校验,这样AMS只知道SubActivity的存在,那么AMS是如何能控制TargetActivity生命周期的回调的?我们接着往下看,启动Activity时会调用ActivityThread的performLaunchActivity方法,如下所示。
frameworks/base/core/java/android/app/ActivityThread.java
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {...java.lang.ClassLoader cl = appContext.getClassLoader();activity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent);//1...activity.attach(appContext, this, getInstrumentation(), r.token,r.ident, app, r.intent, r.activityInfo, title, r.parent,r.embeddedID, r.lastNonConfigurationInstances, config,r.referrer, r.voiceInteractor, window, r.configCallback);...mActivities.put(r.token, r);//2...return activity;}
注释1处根据Activity的类名用ClassLoader加载Acitivty,接着调用Activity的attach方法,将r.token赋值给Activity的成员变量mToken。在注释2处将ActivityClientRecord根据r.token存在mActivities中(mActivities类型为ArrayMap<IBinder, ActivityClientRecord>
),再结合Activity的finish方法的注释1处,可以得出结论:AMS和ActivityThread之间的通信采用了token来对Activity进行标识,并且此后的Activity的生命周期处理也是根据token来对Activity进行标识的。
回到我们这个例子来,我们在Activity启动时用插件TargetActivity替换占坑SubActivity,这一过程在performLaunchActivity方法调用之前,因此注释2处的r.token指向的是TargetActivity,在performDestroyActivity的注释1处获取的就是代表TargetActivity的ActivityClientRecord,可见TargetActivity是具有生命周期的。
3.Hook Instrumentation方案实现
Hook Instrumentation实现要比Hook IActivityManager实现要简洁一些,示例代码会和Hook IActivityManager实现有重复,重复的部分这里不再赘述。
Hook Instrumentation实现同样也需要用到占坑Activity,与Hook IActivityManager实现不同的是,用占坑Activity替换插件Activity以及还原插件Activity的地方不同。Acitivty的startActivity方法调用时序图如图4所示。
图4
从图4可以发现,在Activity通过AMS校验前,会调用Activity的startActivityForResult方法:
frameworks/base/core/java/android/app/Activity.java
public void startActivityForResult(@RequiresPermission Intent intent, int requestCode,@Nullable Bundle options) {if (mParent == null) {options = transferSpringboardActivityOptions(options);Instrumentation.ActivityResult ar =mInstrumentation.execStartActivity(this, mMainThread.getApplicationThread(), mToken, this,intent, requestCode, options);...} else {...}}
startActivityForResult方法中调用了Instrumentation的execStartActivity方法来激活Activity的生命周期。
如图3所示,ActivityThread启动Activity的过程中会调用ActivityThread的performLaunchActivity方法,如下所示。
frameworks/base/core/java/android/app/ActivityThread.java
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) { ...//创建要启动Activity的上下文环境ContextImpl appContext = createBaseContextForActivity(r);Activity activity = null;try {java.lang.ClassLoader cl = appContext.getClassLoader();//用类加载器来创建Activity的实例activity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent);//1...} catch (Exception e) {...}...return activity;}
注释1处调用了mInstrumentation的newActivity方法,其内部会用类加载器来创建Activity的实例。看到这里我们可以得到方案,就是在Instrumentation的execStartActivity方法中用占坑SubActivity来通过AMS的验证,在Instrumentation的newActivity方法中还原TargetActivity,这两部操作都和Instrumentation有关,因此我们可以用自定义的Instrumentation来替换掉mInstrumentation。首先我们自定义一个Instrumentation,在execStartActivity方法中将启动的TargetActivity替换为SubActivity,如下所示。
InstrumentationProxy.java
public class InstrumentationProxy extends Instrumentation {private Instrumentation mInstrumentation;private PackageManager mPackageManager;public InstrumentationProxy(Instrumentation instrumentation, PackageManager packageManager) {mInstrumentation = instrumentation;mPackageManager = packageManager;}public ActivityResult execStartActivity(Context who, IBinder contextThread, IBinder token, Activity target,Intent intent, int requestCode, Bundle options) {List<ResolveInfo> infos = mPackageManager.queryIntentActivities(intent, PackageManager.MATCH_ALL);if (infos == null || infos.size() == 0) {intent.putExtra(HookHelper.TARGET_INTENsT_NAME, intent.getComponent().getClassName());//1intent.setClassName(who, "com.example.liuwangshu.pluginactivity.StubActivity");//2}try {Method execMethod = Instrumentation.class.getDeclaredMethod("execStartActivity",Context.class, IBinder.class, IBinder.class, Activity.class, Intent.class, int.class, Bundle.class);return (ActivityResult) execMethod.invoke(mInstrumentation, who, contextThread, token,target, intent, requestCode, options);} catch (Exception e) {e.printStackTrace();}return null;}
}
首先查找要启动的Activity是否已经在AndroidManifest.xml中注册了,如果没有就在注释1处将要启动的Activity(TargetActivity)的ClassName保存起来用于后面还原TargetActivity,接着在注释2处替换要启动的Activity为StubActivity,最后通过反射调用execStartActivity方法,这样就可以用StubActivity通过AMS的验证。在InstrumentationProxy 的newActivity方法还原TargetActivity,如下所示。
InstrumentationProxy.java
public Activity newActivity(ClassLoader cl, String className, Intent intent) throws InstantiationException,IllegalAccessException, ClassNotFoundException {String intentName = intent.getStringExtra(HookHelper.TARGET_INTENT_NAME);if (!TextUtils.isEmpty(intentName)) {return super.newActivity(cl, intentName, intent);}return super.newActivity(cl, className, intent);
}
newActivity方法中创建了此前保存的TargetActivity,完成了还原TargetActivity。
编写hookInstrumentation方法,用InstrumentationProxy替换mInstrumentation:
HookHelper.java
public static void hookInstrumentation(Context context) throws Exception {Class<?> contextImplClass = Class.forName("android.app.ContextImpl");Field mMainThreadField =FieldUtil.getField(contextImplClass,"mMainThread");//1Object activityThread = mMainThreadField.get(context);//2Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");Field mInstrumentationField=FieldUtil.getField(activityThreadClass,"mInstrumentation");//3FieldUtil.setField(activityThreadClass,activityThread,"mInstrumentation",new InstrumentationProxy((Instrumentation) mInstrumentationField.get(activityThread),context.getPackageManager()));}
注释1处获取ContextImpl类的ActivityThread类型的mMainThread字段,注释2出获取当前上下文环境的ActivityThread对象。
注释3出获取ActivityThread类中的mInstrumentation字段,最后用InstrumentationProxy来替换mInstrumentation。
在MyApplication的attachBaseContext方法中调用HookHelper的hookInstrumentation方法,运行程序,当我们点击启动插件按钮,发现启动的是插件TargetActivity。
4. 总结
这一节我们学到了启动插件Activity的原理,主要的方案就是先用一个在AndroidManifest.xml中注册的Activity来进行占坑,用来通过AMS的校验,接着在合适的时机用插件Activity替换占坑的Activity。为了更好的讲解启动插件Activity的原理,本小节省略了插件Activity的加载逻辑,直接创建一个TargetActivity来代表已经加载进来的插件Activity。同时这一节使我们更好的理解了Activity的启动过程。更多的Android插件化原理请查看《Android进阶解密》。
Android插件化原理(一)Activity插件化相关推荐
- Android 插件化原理解析——Activity生命周期管理
之前的 Android插件化原理解析 系列文章揭开了Hook机制的神秘面纱,现在我们手握倚天屠龙,那么如何通过这种技术完成插件化方案呢?具体来说,插件中的Activity,Service等组件如何在A ...
- Android 插件化原理 完胜360插件框架 技术实战
性能优化 Android 性能优化 (一)APK高效瘦身 http://blog.csdn.net/whb20081815/article/details/70140063 Android 性能优化 ...
- 【Android 插件化】Hook 插件化框架 ( hook 插件化原理 | 插件包管理 )
Android 插件化系列文章目录 [Android 插件化]插件化简介 ( 组件化与插件化 ) [Android 插件化]插件化原理 ( JVM 内存数据 | 类加载流程 ) [Android 插件 ...
- Android 插件化原理入门笔记
Android开发笔记 onGithub 笔记,参考7.2中所列参考文章所写,DEMO地址在PluginTestDemoApplication 1.综述 2015年是Android插件化技术突飞猛进的 ...
- Android组件化原理
Android组件化原理 什么是组件化? 为什么使用组件化? 一步步搭建组件化 组件化开发要注意的几点问题 1.新建模块 2.统一Gradle版本号 3.创建基础库 4.组件模式和集成模式转换 5.A ...
- Android 插件化原理学习 —— Hook 机制之动态代理
前言 为了实现 App 的快速迭代更新,基于 H5 Hybrid 的解决方案有很多,由于 webview 本身的性能问题,也随之出现了很多基于 JS 引擎实现的原生渲染的方案,例如 React Nat ...
- 【Android 插件化】VAHunt 引入 | VAHunt 原理 | VAHunt 识别插件化引擎
文章目录 一.VAHunt 引入 二.VAHunt 原理 三.识别插件化引擎 一.VAHunt 引入 从应用开发者角度出发 , 保护自己开发的应用不被恶意开发者使用插件化虚拟引擎二次打包 , 并植入恶 ...
- 【Android 插件化】Hook 插件化框架总结 ( 插件包管理 | Hook Activity 启动流程 | Hook 插件包资源加载 ) ★★★
Android 插件化系列文章目录 [Android 插件化]插件化简介 ( 组件化与插件化 ) [Android 插件化]插件化原理 ( JVM 内存数据 | 类加载流程 ) [Android 插件 ...
- 【Android 插件化】Hook 插件化框架 ( Hook Activity 启动流程 | 主线程创建 Activity 实例之前使用插件 Activity 类替换占位的组件 )
Android 插件化系列文章目录 [Android 插件化]插件化简介 ( 组件化与插件化 ) [Android 插件化]插件化原理 ( JVM 内存数据 | 类加载流程 ) [Android 插件 ...
最新文章
- C++拾取——Linux下实测布隆过滤器(Bloom filter)和unordered_multiset查询效率
- LINQ to XML 常用操作(转)
- 洛谷P1372 又是毕业季IP1414 又是毕业季II[最大公约数]
- 江苏省计算机一级在线考试,2020江苏省一级计算机基础及MS Office应用考试在线自测试题库(不限设备,登陆即可做题)...
- git命令下载项目,上传android项目到github步骤,以及取消项目关联Git,设置git忽略文件
- (王道408考研操作系统)第二章进程管理-第二节1:调度的基本概念及分类以及进程优先级
- c语言输入一串数字存入数组_在Excel中快速输入,竟是输入一串数字?
- 领域驱动设计DDD之读书笔记
- C# 调用C++DLL注意事项
- 【元胞自动机】基于matlab激进策略元胞自动机三车道(不开放辅路,软件园影响)交通流模型【含Matlab源码 1297期】
- pb 数据窗口 *号隐藏_王者荣耀背后的腾讯自研数据库TcaplusDB实践
- 【第六课】Smart 3d常见问题集锦
- uniapp签名使用canvas实现多张图片合成一张
- vue 使用 vue-awesome-swiper(swiper)解决方法
- 介绍几个免费的英文ASP.NET的CMS程序
- OpenAI注册(ChatGPT)
- python小白不要怕,小编来保护你~
- [Python图像处理] 使用 HSV 色彩空间检测病毒对象
- 如何做出实用而强大的数据地图?
- 目前已知摄像头的三维坐标和三维朝向,已知摄像头画面宽高,某一物体在该画面中的位置坐标,以及该物体中心距离摄像头的距离,求该物体在现实世界中的坐标,用c++实现,使用小孔成像原理,直接上代码...