怎样在宿主中启动插件中的组件
首先,我们能够调用插件中的组件,得益于Java中提供的反射机制与类型强转机制。由于我们不能拿到插件中类的真实类型,想要调用到插件中的组件,需要制定一个标准,只有所有插件都实现了 此标准,才能够在宿主App中调用插件中的功能。那这里所谓的标准,就是Java中的接口。制定一套接口,让所有插件和宿主都依赖该接口,就可以在宿主内通过反射获取到插件内部的组件,然后强转为实现的Interface类型,这样就可以进行通信了。
创建一个项目,包含3个module,其中一个为插件化标准:standard,另外两个分别为宿主module:app以及插件module人:plugin_package,二者都需要依赖于standard。
制定Activity的标准-ActivityInterface
package com.oliver.standard;
public interface ActivityInterface {
/**
* 把宿主(App)的环境给插件
*
* @param appActivity 宿主Activity
*/
void insertAppContext(Activity appActivity);
void onActivityCreate(Bundle savedInstanceState);
void onActivityStart();
void onActivityResume();
void onActivityDestroy();
void onActivityPause();
void onActivityStop();
void onActivityRestart();
void onActivityNewIntent(Intent intent);
}
在plugin_package中添加BaseActivity,并实现ActivityInterface
package com.oliver.plugin_package;
public class BaseActivity extends Activity implements ActivityInterface {
// 宿主的Activity环境
public Activity appActivity;
@Override
public void insertAppContext(Activity appActivity) {
this.appActivity = appActivity;
}
@Override
public void onActivityCreate(Bundle savedInstanceState) {
}
@Override
public void onActivityStart() {
}
@Override
public void onActivityResume() {
}
@Override
public void onActivityDestroy() {
}
@Override
public void onActivityPause() {
}
@Override
public void onActivityStop() {
}
@Override
public void onActivityRestart() {
}
@Override
public void onActivityNewIntent(Intent intent) {
}
// 重写findViewByID方法,因为该方法会涉及到Activity对应的环境。由于这里的Activity是插件中的,故而没有
// 使用环境,因此通过宿主appActivity来调用。在回调到AppActivity的时候,就可以通过PluginManager获取到
// DexClassLoader和插件Resources,用来加载classes文件和资源
// 同理,和使用环境挂钩的都需要重写,使用宿主AppActivity来替代
@Override
public <T extends View> T findViewById(int id) {
return appActivity.findViewById(id);
}
@Override
public void setContentView(int res) {
appActivity.setContentView(res);
}
public void startActivity(Class<? extends Activity> clazz){
Intent intent = new Intent();
intent.putExtra(Const.COMPONENT_NAME,clazz.getName());
appActivity.startActivity(intent);
}
@Override
public ComponentName startService(Intent service) {
Intent intent = new Intent();
intent.putExtra(Const.COMPONENT_NAME,service.getComponent().getClassName());
return appActivity.startService(intent);
}
}
@Nullable
public <T extends View> T findViewById(@IdRes int id) {
// 一个没有启动过的Activity是没有环境的,故而也不存在window
return getWindow().findViewById(id);
}
创建PluginActivity并继承BaseActivity
public class PluginActivity extends BaseActivity {
@Override
public void onActivityCreate(Bundle savedInstanceState) {
super.onActivityCreate(savedInstanceState);
// 调用父类
// 该布局文件中定义了两个按钮,分别用来启动插件内部的Activity和Service
setContentView(R.layout.activity_plugin);
// 这里的上下文不能使用this,同样的道理,当前Activity没有使用环境
// 注意,凡是在插件中使用到上下文的都应该使用宿主传过来的AppActivity
Toast.makeText(appActivity, "这是插件的吐司", Toast.LENGTH_SHORT).show();
findViewById(R.id.btn_start).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
startActivity(TestActivity.class);
}
});
findViewById(R.id.btn_start_service).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
startService(new Intent(appActivity, TestService.class));
}
});
}
}
在宿主module中启动PluginActivity
● 初始化PluginManager
package com.oliver.plugin;
public class App extends Application {
@Override
public void onCreate() {
super.onCreate();
PluginManager.initContext(this);
}
}
ProxyActivity
创建一个代理Activity,称之为ProxyActivity。如果我们直接在宿主中调用startActivity启动PluginActivity,肯定是会报错的。因为最起码PluginActivity没有在宿主App中的manifest.xml文件中进行注册。所以我们需要一个ProxyActivity,通过ProxyActivity重写getClassLoader()和getResources()方法,在各个声明周期方法回调的时候通过制定的标准去通知PluginActivity,这样就可以将PluginActivity的生命周期方法正常调用。由于ProxyActivity不做别的事,仅仅是一个用来给PluginActivity进行占位使用的,故而该类方式又称之为占位式插件化。
public class ProxyActivity extends Activity {
private static final String TAG = "ProxyActivity";
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 该参数来自MainActivity中,表示PluginActivity的全类型定名
String className = getIntent().getStringExtra(Const.COMPONENT_NAME);
try {
// 加载插件中的PluginActivity
Class<?> pluginActivityClass = getClassLoader().loadClass(className);
// 实例化插件包里面的Activity
Constructor<?> constructor = pluginActivityClass.getConstructor(new Class[]{});
Object instance = constructor.newInstance(new Object[]{});
ActivityInterface activityInterface = (ActivityInterface) instance;
// 给插件注入宿主环境
activityInterface.insertAppContext(this);
Bundle bundle = new Bundle();
bundle.putString("appName", "宿主传递过来的信息");
// 执行插件里面的onActivityCreate()
activityInterface.onActivityCreate(bundle);
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
protected void onPause() {
super.onPause();
}
@Override
protected void onStop() {
super.onStop();
}
@Override
protected void onRestart() {
super.onRestart();
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
}
/**
* 插件内部启动Activity
*/
@Override
public void startActivity(Intent intent) {
String className = intent.getStringExtra(Const.COMPONENT_NAME);
// 要给TestActivity进栈,相当于使用ProxyActivity去加载TestActivity的布局,然后ProxyActivity占据的
// 回退栈的位置,实际上应该是TestActivity所在,只不过其功能全部由ProxyActivity代理了而已
Intent proxyIntent = new Intent(this, ProxyActivity.class);
proxyIntent.putExtra(Const.COMPONENT_NAME, className);
// 这里需要使用super调用,否则会造成死递归
super.startActivity(proxyIntent);
}
@Override
public ComponentName startService(Intent service) {
String className = service.getStringExtra(Const.COMPONENT_NAME);
// 要给TestActivity进栈
Intent proxyIntent = new Intent(this, ProxyService.class);
proxyIntent.putExtra(Const.COMPONENT_NAME, className);
return super.startService(proxyIntent);
}
/**
* 重写该方法,表示加载的资源是插件中的资源
*/
@Override
public Resources getResources() {
return PluginManager.getInstance().getResources();
}
/**
* 重写该方法,表示加载的classes是插件中的类
*/
@Override
public ClassLoader getClassLoader() {
return PluginManager.getInstance().getClassLoader();
}
}
注意:ProxyActivity只能继承至Activity,如果继承AppCompatActivity的话,就会报错。原因是调用以下代码段的时候,R.id.decor_content_parent在宿主和插件中的值不一致导致。经过Debug发现,调用到这里的时候,使用的是插件中的id值,导致找不到DecorContentParent,接下来就直接报NPE。
报错代码段:
// Now inflate the view using the themed context and set it as the content view
subDecor = (ViewGroup) LayoutInflater.from(themedContext).inflate(R.layout.abc_screen_toolbar, null);
mDecorContentParent = (DecorContentParent) subDecor.findViewById(R.id.decor_content_parent);
mDecorContentParent.setWindowCallback(getWindowCallback());
报错堆栈信息:
E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.oliver.plugin, PID: 8909
java.lang.NullPointerException: Attempt to invoke interface method 'void androidx.appcompat.widget.DecorContentParent.setWindowCallback(android.view.Window$Callback)' on a null object reference
at androidx.appcompat.app.AppCompatDelegateImpl.createSubDecor(AppCompatDelegateImpl.java:900)
at androidx.appcompat.app.AppCompatDelegateImpl.ensureSubDecor(AppCompatDelegateImpl.java:806)
at androidx.appcompat.app.AppCompatDelegateImpl.onPostCreate(AppCompatDelegateImpl.java:527)
at androidx.appcompat.app.AppCompatActivity.onPostCreate(AppCompatActivity.java:127)
at android.app.Instrumentation.callActivityOnPostCreate(Instrumentation.java:1343)
MainActivity中通过按钮点击事件启动插件中的PluginActivity
package com.oliver.plugin;
// 启动插件中的Activity
public void startPlugin(View view) {
// 插件包
String dexPath = PluginManager.getInstance().getDexPath();
if (TextUtils.isEmpty(dexPath)) {
Log.e(TAG, "startPlugin: 插件文件不存在");
}
// 获取插件包里面的Activity
PackageManager packageManager = getPackageManager();
// 获取包名对应的应用的PackageInfo,通过该对象可以获取到应用的诸多信息,例如,所有的组件等
PackageInfo packageInfo = packageManager.getPackageArchiveInfo(dexPath, PackageManager.GET_ACTIVITIES);
// 获取manifest.xml 第一个注册的Activity,也就是PluginActivity
ActivityInfo activityInfo = packageInfo.activities[0];
Intent intent = new Intent(this, ProxyActivity.class);
intent.putExtra(Const.COMPONENT_NAME,activityInfo.name);
startActivity(intent);
}
经过以上步骤的编写,我们就可以通过宿主启动插件的Activity组件了。
网友评论