一、基础
1.资源管理
获取apk资源原理
Android工程在打包apk时会将资源名与资源id在R.java中一一映射起来:
public final class R {
...
public static final class string { //字符串资源
public static final int app_name=0x7f070000; //资源名:app_name,资源id:0x7f070000
}
...
}
获取资源方法:
Resources resources = getResources(); //获取Resources对象
int resourceId = R.string.app_name; //通过资源名获取资源id
String str = resources.getString(resourceId); //通过Resources对象以及资源id获取资源
源码分析:
//Resources.java
/**
* Create a new Resources object on top of an existing set of assets in an
* AssetManager.
*
* @deprecated Resources should not be constructed by apps.
* See {@link android.content.Context#createConfigurationContext(Configuration)}.
*
* @param assets Previously created AssetManager.
* @param metrics Current display metrics to consider when
* selecting/computing resource values.
* @param config Desired device configuration to consider when
* selecting/computing resource values (optional).
*/
@Deprecated
public Resources(AssetManager assets, DisplayMetrics metrics, Configuration config) {
this(null);
mResourcesImpl = new ResourcesImpl(assets, metrics, config, new DisplayAdjustments());
}
//AssetManager.java
/**
* @deprecated Use {@link #setApkAssets(ApkAssets[], boolean)}
* @hide
*/
@Deprecated
public int addAssetPath(String path) {
return addAssetPathInternal(path, false /*overlay*/, false /*appAsLib*/);
}
将apk的path传入,包装一个AssetManager,然后用AssetManager生成Resources,通过Resources可以获取apk的资源。
获取插件apk资源
例子:(三星G9500,P OS)
//步骤1.创建一个插件apk工程module:plugin,其中res/values/strings.xml内容如下:
<resources>
<string name="app_name">plugin</string>
</resources>
//步骤2.生成插件apk:plugin-debug.apk,并放到手机根目录
//步骤3.创建一个宿主apk工程module:app,其中MainActivity代码如下:
(需要在manifest中添加权限:<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>)
public class MainActivity extends Activity {
private static final String TAG = "HostClass";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
String apkPath = Environment.getExternalStorageDirectory() + "/plugin-debug.apk";
Log.d(TAG, "zwm, apkPath: " + apkPath);
File file = new File(apkPath);
if(!file.exists()) {
Log.d(TAG, "zwm, file not exist");
return;
}
Resources pluginResources = getPluginResources(apkPath);
Log.d(TAG, "zwm, pluginResources: " + pluginResources);
int pluginResId = getPluginResId(apkPath, "com.tomorrow.plugin", "app_name");
Log.d(TAG, "zwm, pluginResId: " + pluginResId);
String pluginStr = pluginResources.getString(pluginResId);
Log.d(TAG, "zwm, pluginStr: " + pluginStr);
}
public Resources getPluginResources(String pluginPath) {
try {
AssetManager assetManager = AssetManager.class.newInstance();
//通过反射调用方法addAssetPath(String path)
Method addAssetPath = assetManager.getClass().getMethod("addAssetPath", String.class);
//将插件apk文件路径添加进AssetManager
addAssetPath.invoke(assetManager, pluginPath);
//获取宿主apk的Resources对象
Resources superRes = getResources();
//创建插件apk的Resources对象
Resources mResources = new Resources(assetManager, superRes.getDisplayMetrics(), superRes.getConfiguration());
return mResources;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public int getPluginResId(String pluginPath, String apkPackageName, String resName) {
try {
//创建类加载器对象,加载指定路径apk文件
PathClassLoader classLoader = new PathClassLoader(pluginPath, null, getClassLoader());
//通过反射获取资源id
Class<?> clazz = classLoader.loadClass(apkPackageName + ".R$string");
Field field = clazz.getField(resName);
return (int)field.get(null);
} catch (Exception e) {
e.printStackTrace();
}
return 0;
}
}
//步骤4.运行宿主apk,输出log如下:
2019-09-17 17:10:40.051 HostClass: zwm, apkPath: /storage/emulated/0/plugin-debug.apk
2019-09-17 17:10:40.078 HostClass: zwm, pluginResources: android.content.res.Resources@1ea7b93
2019-09-17 17:10:40.092 HostClass: zwm, pluginResId: 2131165184
2019-09-17 17:10:40.093 HostClass: zwm, pluginStr: plugin
2.Activity生命周期管理
Activity组件是需要在manifest中注册后才能以标准Intent的方式启动,通过ClassLoader加载并实例化的Activity实例只是一个普通的Java对象,能调用对象的方法,但是它没有生命周期。
代理Activity模式
主项目APK注册一个代理Activity(命名为ProxyActivity),ProxyActivity是一个普通的Activity,但只是一个空壳,自身并没有什么业务逻辑。每次打开插件APK里的某一个Activity的时候,都是在主项目里使用标准的方式启动ProxyActivity,再在ProxyActivity的生命周期里同步调用插件中的Activity实例的生命周期方法,从而执行插件APK的业务逻辑。
用反射调用插件Activity相应生命周期:
//步骤1.创建一个插件apk工程module:plugin,并创建一个插件类PluginActivity,代码如下:
public class PluginActivity {
private static final String TAG = "PluginActivity";
protected void onResume() {
Log.d(TAG, "zwm, Plugin Activity onResume");
}
protected void onPause() {
Log.d(TAG, "zwm, Plugin Activity onPause");
}
protected void onDestroy() {
Log.d(TAG, "zwm, Plugin Activity onDestroy");
}
}
//步骤2.生成插件apk:plugin-debug.apk,并放到手机根目录
//步骤3.创建一个宿主apk工程module:app,其中MainActivity代码如下:
(需要在manifest中添加权限:<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>)
public class MainActivity extends Activity {
private static final String TAG = "HostActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(TAG, "zwm, Host Activity onCreate");
setContentView(R.layout.activity_main);
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
startPluginActivity();
}
}, 2000);
}
private void startPluginActivity() {
Log.d(TAG, "zwm, Host Activity startPluginActivity");
Intent intent = new Intent(this, ProxyActivity.class);
startActivity(intent);
}
}
//步骤4.创建一个代理类ProxyActivity,代码如下:
(需要在manifest中注册activity:<activity android:name=".ProxyActivity"/>)
public class ProxyActivity extends Activity {
private static final String TAG = "ProxyActivity";
private Class<?> pluginActivity;
private Object pluginInstance;
private Method onResume;
private Method onPause;
private Method onDestroy;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(TAG, "zwm, Proxy Activity onCreate");
init();
}
private void init() {
String apkPath = Environment.getExternalStorageDirectory() + "/plugin-debug.apk";
Log.d(TAG, "zwm, Proxy Activity init, apkPath: " + apkPath);
DexClassLoader classLoader = new DexClassLoader(apkPath, null, null, getClassLoader());
try {
pluginActivity = classLoader.loadClass("com.tomorrow.plugin.PluginActivity");
Log.d(TAG, "zwm, pluginActivity: " + pluginActivity);
pluginInstance = pluginActivity.newInstance();
Log.d(TAG, "zwm, pluginInstance: " + pluginInstance);
onResume = pluginActivity.getDeclaredMethod("onResume", null);
onResume.setAccessible(true);
Log.d(TAG, "zwm, onResume: " + onResume);
onPause = pluginActivity.getDeclaredMethod("onPause", null);
onPause.setAccessible(true);
Log.d(TAG, "zwm, onPause: " + onPause);
onDestroy = pluginActivity.getDeclaredMethod("onDestroy", null);
onDestroy.setAccessible(true);
Log.d(TAG, "zwm, onDestroy: " + onDestroy);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
@Override
protected void onResume() {
super.onResume();
Log.d(TAG, "zwm, Proxy Activity onResume");
if(onResume != null) {
try {
onResume.invoke(pluginInstance, null);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
@Override
protected void onPause() {
super.onPause();
Log.d(TAG, "zwm, Proxy Activity onPause");
if(onPause != null) {
try {
onPause.invoke(pluginInstance, null);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
@Override
protected void onDestroy() {
super.onDestroy();
Log.d(TAG, "zwm, Proxy Activity onDestroy");
if(onDestroy != null) {
try {
onDestroy.invoke(pluginInstance, null);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
}
//步骤5.运行宿主apk,输出log如下:
2019-10-22 17:05:56.714 D/HostActivity: zwm, Host Activity onCreate
2019-10-22 17:05:58.806 D/HostActivity: zwm, Host Activity startPluginActivity
2019-10-22 17:05:58.947 D/ProxyActivity: zwm, Proxy Activity onCreate
2019-10-22 17:05:58.948 D/ProxyActivity: zwm, Proxy Activity init, apkPath: /storage/emulated/0/plugin-debug.apk
2019-10-22 17:05:58.961 D/ProxyActivity: zwm, pluginActivity: class com.tomorrow.plugin.PluginActivity
2019-10-22 17:05:58.962 D/ProxyActivity: zwm, pluginInstance: com.tomorrow.plugin.PluginActivity@ae56d61
2019-10-22 17:05:58.962 D/ProxyActivity: zwm, onResume: protected void com.tomorrow.plugin.PluginActivity.onResume()
2019-10-22 17:05:58.962 D/ProxyActivity: zwm, onPause: protected void com.tomorrow.plugin.PluginActivity.onPause()
2019-10-22 17:05:58.962 D/ProxyActivity: zwm, onDestroy: protected void com.tomorrow.plugin.PluginActivity.onDestroy()
2019-10-22 17:05:58.993 D/ProxyActivity: zwm, Proxy Activity onResume
2019-10-22 17:05:58.994 D/PluginActivity: zwm, Plugin Activity onResume
2019-10-22 17:06:04.908 D/ProxyActivity: zwm, Proxy Activity onPause
2019-10-22 17:06:04.908 D/PluginActivity: zwm, Plugin Activity onPause
2019-10-22 17:06:05.498 D/ProxyActivity: zwm, Proxy Activity onDestroy
2019-10-22 17:06:05.498 D/PluginActivity: zwm, Plugin Activity onDestroy
用接口方式调用插件Activity相应生命周期:
//步骤1.创建一个插件apk工程module:plugin,并创建一个插件类PluginActivity,代码如下:
public class PluginActivity implements DLPlugin {
private static final String TAG = "PluginActivity";
@Override
public void onResume() {
Log.d(TAG, "zwm, Plugin Activity onResume");
}
@Override
public void onPause() {
Log.d(TAG, "zwm, Plugin Activity onPause");
}
@Override
public void onDestroy() {
Log.d(TAG, "zwm, Plugin Activity onDestroy");
}
}
//步骤2.在插件com.tomorrow.interfaces包中创建接口DLPlugin,代码如下:
package com.tomorrow.interfaces;
public interface DLPlugin {
public void onResume();
public void onPause();
public void onDestroy();
}
//步骤3.生成插件apk:plugin-debug.apk,并放到手机根目录
//步骤4.创建一个宿主apk工程module:app,其中MainActivity代码如下:
(需要在manifest中添加权限:<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>)
public class MainActivity extends Activity {
private static final String TAG = "HostActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(TAG, "zwm, Host Activity onCreate");
setContentView(R.layout.activity_main);
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
startPluginActivity();
}
}, 2000);
}
private void startPluginActivity() {
Log.d(TAG, "zwm, Host Activity startPluginActivity");
Intent intent = new Intent(this, ProxyActivity.class);
startActivity(intent);
}
}
//步骤5.创建一个代理类ProxyActivity,代码如下:
(需要在manifest中注册activity:<activity android:name=".ProxyActivity"/>)
public class ProxyActivity extends Activity {
private static final String TAG = "ProxyActivity";
private Class<?> pluginActivity;
private DLPlugin pluginInstance;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(TAG, "zwm, Proxy Activity onCreate");
init();
}
private void init() {
String apkPath = Environment.getExternalStorageDirectory() + "/plugin-debug.apk";
Log.d(TAG, "zwm, Proxy Activity init, apkPath: " + apkPath);
DexClassLoader classLoader = new DexClassLoader(apkPath, null, null, getClassLoader());
try {
pluginActivity = classLoader.loadClass("com.tomorrow.plugin.PluginActivity");
Log.d(TAG, "zwm, pluginActivity: " + pluginActivity);
pluginInstance = (DLPlugin) pluginActivity.newInstance();
Log.d(TAG, "zwm, pluginInstance: " + pluginInstance);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
@Override
protected void onResume() {
super.onResume();
Log.d(TAG, "zwm, Proxy Activity onResume");
pluginInstance.onResume();
}
@Override
protected void onPause() {
super.onPause();
Log.d(TAG, "zwm, Proxy Activity onPause");
pluginInstance.onPause();
}
@Override
protected void onDestroy() {
super.onDestroy();
Log.d(TAG, "zwm, Proxy Activity onDestroy");
pluginInstance.onDestroy();
}
}
//步骤6.在宿主com.tomorrow.interfaces包中创建接口DLPlugin,代码如下:
package com.tomorrow.interfaces;
public interface DLPlugin {
public void onResume();
public void onPause();
public void onDestroy();
}
//步骤7.运行宿主apk,输出log如下:
2019-10-22 18:34:06.470 D/HostActivity: zwm, Host Activity onCreate
2019-10-22 18:34:09.979 D/HostActivity: zwm, Host Activity startPluginActivity
2019-10-22 18:34:10.212 D/ProxyActivity: zwm, Proxy Activity onCreate
2019-10-22 18:34:10.215 D/ProxyActivity: zwm, Proxy Activity init, apkPath: /storage/emulated/0/plugin-debug.apk
2019-10-22 18:34:10.238 D/ProxyActivity: zwm, pluginActivity: class com.tomorrow.plugin.PluginActivity
2019-10-22 18:34:10.239 D/ProxyActivity: zwm, pluginInstance: com.tomorrow.plugin.PluginActivity@4b81192
2019-10-22 18:34:10.387 D/ProxyActivity: zwm, Proxy Activity onResume
2019-10-22 18:34:10.387 D/PluginActivity: zwm, Plugin Activity onResume
2019-10-22 18:34:10.438 D/ProxyActivity: zwm, Proxy Activity onPause
2019-10-22 18:34:10.438 D/PluginActivity: zwm, Plugin Activity onPause
二、使用(三星G9500,P OS)
//步骤1.从GitHub下载Dynamic-load-apk源码:https://github.com/singwhatiwanna/dynamic-load-apk
//步骤2.编译lib模块生成lib-debug.aar
//步骤3.将lib-debug.aar放入主项目模块及插件模块libs目录下
//步骤4.修改主项目模块及插件模块build.gradle,代码如下:
repositories {
flatDir{
dirs 'libs'
}
}
dependencies {
implementation(name: 'lib-debug', ext: 'aar')
}
//步骤5.插件模块两个Activity及对应的布局文件内容如下:
//MainActivity
public class MainActivity extends DLBasePluginActivity {
private static final String TAG = "PluginActivity";
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Log.d(TAG, "zwm, Plugin Activity onCreate");
}
@Override
public void onResume() {
super.onResume();
Log.d(TAG, "zwm, Plugin Activity onResume");
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
Log.d(TAG, "zwm, Plugin Activity startPluginActivity");
DLIntent intent = new DLIntent(getPackageName(), PluginActivity2.class);
startPluginActivity(intent);
}
}, 2000);
}
@Override
public void onPause() {
super.onPause();
Log.d(TAG, "zwm, Plugin Activity onPause");
}
@Override
public void onDestroy() {
super.onDestroy();
Log.d(TAG, "zwm, Plugin Activity onDestroy");
}
}
//activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:id="@+id/textview"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="This is Plugin Activity"
android:textSize="20dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</android.support.constraint.ConstraintLayout>
//PluginActivity2
public class PluginActivity2 extends DLBasePluginActivity {
private static final String TAG = "PluginActivity2";
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_2);
Log.d(TAG, "zwm, Plugin Activity2 onCreate");
Log.d(TAG, "zwm, Plugin Activity2 this: " + this);
Log.d(TAG, "zwm, Plugin Activity2 that: " + that);
TextView textView = that.findViewById(R.id.textview);
String text = textView.getText().toString();
Toast.makeText(that.getApplicationContext(), text, Toast.LENGTH_LONG).show();
}
@Override
public void onResume() {
super.onResume();
Log.d(TAG, "zwm, Plugin Activity2 onResume");
}
@Override
public void onPause() {
super.onPause();
Log.d(TAG, "zwm, Plugin Activity2 onPause");
}
@Override
public void onDestroy() {
super.onDestroy();
Log.d(TAG, "zwm, Plugin Activity2 onDestroy");
}
}
//activity_2.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:id="@+id/textview"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="This is Plugin Activity2"
android:textSize="20dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</android.support.constraint.ConstraintLayout>
//步骤6.插件模块manifest文件内容如下:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.tomorrow.plugin">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".PluginActivity2"
android:label="@string/app_name"
android:theme="@android:style/Theme.Light.NoTitleBar.Fullscreen" >
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
</application>
</manifest>
//步骤7.生成插件apk:plugin-debug.apk,并放到手机根目录
//步骤8.主项目模块Activity及对应的布局文件内容如下:
//MainActivity
public class MainActivity extends Activity {
private static final String TAG = "HostActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(TAG, "zwm, Host Activity onCreate");
setContentView(R.layout.activity_main);
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
startPluginActivity();
}
}, 2000);
}
private void startPluginActivity() {
Log.d(TAG, "zwm, Host Activity startPluginActivity");
String apkPath = Environment.getExternalStorageDirectory() + "/plugin-debug.apk";
Log.d(TAG, "zwm, Host Activity apkPath: " + apkPath);
File file = new File(apkPath);
if(!file.exists()) {
Log.d(TAG, "zwm, Host Activity file not exist");
return;
}
PluginItem item = new PluginItem();
item.pluginPath = apkPath;
Log.d(TAG, "zwm, Host Activity pluginPath: " + item.pluginPath);
item.packageInfo = DLUtils.getPackageInfo(this, item.pluginPath);
Log.d(TAG, "zwm, Host Activity packageInfo: " + item.packageInfo);
if (item.packageInfo.activities != null && item.packageInfo.activities.length > 0) {
item.launcherActivityName = item.packageInfo.activities[0].name;
Log.d(TAG, "zwm, Host Activity launcherActivityName: " + item.launcherActivityName);
}
if (item.packageInfo.services != null && item.packageInfo.services.length > 0) {
item.launcherServiceName = item.packageInfo.services[0].name;
Log.d(TAG, "zwm, Host Activity launcherServiceName: " + item.launcherServiceName);
}
DLPluginManager pluginManager = DLPluginManager.getInstance(this);
DLPluginPackage dlPluginPackage = pluginManager.loadApk(item.pluginPath);
Log.d(TAG, "zwm, Host Activity loadApk, dlPluginPackage: " + dlPluginPackage);
int result = pluginManager.startPluginActivity(this, new DLIntent(item.packageInfo.packageName, item.launcherActivityName));
Log.d(TAG, "zwm, Host Activity startPluginActivity, result: " + result);
}
public static class PluginItem {
public PackageInfo packageInfo;
public String pluginPath;
public String launcherActivityName;
public String launcherServiceName;
public PluginItem() {
}
}
}
//activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:id="@+id/textview"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="This is Host Activity"
android:textSize="20dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</android.support.constraint.ConstraintLayout>
//步骤9.主项目模块manifest文件内容如下:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.tomorrow.testnetworkcache">
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.INTERNET"/>
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:usesCleartextTraffic="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name="com.ryg.dynamicload.DLProxyActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="com.ryg.dynamicload.proxy.activity.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
</application>
</manifest>
//步骤10.运行宿主apk,输出log如下:
2019-10-23 13:21:45.129 12010-12010/com.tomorrow.testnetworkcache D/HostActivity: zwm, Host Activity onCreate
2019-10-23 13:21:47.278 12010-12010/com.tomorrow.testnetworkcache D/HostActivity: zwm, Host Activity startPluginActivity
2019-10-23 13:21:47.291 12010-12010/com.tomorrow.testnetworkcache D/HostActivity: zwm, Host Activity apkPath: /storage/emulated/0/plugin-debug.apk
2019-10-23 13:21:47.293 12010-12010/com.tomorrow.testnetworkcache D/HostActivity: zwm, Host Activity pluginPath: /storage/emulated/0/plugin-debug.apk
2019-10-23 13:21:47.347 12010-12010/com.tomorrow.testnetworkcache D/HostActivity: zwm, Host Activity packageInfo: PackageInfo{8d48aea com.tomorrow.plugin}
2019-10-23 13:21:47.347 12010-12010/com.tomorrow.testnetworkcache D/HostActivity: zwm, Host Activity launcherActivityName: com.tomorrow.plugin.MainActivity
2019-10-23 13:21:47.442 12010-12010/com.tomorrow.testnetworkcache D/HostActivity: zwm, Host Activity dlPluginPackage: com.ryg.dynamicload.internal.DLPluginPackage@ae86bb6
2019-10-23 13:21:47.484 12010-12010/com.tomorrow.testnetworkcache D/HostActivity: zwm, Host Activity result: 0
2019-10-23 13:21:47.615 12010-12010/com.tomorrow.testnetworkcache D/PluginActivity: zwm, Plugin Activity onCreate
2019-10-23 13:21:47.619 12010-12010/com.tomorrow.testnetworkcache D/PluginActivity: zwm, Plugin Activity onResume
2019-10-23 13:21:49.624 12010-12010/com.tomorrow.testnetworkcache D/PluginActivity: zwm, Plugin Activity startPluginActivity
2019-10-23 13:21:49.704 12010-12010/com.tomorrow.testnetworkcache D/PluginActivity: zwm, Plugin Activity onPause
2019-10-23 13:21:49.805 12010-12010/com.tomorrow.testnetworkcache D/PluginActivity2: zwm, Plugin Activity2 onCreate
2019-10-23 13:21:49.806 12010-12010/com.tomorrow.testnetworkcache D/PluginActivity2: zwm, Plugin Activity2 this: com.tomorrow.plugin.PluginActivity2@a89aa52
2019-10-23 13:21:49.806 12010-12010/com.tomorrow.testnetworkcache D/PluginActivity2: zwm, Plugin Activity2 that: com.ryg.dynamicload.DLProxyActivity@beeb123
2019-10-23 13:21:49.933 12010-12010/com.tomorrow.testnetworkcache D/PluginActivity2: zwm, Plugin Activity2 onResume
2019-10-23 13:22:01.939 12010-12010/com.tomorrow.testnetworkcache D/PluginActivity2: zwm, Plugin Activity2 onPause
三、原理
Dynamic-load-apk原理的核心思想可以总结为两个字:代理。通过在manifest中注册代理组件,当启动插件组件时首先启动一个代理组件,然后通过这个代理组件来构建、启动插件组件。
DLPluginManager.getInstance(this).loadApk(item.pluginPath)
//加载插件APK
public DLPluginPackage loadApk(String dexPath) {
// when loadApk is called by host apk, we assume that plugin is invoked
// by host.
return loadApk(dexPath, true); //调用重载方法加载插件APK
}
public DLPluginPackage loadApk(final String dexPath, boolean hasSoLib) {
mFrom = DLConstants.FROM_EXTERNAL;
PackageInfo packageInfo = mContext.getPackageManager().getPackageArchiveInfo(dexPath,
PackageManager.GET_ACTIVITIES | PackageManager.GET_SERVICES); //获取插件APK的PackageInfo对象
if (packageInfo == null) {
return null;
}
DLPluginPackage pluginPackage = preparePluginEnv(packageInfo, dexPath); //准备好插件使用环境
if (hasSoLib) {
copySoLib(dexPath); //拷贝插件的so库到指定目录
}
return pluginPackage;
}
private DLPluginPackage preparePluginEnv(PackageInfo packageInfo, String dexPath) {
DLPluginPackage pluginPackage = mPackagesHolder.get(packageInfo.packageName); //获取缓存
if (pluginPackage != null) {
return pluginPackage;
}
DexClassLoader dexClassLoader = createDexClassLoader(dexPath); //创建DexClassLoader
AssetManager assetManager = createAssetManager(dexPath); //创建AssetManager
Resources resources = createResources(assetManager); //创建Resources
// create pluginPackage
pluginPackage = new DLPluginPackage(dexClassLoader, resources, packageInfo); //封装到DLPluginPackage
mPackagesHolder.put(packageInfo.packageName, pluginPackage); //存入缓存
return pluginPackage; //返回DLPluginPackage
}
//注:@param optimizedDirectory this parameter is deprecated and has no effect since API level 26.
private DexClassLoader createDexClassLoader(String dexPath) {
File dexOutputDir = mContext.getDir("dex", Context.MODE_PRIVATE);
dexOutputPath = dexOutputDir.getAbsolutePath();
DexClassLoader loader = new DexClassLoader(dexPath, dexOutputPath, mNativeLibDir, mContext.getClassLoader()); //创建DexClassLoader
return loader;
}
private AssetManager createAssetManager(String dexPath) {
try {
AssetManager assetManager = AssetManager.class.newInstance();
Method addAssetPath = assetManager.getClass().getMethod("addAssetPath", String.class);
addAssetPath.invoke(assetManager, dexPath); //反射调用AssetManager#addAssetPath方法,加载插件APK资源
return assetManager;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
private Resources createResources(AssetManager assetManager) {
Resources superRes = mContext.getResources();
Resources resources = new Resources(assetManager, superRes.getDisplayMetrics(), superRes.getConfiguration()); //创建Resources
return resources;
}
DLPluginManager.getInstance(this).startPluginActivity(this, new DLIntent(item.packageInfo.packageName, item.launcherActivityName))
//启动插件Activity
public int startPluginActivity(Context context, DLIntent dlIntent) {
return startPluginActivityForResult(context, dlIntent, -1);
}
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
public int startPluginActivityForResult(Context context, DLIntent dlIntent, int requestCode) {
if (mFrom == DLConstants.FROM_INTERNAL) { //启动主项目模块内部的Activity
dlIntent.setClassName(context, dlIntent.getPluginClass());
performStartActivityForResult(context, dlIntent, requestCode);
return DLPluginManager.START_RESULT_SUCCESS;
}
String packageName = dlIntent.getPluginPackage(); //获取插件APK包名
if (TextUtils.isEmpty(packageName)) {
throw new NullPointerException("disallow null packageName.");
}
DLPluginPackage pluginPackage = mPackagesHolder.get(packageName); //获取插件APK对应的DLPluginPackage缓存
if (pluginPackage == null) {
return START_RESULT_NO_PKG;
}
final String className = getPluginActivityFullPath(dlIntent, pluginPackage); //获取要启动的插件Activity类的全限定名
Class<?> clazz = loadPluginClass(pluginPackage.classLoader, className); //加载要启动的插件Activity类
if (clazz == null) {
return START_RESULT_NO_CLASS;
}
// get the proxy activity class, the proxy activity will launch the
// plugin activity.
Class<? extends Activity> activityClass = getProxyActivityClass(clazz); //根据要启动的插件Activity类获取对应的代理Activity类
if (activityClass == null) {
return START_RESULT_TYPE_ERROR;
}
// put extra data
dlIntent.putExtra(DLConstants.EXTRA_CLASS, className);
dlIntent.putExtra(DLConstants.EXTRA_PACKAGE, packageName);
dlIntent.setClass(mContext, activityClass);
performStartActivityForResult(context, dlIntent, requestCode); //启动代理Activity
return START_RESULT_SUCCESS;
}
private Class<?> loadPluginClass(ClassLoader classLoader, String className) {
Class<?> clazz = null;
try {
clazz = Class.forName(className, true, classLoader); //使用指定的类加载器加载指定的类
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return clazz;
}
private Class<? extends Activity> getProxyActivityClass(Class<?> clazz) {
Class<? extends Activity> activityClass = null;
if (DLBasePluginActivity.class.isAssignableFrom(clazz)) { //如果clazz是DLBasePluginActivity的子类或者子接口
activityClass = DLProxyActivity.class; //返回代理Activity:DLProxyActivity
} else if (DLBasePluginFragmentActivity.class.isAssignableFrom(clazz)) { //如果clazz是DLBasePluginFragmentActivity的子类或者子接口
activityClass = DLProxyFragmentActivity.class; //返回代理Activity:DLProxyFragmentActivity
}
return activityClass;
}
public class DLProxyActivity extends Activity implements DLAttachable
//代理Activity
public class DLProxyActivity extends Activity implements DLAttachable {
protected DLPlugin mRemoteActivity; //插件Activity
private DLProxyImpl impl = new DLProxyImpl(this); //代理逻辑类
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
impl.onCreate(getIntent()); //代理逻辑初始化
}
@Override
public void attach(DLPlugin remoteActivity, DLPluginManager pluginManager) {
mRemoteActivity = remoteActivity; //插件Activity赋值
}
@Override
public AssetManager getAssets() { //获取AssetManager
return impl.getAssets() == null ? super.getAssets() : impl.getAssets();
}
@Override
public Resources getResources() { //获取Resources
return impl.getResources() == null ? super.getResources() : impl.getResources();
}
@Override
public Theme getTheme() { //获取Theme
return impl.getTheme() == null ? super.getTheme() : impl.getTheme();
}
@Override
public ClassLoader getClassLoader() { //获取ClassLoader
return impl.getClassLoader();
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
mRemoteActivity.onActivityResult(requestCode, resultCode, data);
super.onActivityResult(requestCode, resultCode, data);
}
@Override
protected void onStart() {
mRemoteActivity.onStart();
super.onStart();
}
@Override
protected void onRestart() {
mRemoteActivity.onRestart();
super.onRestart();
}
@Override
protected void onResume() {
mRemoteActivity.onResume();
super.onResume();
}
@Override
protected void onPause() {
mRemoteActivity.onPause();
super.onPause();
}
@Override
protected void onStop() {
mRemoteActivity.onStop();
super.onStop();
}
@Override
protected void onDestroy() {
mRemoteActivity.onDestroy();
super.onDestroy();
}
@Override
protected void onSaveInstanceState(Bundle outState) {
mRemoteActivity.onSaveInstanceState(outState);
super.onSaveInstanceState(outState);
}
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
mRemoteActivity.onRestoreInstanceState(savedInstanceState);
super.onRestoreInstanceState(savedInstanceState);
}
@Override
protected void onNewIntent(Intent intent) {
mRemoteActivity.onNewIntent(intent);
super.onNewIntent(intent);
}
@Override
public void onBackPressed() {
mRemoteActivity.onBackPressed();
super.onBackPressed();
}
@Override
public boolean onTouchEvent(MotionEvent event) {
super.onTouchEvent(event);
return mRemoteActivity.onTouchEvent(event);
}
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
super.onKeyUp(keyCode, event);
return mRemoteActivity.onKeyUp(keyCode, event);
}
@Override
public void onWindowAttributesChanged(LayoutParams params) {
mRemoteActivity.onWindowAttributesChanged(params);
super.onWindowAttributesChanged(params);
}
@Override
public void onWindowFocusChanged(boolean hasFocus) {
mRemoteActivity.onWindowFocusChanged(hasFocus);
super.onWindowFocusChanged(hasFocus);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
mRemoteActivity.onCreateOptionsMenu(menu);
return super.onCreateOptionsMenu(menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
mRemoteActivity.onOptionsItemSelected(item);
return super.onOptionsItemSelected(item);
}
@Override
public ComponentName startService(Intent service) {
return super.startService(service);
}
}
//代理逻辑类
public class DLProxyImpl {
private static final String TAG = "DLProxyImpl";
private String mClass;
private String mPackageName;
private DLPluginPackage mPluginPackage;
private DLPluginManager mPluginManager;
private AssetManager mAssetManager;
private Resources mResources;
private Theme mTheme;
private ActivityInfo mActivityInfo;
private Activity mProxyActivity; //代理Activity
protected DLPlugin mPluginActivity; //插件Activity
public ClassLoader mPluginClassLoader;
public DLProxyImpl(Activity activity) {
mProxyActivity = activity;
}
private void initializeActivityInfo() {
PackageInfo packageInfo = mPluginPackage.packageInfo;
if ((packageInfo.activities != null) && (packageInfo.activities.length > 0)) {
if (mClass == null) {
mClass = packageInfo.activities[0].name;
}
//Finals 修复主题BUG
int defaultTheme = packageInfo.applicationInfo.theme;
for (ActivityInfo a : packageInfo.activities) {
if (a.name.equals(mClass)) {
mActivityInfo = a;
// Finals ADD 修复主题没有配置的时候插件异常
if (mActivityInfo.theme == 0) {
if (defaultTheme != 0) {
mActivityInfo.theme = defaultTheme;
} else {
if (Build.VERSION.SDK_INT >= 14) {
mActivityInfo.theme = android.R.style.Theme_DeviceDefault;
} else {
mActivityInfo.theme = android.R.style.Theme;
}
}
}
}
}
}
}
private void handleActivityInfo() {
Log.d(TAG, "handleActivityInfo, theme=" + mActivityInfo.theme);
if (mActivityInfo.theme > 0) {
mProxyActivity.setTheme(mActivityInfo.theme);
}
Theme superTheme = mProxyActivity.getTheme();
mTheme = mResources.newTheme();
mTheme.setTo(superTheme);
// Finals适配三星以及部分加载XML出现异常BUG
try {
mTheme.applyStyle(mActivityInfo.theme, true);
} catch (Exception e) {
e.printStackTrace();
}
// TODO: handle mActivityInfo.launchMode here in the future.
}
public void onCreate(Intent intent) {
Log.d(TAG, "zwm, onCreate");
// set the extra's class loader
intent.setExtrasClassLoader(DLConfigs.sPluginClassloader);
mPackageName = intent.getStringExtra(DLConstants.EXTRA_PACKAGE); //要启动的插件APK包名
mClass = intent.getStringExtra(DLConstants.EXTRA_CLASS); //要启动的插件Activity类名
Log.d(TAG, "mClass=" + mClass + " mPackageName=" + mPackageName);
mPluginManager = DLPluginManager.getInstance(mProxyActivity);
mPluginPackage = mPluginManager.getPackage(mPackageName); //获取插件APK对应的DLPluginPackage缓存
mAssetManager = mPluginPackage.assetManager; //获取插件APK对应的AssetManager
mResources = mPluginPackage.resources; //获取插件APK对应的Resources
initializeActivityInfo(); //获取插件Activity主题
handleActivityInfo(); //给代理Activity设置主题
launchTargetActivity(); //启动插件Activity
}
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
protected void launchTargetActivity() {
try {
Class<?> localClass = getClassLoader().loadClass(mClass); //加载插件Activity类
Constructor<?> localConstructor = localClass.getConstructor(new Class[] {});
Object instance = localConstructor.newInstance(new Object[] {}); //创建插件Activity对象
mPluginActivity = (DLPlugin) instance; //使用接口引用
((DLAttachable) mProxyActivity).attach(mPluginActivity, mPluginManager); //将插件Activity对象传递给代理Activity对象
Log.d(TAG, "instance = " + instance);
// attach the proxy activity and plugin package to the mPluginActivity
mPluginActivity.attach(mProxyActivity, mPluginPackage); //将代理Activity对象传递给插件Activity对象
Bundle bundle = new Bundle();
bundle.putInt(DLConstants.FROM, DLConstants.FROM_EXTERNAL);
mPluginActivity.onCreate(bundle); //调用插件Activity的onCreate方法
} catch (Exception e) {
e.printStackTrace();
}
}
public ClassLoader getClassLoader() {
return mPluginPackage.classLoader;
}
public AssetManager getAssets() {
return mAssetManager;
}
public Resources getResources() {
return mResources;
}
public Theme getTheme() {
return mTheme;
}
public DLPlugin getRemoteActivity() {
return mPluginActivity;
}
}
插件内部资源加载
//public class PluginActivity extends DLBasePluginActivity
String name = getResources().getString(R.string.app_name); //通过DLBasePluginActivity获取Resources
//public class DLBasePluginActivity extends Activity implements DLPlugin
public Resources getResources() { //启动插件Activity时mFrom值为1
return this.mFrom == 0 ? super.getResources() : this.mProxyActivity.getResources(); //通过DLProxyActivity获取Resources
}
//public class DLProxyActivity extends Activity implements DLAttachable
@Override
public Resources getResources() {
return impl.getResources() == null ? super.getResources() : impl.getResources(); //通过DLProxyImpl获取Resources
}
//public class DLProxyImpl
public Resources getResources() {
return mResources; //插件APK对应的Resources
}
//public class DLProxyImpl
public void onCreate(Intent intent) {
...
mPluginManager = DLPluginManager.getInstance(mProxyActivity);
mPluginPackage = mPluginManager.getPackage(mPackageName); //获取插件APK对应的DLPluginPackage缓存
mAssetManager = mPluginPackage.assetManager; //获取插件APK对应的AssetManager
mResources = mPluginPackage.resources; //获取插件APK对应的Resources
...
}
网友评论