简介
- 模块化:将一个项目的可以共享的部分抽取出来,形成独立的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下载