1 插件化概述
插件化屬於動態加載技術
動態加載技術:插件化、熱修復
動態加載技術:在應用程序運行時,動態加載一些程序中原本不存在的可執行文件並運行這些文件裏的代碼邏輯,可執行文件總的來說分爲兩個,一個是動態鏈接庫so,另一種是dex相關文件(dex文件包含jar/apk文件)。
插件化的作用:主要用於解決應用越來越龐大以及功能模塊的解耦,所以小項目中一般用的不多。
熱修復:主要用於修復bug。
業務複雜,模塊的解耦。
應用的接入。
65536限制,內存佔用大。
2 插樁式原理與實現
在宿主 App 中使用反射加載插件中的類 A, A 是沒有生命週期的,就是一個普普通通的類而已 。比如插件裏的 Activity , 就算我們“欺騙了” AMS 檢查 AndroidManifest 的過程,這個Activity 也啓動不了,類似 onResume 、 onPaused 這些生命週期函數都不能被正常調用,因爲宿主 App 根本就不把它當作 Activity 來對待 。爲此,我們在宿主 App 中設計一個代理類 ProxyActivity ,這是一個 Activity ,是宿主 App所認識的 。 讓 ProxyActivity 內部有一個對插件 ActivityA 的引用,讓ProxyActivity 的任何生命週期函數都調用 ActivityA 中同名函數。
新建Android項目,並創建兩個Module:
- Module1:pluginstand,是一個Android Library;
- Module2:taopiaopiao,是一個Phone Module。
將pluginstand作爲依賴庫分別添加到app Module和taopiaopiao Module中。
因爲插件(tiaopiaopiao)只是一個apk,並沒有安裝到手機上,不具有生命週期,這就需要通過獲取宿主(app)的生命週期。
2.1 pluginstand
在pluginstand中創建一個接口PayInterfaceActivity,包含了各個生命週期的方法,如下:
public interface PayInterfaceActivity {
void attach(Activity proxyActivity);
void onCreate(Bundle saveInstanceState);
void onStart();
void onResume();
void onPause();
void onStop();
void onDestroy();
void onSaveInstanceState(Bundle outState);
boolean onTouchEvent(MotionEvent event);
void onBackPressed();
}
2.2 插件taopiaopiao
在taopiaopiao中創建一個基類BaseActivity實現PayInterfaceActivity接口,如下:
public class BaseActivity extends Activity implements PayInterfaceActivity {
protected Activity that;
@Override
public void attach(Activity proxyActivity) {//1
this.that = proxyActivity;
}
@Override
public void setContentView(View view) {
if(that != null){
that.setContentView(view);
} else {
super.setContentView(view);
}
}
@Override
public void setContentView(int layoutResID) {
if(that != null){
that.setContentView(layoutResID);
} else {
super.setContentView(layoutResID);
}
}
@Override
public void startActivity(Intent intent) {
Intent m = new Intent();
m.putExtra("className",intent.getComponent().getClassName());
that.startActivity(m);
}
@Override
public View findViewById(int id) {
return that.findViewById(id);
}
@Override
public void onCreate(Bundle saveInstanceState) {
}
@Override
public void onStart() {
}
@Override
public void onResume() {
}
@Override
public void onPause() {
}
@Override
public void onStop() {
}
@Override
public void onDestroy() {
}
@Override
public void onSaveInstanceState(Bundle outState) {
}
}
註釋1:通過attach方法,將宿主(app)的ProxyActivity傳入
插件taopiaopiao中的主頁面TaoMainActivity,如下:
public class TaoMainActivity extends BaseActivity {
@Override
public void onCreate(Bundle savedInstanceState) {//1
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);//2
findViewById(R.id.img).setOnClickListener(new View.OnClickListener() {//3
@Override
public void onClick(View v) {
Toast.makeText(that,"插件",Toast.LENGTH_SHORT).show();
startActivity(new Intent(that, SceondActivity.class));//4
}
});
}
}
註釋1:覆寫了BaseActivity中的onCreate方法。
註釋2:調用BaseActivity的setContentView方法。
註釋3:調用BaseActivity的findViewById方法。
註釋4:調用BaseActivity的startActivity方法實現在插件中跳轉到另一個頁面。
2.3 宿主 app
在MainActivity有兩個按鈕,一個用於加載插件,一個用於跳轉到插件的頁面
public void load(View view) {
PluginManager.getInstance().loadPath(this);
}
public void click(View view) {
Intent intent = new Intent(this, ProxyActivity.class);
intent.putExtra("className", PluginManager.getInstance().getPackageInfo().activities[0].name);
startActivity(intent);
}
下面是具體實現。
2.3.1 獲取插件
將taopiaopiao中生成的apk拷貝到data/data/com.hongx.plugin/app_plugin
創建PluginManager用於加載apk,並獲取Resources、DexClassLoader和PackageInfo,如下:
public class PluginManager {
private static final PluginManager ourInstance = new PluginManager();
private DexClassLoader dexClassLoader;
private Resources resources;
private PackageInfo packageInfo;
public static PluginManager getInstance(){
return ourInstance;
}
public PluginManager() {
}
public void loadPath(Context context){
File filesDir = context.getDir("plugin", Context.MODE_PRIVATE);
String name = "plugin.apk";
String path = new File(filesDir, name).getAbsolutePath();
PackageManager packageManager = context.getPackageManager();
packageInfo = packageManager.getPackageArchiveInfo(path, PackageManager.GET_ACTIVITIES);
//activity
///data/data/com.hongx.plugin/app_dex/plugin.dex
File dex = context.getDir("dex", Context.MODE_PRIVATE);
dexClassLoader = new DexClassLoader(path, dex.getAbsolutePath(), null, context.getClassLoader());
//resource
try {
AssetManager manager = AssetManager.class.newInstance();
Method addAssetPath = AssetManager.class.getMethod("addAssetPath", String.class);
//path = /data/data/com.hongx.plugin/app_plugin/plugin.apk
addAssetPath.invoke(manager, path);
resources = new Resources(manager,
context.getResources().getDisplayMetrics(),
context.getResources().getConfiguration());
} catch (Exception e) {
e.printStackTrace();
}
}
public Resources getResources(){
return resources;
}
public DexClassLoader getDexClassLoader(){
return dexClassLoader;
}
public PackageInfo getPackageInfo(){
return packageInfo;
}
}
2.3.2 創建ProxyActivity
public class ProxyActivity extends Activity {
//需要加載插件的全類名
private String className;
PayInterfaceActivity payInterfaceActivity;
@Override
protected void onCreate( Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
className = getIntent().getStringExtra("className");//1
try {
//TaoMainActivity
Class<?> aClass = getClassLoader().loadClass(className);
Constructor constructor = aClass.getConstructor(new Class[]{});
Object in = constructor.newInstance(new Object[]{});//2
payInterfaceActivity = (PayInterfaceActivity) in;
payInterfaceActivity.attach(this);//3
//如果需要參數,可以使用Bundle
Bundle bundle = new Bundle();
payInterfaceActivity.onCreate(bundle);//4
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void startActivity(Intent intent) {
String className = intent.getStringExtra("className");
Intent intent1 = new Intent(this, ProxyActivity.class);
intent1.putExtra("className", className);
super.startActivity(intent1);
}
//重寫加載類
@Override
public ClassLoader getClassLoader() {
return PluginManager.getInstance().getDexClassLoader();
}
//重寫加載資源
@Override
public Resources getResources() {
return PluginManager.getInstance().getResources();
}
@Override
protected void onStart() {
super.onStart();
payInterfaceActivity.onStart();
}
@Override
protected void onResume() {
super.onResume();
payInterfaceActivity.onResume();
}
@Override
protected void onStop() {
super.onStop();
payInterfaceActivity.onStop();
}
@Override
protected void onDestroy() {
super.onDestroy();
payInterfaceActivity.onDestroy();
}
}
註釋1:在上面已經提到點擊頁面跳轉按鈕跳轉至插件的TaoMainActivity。PluginManager.getInstance().getPackageInfo()得到的是插件的包信息,activities[0]對應的就是ToMainActivity,也就是獲取到ToMainActivity的類名並將類名傳遞到ProxyActivity中以供使用。
註釋2:通過類加載器和反射機制獲取到了TaoMainActivity對象。
註釋3:將ProxyActivity傳遞到TaoMainActivity中。
註釋4:調用了TaoMainActivity的onCreate方法。
再來分析下TaoMainActivity:
public class TaoMainActivity extends BaseActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);//1
findViewById(R.id.img).setOnClickListener(new View.OnClickListener() {//2
@Override
public void onClick(View v) {
Toast.makeText(that,"插件",Toast.LENGTH_SHORT).show();
startActivity(new Intent(that, SceondActivity.class));
}
});
}
}
- 註釋1:調用的是BaseActivity中的setContentView方法,其實最終調用的是ProxyActivity的setContentView方法,代碼如下:
@Override
public void setContentView(int layoutResID) {
if(that != null){
that.setContentView(layoutResID);
} else {
super.setContentView(layoutResID);
}
}
- 註釋2:跳轉到taopiaopiao的SceondActivity
BaseActivity的startActivity方法如下:
@Override
public void startActivity(Intent intent) {
Intent m = new Intent();
m.putExtra("className",intent.getComponent().getClassName());
that.startActivity(m);
}
最後還是使用了ProxyActivity的startActivity方法,如下:
@Override
public void startActivity(Intent intent) {
String className = intent.getStringExtra("className");
Intent intent1 = new Intent(this, ProxyActivity.class);
intent1.putExtra("className", className);
super.startActivity(intent1);
}
className就是SecondActivity的類名,流程和加載TaoMainActivity一樣,也是將類名傳遞到ProxyActivity中。