我们在前面一系列文章中已经介绍完了插件化原理以及三个根本问题的解决方案,本文主要就是作为前面几篇文章的一个总结,通过项目实践将前面的知识点串起来使完成一个入门级简单的插件化工程以及在实际插件化开发中遇到的一些总结。
实践
我们先通过Android Studio创建一个工程,工程中包括了两个Application模块,分别是宿主Host和插件PlugIn。工程结构如下左图,我们的目标就是在宿主中通过前面文章介绍的知识点,将插件中的代码和资源都合并到宿主去,并且要在宿主中通过动态替换和静态代理两种方式来启动插件中的两个Activity。最终app启动后如下右图。
第一步创建宿主的Application:
public class HostApplication extends Application {
private static Context sContext;
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
sContext = base;
// 这是测试代码,只为模拟下载插件过程
Utile.simulationDownload(base);
// 加载插件
PluginManager.load(base);
// Hook Activity
ActivityHookHelper.hookActivity();
}
public static Context getContext() {
return sContext;
}
}
在Application的启动后,第一时间是下载插件和加载插件。
第二步是下载插件,这里为了方便演示,就采用了模拟下载逻辑,先将已经编好的插件PlugIn-debug.apk文件放置于宿主中的assets中,然后通过代码将其拷贝到files目录下,代码如下:
public class Utile {
/**
* 模拟下载,实际上是将Assets中的插件apk文件复制到/files 目录下
*
* @param context
*/
public static void simulationDownload(Context context) {
String sourceName = PluginManager.PLUGIN_NAME;
AssetManager am = context.getAssets();
InputStream is = null;
FileOutputStream fos = null;
try {
is = am.open(sourceName);
File extractFile = context.getFileStreamPath(sourceName);
fos = new FileOutputStream(extractFile);
byte[] buffer = new byte[1024];
int count = 0;
while ((count = is.read(buffer)) > 0) {
fos.write(buffer, 0, count);
}
fos.flush();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (is != null) {
is.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
if (fos != null) {
fos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
第三步是加载插件,这一步非常重要,插件的dex代码合并和资源的合并都在这里完成,请看代码:
public class PluginManager {
public final static String PLUGIN_NAME = "PlugIn-debug.apk";
private static Resources sResources;
/**
* 插件加载入口
* @param applicationBaseContext
*/
public static void load(Context applicationBaseContext) {
try {
mergePluginDex(applicationBaseContext, PLUGIN_NAME);
Resources newResources = mergePluginResources(applicationBaseContext, PLUGIN_NAME);
initPlugIn(applicationBaseContext, newResources);
sResources = newResources;
} catch (Exception e) {
e.printStackTrace();
}
}
public static Resources getResources() {
return sResources;
}
/**
* 合并插件中的代码
* @param context
* @param apkName
* @throws IllegalAccessException
* @throws NoSuchMethodException
* @throws IOException
* @throws InvocationTargetException
* @throws InstantiationException
* @throws NoSuchFieldException
*/
private static void mergePluginDex(Context context, String apkName)
throws IllegalAccessException, NoSuchMethodException, IOException, InvocationTargetException, InstantiationException, NoSuchFieldException {
ClassLoader pathClassLoaderClass = context.getClassLoader();
// 获取 PathClassLoader(BaseDexClassLoader) 的 DexPathList 对象变量 pathList
Class baseDexClassLoaderClass = BaseDexClassLoader.class;
Field pathListField = baseDexClassLoaderClass.getDeclaredField("pathList");
pathListField.setAccessible(true);
Object pathListObj = pathListField.get(pathClassLoaderClass);
// 获取 DexPathList 的 Element[] 对象变量 dexElements
Class dexPathListClass = pathListObj.getClass();
Field dexElementsField = dexPathListClass.getDeclaredField("dexElements");
dexElementsField.setAccessible(true);
Object[] dexElementListObj = (Object[]) dexElementsField.get(pathListObj);
// 获得 Element 类型
Class<?> elementClass = dexElementListObj.getClass().getComponentType();
// 创建一个新的Element[], 将用于替换原始的数组
Object[] newElementListObj = (Object[]) Array.newInstance(elementClass, dexElementListObj.length + 1);
Object pluginElementObj = null;
File apkFile = context.getFileStreamPath(apkName);
File optDexFile = context.getFileStreamPath(apkName.replace(".apk", ".dex"));
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
// 构造插件的 Element,构造函数参数:(File file, boolean isDirectory, File zip, DexFile dexFile)
Class[] paramClass = {File.class, boolean.class, File.class, DexFile.class};
Object[] paramValue = {apkFile, false, apkFile, DexFile.loadDex(apkFile.getCanonicalPath(), optDexFile.getAbsolutePath(), 0)};
Constructor elementCtor = elementClass.getDeclaredConstructor(paramClass);
elementCtor.setAccessible(true);
pluginElementObj = elementCtor.newInstance(paramValue);
} else {
// 构造插件的 Element,构造函数参数:(DexFile dexFile, File file)
Class[] paramClass = {DexFile.class, File.class};
Object[] paramValue = {DexFile.loadDex(apkFile.getCanonicalPath(), optDexFile.getAbsolutePath(), 0), apkFile};
Constructor elementCtor = elementClass.getDeclaredConstructor(paramClass);
elementCtor.setAccessible(true);
pluginElementObj = elementCtor.newInstance(paramValue);
}
Object[] pluginElementListObj = new Object[]{pluginElementObj};
// 把原来 PathClassLoader 中的 elements 复制进去新的Element[]中
System.arraycopy(dexElementListObj, 0, newElementListObj, 0, dexElementListObj.length);
// 把插件的 element 复制进去新的 Element[] 中
System.arraycopy(pluginElementListObj, 0, newElementListObj, dexElementListObj.length, pluginElementListObj.length);
// 替换原来 PathClassLoader 中的 dexElements 值
Field field = pathListObj.getClass().getDeclaredField("dexElements");
field.setAccessible(true);
field.set(pathListObj, newElementListObj);
}
/**
* 合并插件中的资源
* @param applicationBaseContext
* @param apkName
* @return 合并后新的资源对象
* @throws IllegalAccessException
* @throws InstantiationException
* @throws NoSuchMethodException
* @throws InvocationTargetException
* @throws NoSuchFieldException
*/
private static Resources mergePluginResources(Context applicationBaseContext, String apkName)
throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
// 创建一个新的 AssetManager 对象
AssetManager newAssetManagerObj = AssetManager.class.newInstance();
Method addAssetPath = AssetManager.class.getMethod("addAssetPath", String.class);
// 塞入原来宿主的资源
addAssetPath.invoke(newAssetManagerObj, applicationBaseContext.getPackageResourcePath());
// 塞入插件的资源
File optDexFile = applicationBaseContext.getFileStreamPath(apkName);
addAssetPath.invoke(newAssetManagerObj, optDexFile.getAbsolutePath());
// ----------------------------------------------
// 创建一个新的 Resources 对象
Resources newResourcesObj = new Resources(newAssetManagerObj,
applicationBaseContext.getResources().getDisplayMetrics(),
applicationBaseContext.getResources().getConfiguration());
// ----------------------------------------------
// 获取 ContextImpl 中的 Resources 类型的 mResources 变量,并替换它的值为新的 Resources 对象
Field resourcesField = applicationBaseContext.getClass().getDeclaredField("mResources");
resourcesField.setAccessible(true);
resourcesField.set(applicationBaseContext, newResourcesObj);
// ----------------------------------------------
// 获取 ContextImpl 中的 LoadedApk 类型的 mPackageInfo 变量
Field packageInfoField = applicationBaseContext.getClass().getDeclaredField("mPackageInfo");
packageInfoField.setAccessible(true);
Object packageInfoObj = packageInfoField.get(applicationBaseContext);
// 获取 mPackageInfo 变量对象中类的 Resources 类型的 mResources 变量,,并替换它的值为新的 Resources 对象
// 注意:这是最主要的需要替换的,如果不需要支持插件运行时更新,只留这一个就可以了
Field resourcesField2 = packageInfoObj.getClass().getDeclaredField("mResources");
resourcesField2.setAccessible(true);
resourcesField2.set(packageInfoObj, newResourcesObj);
// ----------------------------------------------
// 获取 ContextImpl 中的 Resources.Theme 类型的 mTheme 变量,并至空它
// 注意:清理mTheme对象,否则通过inflate方式加载资源会报错, 如果是activity动态加载插件,则需要把activity的mTheme对象也设置为null
Field themeField = applicationBaseContext.getClass().getDeclaredField("mTheme");
themeField.setAccessible(true);
themeField.set(applicationBaseContext, null);
return newResourcesObj;
}
/**
* 初始化插件
* @param applicationBaseContext
* @param resources
* @throws ClassNotFoundException
*/
private static void initPlugIn(Context applicationBaseContext, Resources resources)
throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException {
Class plugInApplicationClass = Class.forName("com.zyx.plugin.PlugInApplication");
Class[] paramClass = {Context.class, Resources.class};
Method initMethod = plugInApplicationClass.getMethod("init", paramClass);
initMethod.setAccessible(true);
Object[] paramValue = {applicationBaseContext, resources};
initMethod.invoke(null, paramValue);
}
}
代码中,mergePluginDex方法就是处理合并dex,而mergePluginResources方法就是处理合并资源并返回了合并后的资源对象。这两处详细介绍可见《Android插件化原理和实践 (三) 之 加载插件中的组件代码》和《Android插件化原理和实践 (四) 之 合并插件中的资源》。待这两处逻辑处理好后,就是处理初始化插件,使用了initPlugIn方法,将宿主的Application的Context和合并后的资源对象通过反射传递到插件中。因为插件中是不能存在自己的Application的和如果使用动态替换方式启动Activity的话,Activity的getResources方法必须是要返回合并后的资源对象。
第四步执行HookActivity,让动态替换Activity方案生效,详细可见《Android插件化原理和实践 (六) 之 四大组件解决方案》。
public class ActivityHookHelper {
private static final String EXTRA_TARGET_INTENT = "extra_target_intent";
public static void hookActivity() {
try {
hookAMN();
hookActivityThread();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 第一处Hook地方,Hook ActivityManagerNative中通过getDefault方法获得的对象,使其在调用startActivity时替换成替身Activity,以达到欺骗ActivityManagerSerfvice的目的
* @throws ClassNotFoundException
* @throws IllegalAccessException
* @throws NoSuchFieldException
*/
private static void hookAMN() throws ClassNotFoundException, IllegalAccessException, NoSuchFieldException {
Object gDefaultObj = null;
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
// 获取 ActivityManagerNative 的 gDefault
Class activityManagerNativeClass = Class.forName("android.app.ActivityManagerNative");
Field gDefaultField = activityManagerNativeClass.getDeclaredField("gDefault");
gDefaultField.setAccessible(true);
gDefaultObj = gDefaultField.get(null);
} else {
// 获取 ActivityManager 的 IActivityManagerSingleton
Class activityManagerNativeClass = Class.forName("android.app.ActivityManager");
Field gDefaultField = activityManagerNativeClass.getDeclaredField("IActivityManagerSingleton");
gDefaultField.setAccessible(true);
gDefaultObj = gDefaultField.get(null);
}
// 获取 gDefault 对应在 android.util.Singleton<T> 的单例对象 mInstance
Class singletonClass = Class.forName("android.util.Singleton");
Field mInstanceField = singletonClass.getDeclaredField("mInstance");
mInstanceField.setAccessible(true);
final Object mInstanceObj = mInstanceField.get(gDefaultObj);
// 创建 gDefault 的代理
Class<?> classInterface = Class.forName("android.app.IActivityManager");
Object proxy = Proxy.newProxyInstance(
Thread.currentThread().getContextClassLoader(),
new Class<?>[] { classInterface },
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 只 Hook startActivity 一个方法
if (!"startActivity".equals(method.getName())) {
return method.invoke(mInstanceObj, args);
}
// 找到参数里面的Intent 对象
int index = 0;
for (int i = 0; i < args.length; i++) {
if (args[i] instanceof Intent) {
index = i;
break;
}
}
Intent targetIntent = (Intent) args[index];
// 判断宿主没有声明的Activity才走替换流程
boolean isExistActivity = isExistActivity(targetIntent.getComponent().getClassName());
if (isExistActivity) {
return method.invoke(mInstanceObj, args);
}
String stubPackage = targetIntent.getComponent().getPackageName();
Intent newIntent = new Intent();
newIntent.setComponent(new ComponentName(stubPackage, SubActivity.class.getName()));
// 把原来要启动的目标Activity先存起来
newIntent.putExtra(EXTRA_TARGET_INTENT, targetIntent);
// 替换掉Intent, 即欺骗AMS要启动的是替身Activity
args[index] = newIntent;
return method.invoke(mInstanceObj, args);
}
}
);
//把 gDefault 的 mInstance 字段,替换成 proxy
mInstanceField.set(gDefaultObj, proxy);
}
/**
* 判断Activity是否有在宿主中声明
* @param activity
* @return
*/
private static boolean isExistActivity(String activity) {
try {
PackageManager packageManager = HostApplication.getContext().getPackageManager();
PackageInfo packageInfo = packageManager.getPackageInfo(HostApplication.getContext().getPackageName(), PackageManager.GET_ACTIVITIES);
ActivityInfo[] activities = packageInfo.activities;
for(int i = 0; i < activities.length; i++) {
if (activity.equalsIgnoreCase(activities[i].name)) {
return true;
}
}
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
/**
* 第二处Hook地方,Hook ActivityThread 中的 Handle mCallback 对象,使接收 LAUNCH_ACTIVITY 消息后将替身 Activity 换回目标 Activity
* @throws ClassNotFoundException
* @throws IllegalAccessException
* @throws NoSuchFieldException
*/
private static void hookActivityThread() throws ClassNotFoundException, IllegalAccessException, NoSuchFieldException {
// 获取到当前的 ActivityThread 对象
Class activityThreadClass = Class.forName("android.app.ActivityThread");
Field sCurrentActivityThreadField = activityThreadClass.getDeclaredField("sCurrentActivityThread");
sCurrentActivityThreadField.setAccessible(true);
Object currentActivityThreadObj = sCurrentActivityThreadField.get(null);
// 获取 ActivityThread 对象中的 mH 变量
Field mHField = activityThreadClass.getDeclaredField("mH");
mHField.setAccessible(true);
final Handler mHObj = (Handler) mHField.get(currentActivityThreadObj);
// Hook Handler 的 mCallback 字段
Class handlerClass = Handler.class;
Field mCallbackField = handlerClass.getDeclaredField("mCallback");
mCallbackField.setAccessible(true);
mCallbackField.set(mHObj, new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
int LAUNCH_ACTIVITY = 0;
try {
Class hClass = Class.forName("android.app.ActivityThread$H");
Field launchActivityField = hClass.getDeclaredField("LAUNCH_ACTIVITY");
launchActivityField.setAccessible(true);
LAUNCH_ACTIVITY = (int) launchActivityField.get(null);
if (msg.what == LAUNCH_ACTIVITY) {
// 把替身 Activity 的 Intent 恢复成目标 Activity 的 Intent
Class c = msg.obj.getClass();
Field intentField = c.getDeclaredField("intent");
intentField.setAccessible(true);
Intent intent = (Intent) intentField.get(msg.obj);
Intent targetIntent = intent.getParcelableExtra(EXTRA_TARGET_INTENT);
if (targetIntent != null) {
intent.setComponent(targetIntent.getComponent());
}
}
} catch (Exception e) {
e.printStackTrace();
}
// 走完原来流程
mHObj.handleMessage(msg);
return true;
}
});
}
}
第五步创建动态替换启动Activity所需要的替身Activity,这个Activity什么也不做,只需在AndroidManifest.xml中有声明就好了:
public class SubActivity extends Activity {
}
第六步创建静态代理启动Activity所需的代理Activity:
public class ProxyActivity extends Activity {
public final static String TRAGET_ACTIVITY_CLASS_NAME = "tragetActivityClassName";
private Object mRemoteActivity;
private String mTragetActivityClassName;
private HashMap<String, Method> mActivityLifecircleMethods = new HashMap<String, Method>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mTragetActivityClassName = getIntent().getStringExtra(TRAGET_ACTIVITY_CLASS_NAME);
launchTargetActivity();
invokeOnCreate();
}
@Override
public Resources getResources() {
if (PluginManager.getResources() != null) {
return PluginManager.getResources();
} else {
return super.getResources();
}
}
/**
* 反射目标 Activity
*/
void launchTargetActivity() {
try {
// 获取插件的 Activity 对象
Class<?> localClass = Class.forName(mTragetActivityClassName);
Constructor<?> localConstructor = localClass.getConstructor(new Class[] {});
mRemoteActivity = localConstructor.newInstance(new Object[] {});
// 执行插件 Activity 的 setProxy 方法,传递this过去,使建立双向引用
Method setProxy = localClass.getMethod("setProxy", new Class[] { Activity.class });
setProxy.setAccessible(true);
setProxy.invoke(mRemoteActivity, new Object[] { this });
// 反射插件 Activity 的生命周期函数
launchTargetActivityLifecircleMethods(localClass);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 反射插件 Activity 的生命周期函数
* @param localClass
*/
protected void launchTargetActivityLifecircleMethods(Class<?> localClass) {
Method onCreate = null;
try {
onCreate = localClass.getDeclaredMethod("onCreate", new Class[] { Bundle.class });
onCreate.setAccessible(true);
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
mActivityLifecircleMethods.put("onCreate", onCreate);
String[] methodNames = new String[]{"onRestart", "onStart", "onResume", "onPause", "onStop", "onDestory"};
for (String methodName : methodNames) {
Method method = null;
try {
method = localClass.getDeclaredMethod(methodName, new Class[]{});
method.setAccessible(true);
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
mActivityLifecircleMethods.put(methodName, method);
}
Method onActivityResult = null;
try {
onActivityResult = localClass.getDeclaredMethod("onActivityResult",
new Class[] { int.class, int.class, Intent.class });
onActivityResult.setAccessible(true);
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
mActivityLifecircleMethods.put("onActivityResult", onActivityResult);
}
/**
* 执行插件 Activity 的 onCreate 方法
*/
private void invokeOnCreate() {
Method onCreate = mActivityLifecircleMethods.get("onCreate");
if (onCreate != null) {
try {
Bundle bundle = new Bundle();
onCreate.invoke(mRemoteActivity, new Object[]{bundle});
} catch (Exception e) {
e.printStackTrace();
}
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
invokeOnActivityResult(requestCode, resultCode, data);
}
private void invokeOnActivityResult(int requestCode, int resultCode, Intent data) {
Method onActivityResult = mActivityLifecircleMethods.get("onActivityResult");
if (onActivityResult != null) {
try {
onActivityResult.invoke(mRemoteActivity, new Object[] { requestCode, resultCode, data });
} catch (Exception e) {
e.printStackTrace();
}
}
}
@Override
protected void onStart() {
super.onStart();
invokeOnStart();
}
private void invokeOnStart() {
Method onStart = mActivityLifecircleMethods.get("onStart");
if (onStart != null) {
try {
onStart.invoke(mRemoteActivity, new Object[] {});
} catch (Exception e) {
e.printStackTrace();
}
}
}
@Override
protected void onRestart() {
super.onRestart();
invokeOnRestart();
}
private void invokeOnRestart() {
Method onRestart = mActivityLifecircleMethods.get("onRestart");
if (onRestart != null) {
try {
onRestart.invoke(mRemoteActivity, new Object[] { });
} catch (Exception e) {
e.printStackTrace();
}
}
}
@Override
protected void onResume() {
super.onResume();
invokeOnResume();
}
private void invokeOnResume() {
Method onResume = mActivityLifecircleMethods.get("onResume");
if (onResume != null) {
try {
onResume.invoke(mRemoteActivity, new Object[] { });
} catch (Exception e) {
e.printStackTrace();
}
}
}
@Override
protected void onPause() {
super.onPause();
invokeOnPause();
}
private void invokeOnPause() {
Method onPause = mActivityLifecircleMethods.get("onPause");
if (onPause != null) {
try {
onPause.invoke(mRemoteActivity, new Object[] { });
} catch (Exception e) {
e.printStackTrace();
}
}
}
@Override
protected void onStop() {
super.onStop();
invokeOnStop();
}
private void invokeOnStop() {
Method onStop = mActivityLifecircleMethods.get("onStop");
if (onStop != null) {
try {
onStop.invoke(mRemoteActivity, new Object[] { });
} catch (Exception e) {
e.printStackTrace();
}
}
}
@Override
protected void onDestroy() {
super.onDestroy();
invokeOnDestroy();
}
private void invokeOnDestroy() {
Method onDestroy = mActivityLifecircleMethods.get("onDestroy");
if (onDestroy != null) {
try {
onDestroy.invoke(mRemoteActivity, new Object[] { });
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
ProxyActivity主要是对插件中Activity的引用和其生命周期的代理管理,详细介绍可见《Android插件化原理和实践 (六) 之 四大组件解决方案》。
第七步创建插件中的Application,插件中的Application并非真实的Application,只是一个初始化的入口类罢了,因为插件中不能存在自己的Application,请看代码:
public class PlugInApplication {
private static Resources sResources;
private static Context sApplicationBaseContext;
public static void init(Context applicationBaseContext, Resources resources) {
sApplicationBaseContext = applicationBaseContext;
sResources = resources;
}
public static Resources getResources() {
return sResources;
}
public static Context getApplicationBasecontext() {
return sApplicationBaseContext;
}
}
PlugInApplication类也就是第三步中初始化插件中反射的类,它接收Application的Content对象和合并后的资源对象。
第八步创建一个用于演示动态替换启动Activity的PlugInActivity1,并使其继承父类BaseActivity1:
public class PlugInActivity1 extends BaseActivity1 {
private static final String TAG = "PlugInActivity1";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_plug_in1);
Log.d(TAG, "onCreate()");
}
@Override
protected void onStart() {
super.onStart();
Log.d(TAG, "onStart()");
}
@Override
protected void onPause() {
super.onPause();
Log.d(TAG, "onPause()");
}
@Override
protected void onResume() {
super.onResume();
Log.d(TAG, "onResume()");
}
@Override
protected void onStop() {
super.onStop();
Log.d(TAG, "onStop()");
}
@Override
protected void onDestroy() {
super.onDestroy();
Log.d(TAG, "onDestroy()");
}
}
继承BaseActivity1的目的是为了统一处理getResources。因为要启动插件中的Activity,在Activity中所需要的资源必须要通过重写getResources方法返回合并后的资源对象才能生效。
public class BaseActivity1 extends Activity {
@Override
public Resources getResources() {
if (PlugInApplication.getResources() != null) {
return PlugInApplication.getResources();
} else {
return super.getResources();
}
}
}
第九步创建一个用于演示静态代理启动Activity的PlugInActivity2,并使其继承父类BaseActivity2:
public class PlugInActivity2 extends BaseActivity2 {
private static final String TAG = "PlugInActivity2";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_plug_in2);
}
@Override
protected void onStart() {
super.onStart();
Log.d(TAG, "onStart()");
}
@Override
protected void onPause() {
super.onPause();
Log.d(TAG, "onPause()");
}
@Override
protected void onResume() {
super.onResume();
Log.d(TAG, "onResume()");
}
@Override
protected void onStop() {
super.onStop();
Log.d(TAG, "onStop()");
}
@Override
protected void onDestroy() {
super.onDestroy();
Log.d(TAG, "onDestroy()");
}
}
继承BaseActivity2的目的是为了统一封装that变量和使Activity保持原生代码风格。that变量就是保存了宿主ProxyActivity中反射将自己传递过来的引用对象。
public class BaseActivity2 extends Activity {
protected Activity that;
public void setProxy(Activity proxyActivity) {
that = proxyActivity;
}
@SuppressWarnings("all")
@Override
protected void onCreate(Bundle savedInstanceState) {
}
@Override
public void setContentView(int layoutResID) {
that.setContentView(layoutResID);
}
}
第十步就是修改aapt命令,为了便于团队开发,所以我们打算将修改后的aapt文件放置于工程根目录,这样就不需要再去替Android SDK中的aapt了。关于如何修改aapt请见《Android插件化原理和实践 (五) 之 解决合并资源后资源Id冲突的问题》。然后我们来修改插件中的Gradle,让其可自定义插件包的资源id:
import com.android.sdklib.BuildToolInfo
import java.lang.reflect.Method
apply plugin: 'com.android.application'
Task modifyAaptPathTask = task('modifyAaptPath') << {
android.applicationVariants.all { variant ->
BuildToolInfo buildToolInfo = variant.androidBuilder.getTargetInfo().getBuildTools()
Method addMethod = BuildToolInfo.class.getDeclaredMethod("add", BuildToolInfo.PathId.class, File.class)
addMethod.setAccessible(true)
addMethod.invoke(buildToolInfo, BuildToolInfo.PathId.AAPT, new File(rootDir, "aapt_mac"))
println "[LOG] new aapt path = " + buildToolInfo.getPath(BuildToolInfo.PathId.AAPT)
}
}
android {
……
defaultConfig {
……
}
buildTypes {
……
}
preBuild.doFirst {
modifyAaptPathTask.execute()
}
aaptOptions {
aaptOptions.additionalParameters '--PLUG-resoure-id', '0x71'
}
}
dependencies {
……
}
因为Gradle是兼容Java的,所以上面代码中,在preBuild任务执行完后,再执行了modifyAaptPathTask任务,该任务通过Java反射将aapt路径指向工程根目录。这里使用的是aapt_mac,如果你是Windows开发环境,请将其替换成aapt_win.exe。点击下载aapt_win.exe
如果你工程中使用的编译sdk版本是25或以上的,应该是默认使用了aapt2,aapt2是aapt的优化版本,如果要关闭使用aapt2的话,可以在a工程中的gradle.properties中加上一行配置代码:
android.enableAapt2=false
第十一步编译插件PlugIn工程,将生成的apk文件拷贝到宿主Host工程中assets资源目录中
第十二步修改宿主中MainActivity代码,让其执行调用插件中两个Activity:
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 动态替换方式
Button btn1 = findViewById(R.id.btn_1);
btn1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 正常去启动插件中的Activity
Intent intent = new Intent();
String activityName = "com.zyx.plugin.PlugInActivity1";
intent.setClassName(MainActivity.this, activityName);
startActivity(intent);
}
});
// 静态代理方式
Button btn2 = findViewById(R.id.btn_2);
btn2.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 要启动插件中的Activity,就要先启动宿主中的ProxyActivity
Intent intent = new Intent();
intent.setClass(MainActivity.this, ProxyActivity.class);
intent.putExtra(ProxyActivity.TRAGET_ACTIVITY_CLASS_NAME, "com.zyx.plugin.PlugInActivity2");
startActivity(intent);
}
});
}
}
通过上述十二个步骤就完成了整个入门级插件化工程演示了。其中有些点介绍得有点快,不过这些都是前面文章所详细阐述过的,如果有不明白的地方,可见前面几篇文章的详细介绍。值得注意的是,因为当中使用了一些反射系统底层的代码,所以可能会存在在某些手机厂商定制系统或在不同的Android系统版本中存在失效情况。