簡介
- 模塊化:將一個項目的可以共享的部分抽取出來,形成獨立的lib
- 組件化:組件化本來就是模塊化的概念。核心是模塊角色的可轉化換性,在打包時,是library;調試時,時application
- 插件化:將一個完整的工程,按業務劃分爲不同的插件,來化整爲零,相互配合。插件化的單位是apk(一個完成的應用)。 可以實現apk 的動態加載,動態更新,比組件化更靈活。
我們這裏將以插件化方式,實現一個換膚的功能,通過宿主app的ProxyActivity加載子類app的mainactivity
實現方式
項目目錄結構:
- app就是宿主app,依賴lib_base_plugin
- douban就是需要替換的apk,依賴lib_base_plugin
- lib_base_plugin是公共方法,以及接口
app中的 PluginManager類中 加載sdcard中的plugin.apk文件,得到這個文件中的資源文件resources以及dex加載方式dexClassLoader
public void loadPath(Context context) {
File filesDir = Environment.getExternalStorageDirectory();
String name = "plugin.apk";
String path = new File(filesDir, name).getAbsolutePath();
Log.d(TAG, "--path--" + path);
PackageManager packageManager = context.getPackageManager();
packageInfo = packageManager.getPackageArchiveInfo(path, PackageManager.GET_ACTIVITIES);
//activity
File dex = new File(filesDir, "dex");
dexClassLoader = new DexClassLoader(path, dex.getAbsolutePath(), null, context.getClassLoader());
//resource
try {
AssetManager manager = AssetManager.class.newInstance();
Method addAssetPath = AssetManager.class.getMethod("addAssetPath", String.class);
addAssetPath.invoke(manager, path);
resources = new Resources(manager,
context.getResources().getDisplayMetrics(),
context.getResources().getConfiguration());
} catch (Exception e) {
e.printStackTrace();
}
}
lib_base_plugin中的PayInterfaceActivity接口,所有子類需要用插件化加載的activity,都需要繼承這個接口
public interface PayInterfaceActivity {
public void attach(Activity proxyActivity);
/**
*生命週期
*/
public void onCreate(Bundle saveInstanceState);
public void onStart();
public void onResume();
public void onPause();
public void onStop();
public void onDestroy();
public void onSaveInstanceState(Bundle outState);
public boolean onTouchEvent(MotionEvent event);
public void onBackPressed();
}
在douban中用一個BaseActivity繼承PayInterfaceActivity,這裏的oncreate onStart onResume等,都是PayInterfaceActivity中的方法
public class BaseActivity extends Activity implements PayInterfaceActivity {
protected Activity that;
@Override
public void attach(Activity proxyActivity) {
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) {
}
}
在douban中的MainActivity我們就只寫了一個文字
public class MainActivity extends BaseActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
<?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:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="豆瓣中的MainActivity"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</android.support.constraint.ConstraintLayout>
我們要將douban中的MainActivity加載到ProxyActivity,我們來看一下ProxyActivity中主要的方法
重寫了activity的getClassLoader方法,返回的是子類APP的getDexClassLoader
重寫了activity的getResources方法,返回子類APP的getResources
就是通過這兩個方法,修改了宿主app的加載方式
//重寫加載類
@Override
public ClassLoader getClassLoader() {
return PluginManager.getInstance().getDexClassLoader();
}
//重寫加載資源
@Override
public Resources getResources() {
return PluginManager.getInstance().getResources();
}
看一下ProxyActivity完整代碼
public class ProxyActivity extends Activity {
private static final String TAG = "ProxyActivity";
//需要加載插件的全類名
private String className;
PayInterfaceActivity payInterfaceActivity;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
className = getIntent().getStringExtra("className");
Log.e(TAG, "---className--" + className);
try {
//TaoMainActivity
Class<?> aClass = getClassLoader().loadClass(className);
Constructor constructor = aClass.getConstructor(new Class[]{});
Object in = constructor.newInstance(new Object[]{});
payInterfaceActivity = (PayInterfaceActivity) in;
payInterfaceActivity.attach(this);
//如果需要參數,可以使用Bundle
Bundle bundle = new Bundle();
payInterfaceActivity.onCreate(bundle);
} 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();
}
}
app中的MainActivity
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
load();
}
});
}
public void load() {
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);
}
}
注意事項:
- ProxyActivity的android:launchMode="standard",必須是standard,因爲我們每一個activity其實都是ProxyActivity,如果設置爲singleTask,返回就沒有用了
- PluginManager.getInstance().getPackageInfo().activities[0].name得到的activity是AndroidManifest.xml中你註冊的activity順序來算的
- 需要將子類app生成的plugin.apk放置在sdcard中,並且開啓讀取權限
demo下載地址demo下載