使用OpenAtlas进行插件化开发,插件的开发几乎可以按照正常程序的开发流程进行,无需添加额外的东西。为了验证四大组件是否能够正常工作,这里编写一个插件,验证其功能。除了四大组件外,大多数应用还有Application类。该类我们也需要进行验证。
首先新建一个模块,按照正常流程进行开发。新建Application类,为了方便起见,所有验证都使用日志输出形式。
public class App extends Application {
@Override
public void onCreate() {
super.onCreate();
Log.e("TAG", "Plugin named Component has init!");
Log.e("TAG", "===Plugin Application is===" + this);
}
}
Activity的验证不再重复,前面几篇文章都是以Activity或者Fragment为基础的,有兴趣可以转到相关链接。
- Android插件化开发之OpenAtlas初体验
- Android插件化开发之OpenAtlas生成插件信息列表
- Android插件化开发之OpenAtlas资源打包工具补丁aapt的编译
- Android插件化开发之OpenAtlas插件适配
- Android插件化开发之解决OpenAtlas组件在宿主的注册问题
接下来验证Service,新建一个Service,清单文件的注册放到最后。
public class ComponentService extends Service {
@Override
public void onCreate() {
super.onCreate();
Log.e("TAG", "===Plugin Service onCreate===");
}
@Override
public IBinder onBind(Intent intent) {
Log.e("TAG", "===Plugin Service onBind===");
return new BinderImpl();
}
@Override
public boolean onUnbind(Intent intent) {
Log.e("TAG", "===Plugin Service onUnbind===");
return super.onUnbind(intent);
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.e("TAG", "===Plugin Service onStartCommand===");
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
super.onDestroy();
Log.e("TAG", "===Plugin Service onDestroy===");
}
class BinderImpl extends Binder{
BinderImpl getService(){
return BinderImpl.this;
}
void testMethod(){
Log.e("TAG","BinderImpl testMethod");
}
}
}
然后是ContentProvider。都是空实现,只是输出日志。
public class ComponentContentProvider extends ContentProvider {
@Override
public boolean onCreate() {
Log.e("TAG","ComponentContentProvider onCreate");
return false;
}
@Nullable
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
Log.e("TAG","ComponentContentProvider query:"+Arrays.asList(projection)+" "+Arrays.asList(selectionArgs)+" "+selectionArgs+" "+sortOrder);
return null;
}
@Nullable
@Override
public String getType(Uri uri) {
Log.e("TAG","ComponentContentProvider getType");
return null;
}
@Nullable
@Override
public Uri insert(Uri uri, ContentValues values) {
Log.e("TAG","ComponentContentProvider insert:"+values);
return null;
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
Log.e("TAG","ComponentContentProvider delete:"+selection+" "+ Arrays.asList(selectionArgs));
return 0;
}
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
Log.e("TAG","ComponentContentProvider update:"+values+" "+selection+" "+Arrays.asList(selectionArgs));
return 0;
}
}
以及广播,广播有两种,一种是静态广播,需要在清单文件中注册,一种是动态广播,代码注册
public class ComponentBroadcast extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Log.e("TAG", "===Plugin BroadcastReceiver onReceive===");
}
}
public class ComponentBroadcastDynamic extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Log.e("TAG", "===Plugin ComponentBroadcastDynamic onReceive===");
}
}
最后在插件的清单文件中进行这些组件的注册。
<?xml version="1.0" encoding="utf-8"?>
<manifest package="cn.edu.zafu.component"
xmlns:android="http://schemas.android.com/apk/res/android">
<application
android:name="cn.edu.zafu.component.App"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme">
<activity
android:name=".MainActivity"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<service
android:name="cn.edu.zafu.component.ComponentService"
android:exported="false">
</service>
<receiver android:name="cn.edu.zafu.component.ComponentBroadcast">
<intent-filter>
<action
android:name="cn.edu.zafu.component"/>
</intent-filter>
</receiver>
<provider
android:name="cn.edu.zafu.component.ComponentContentProvider"
android:authorities="cn.edu.zafu.component.ComponentContentProvider"/>
</application>
</manifest>
编写一个Activity验证所有功能,Activity的布局不再贴出,整个布局就是Button组成的一个布局。
public class MainActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Log.e("TAG", "===Plugin MainActivity is===" + this);
findViewById(R.id.start_service).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(MainActivity.this, ComponentService.class);
startService(intent);
}
});
findViewById(R.id.stop_service).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(MainActivity.this, ComponentService.class);
stopService(intent);
}
});
final ServiceConnection conn = new ServiceConnection() {
@Override
public void onServiceDisconnected(ComponentName name) {
Log.e("TAG", "onServiceDisconnected()");
}
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.i("TAG", "onServiceConnected()");
ComponentService.BinderImpl binder = (ComponentService.BinderImpl) service;
ComponentService.BinderImpl bindService = binder.getService();
bindService.testMethod();
}
};
findViewById(R.id.bind_service).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(MainActivity.this, ComponentService.class);
Log.i("TAG", "bindService()");
bindService(intent, conn, Context.BIND_AUTO_CREATE);
}
});
findViewById(R.id.unbind_service).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
unbindService(conn);
}
});
findViewById(R.id.broadcast).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent();
intent.setAction("cn.edu.zafu.component");
sendBroadcast(intent);
}
});
final ComponentBroadcastDynamic componentBroadcastDynamic = new ComponentBroadcastDynamic();
findViewById(R.id.register_broadcast).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction("cn.edu.zafu.component.ComponentBroadcastDynamic");
registerReceiver(componentBroadcastDynamic, intentFilter);
Log.e("TAG","ComponentBroadcastDynamic registerReceiver");
}
});
findViewById(R.id.send_broadcast_dynamic).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent();
intent.setAction("cn.edu.zafu.component.ComponentBroadcastDynamic");
sendBroadcast(intent);
}
});
findViewById(R.id.unregister_broadcast).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.e("TAG", "ComponentBroadcastDynamic unregisterReceiver");
try{
unregisterReceiver(componentBroadcastDynamic);
}catch (Exception e){
Log.e("TAG", "ComponentBroadcastDynamic has already unregister");
}
}
});
findViewById(R.id.contentprovider_insert).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String authorities = "cn.edu.zafu.component.ComponentContentProvider";
Uri CONTENT_URI = Uri.parse("content://" + authorities + "/test");
ContentValues values = new ContentValues();
values.put("title", "title11111111");
Uri uri =MainActivity.this.getContentResolver().insert(CONTENT_URI, values);
}
});
findViewById(R.id.contentprovider_delete).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String authorities = "cn.edu.zafu.component.ComponentContentProvider";
Uri CONTENT_URI = Uri.parse("content://" + authorities + "/test");
int result =MainActivity.this.getContentResolver().delete(CONTENT_URI, "title=?", new String[]{"title11111111"});
Log.e("TAG","====="+result);
}
});
findViewById(R.id.contentprovider_update).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String authorities = "cn.edu.zafu.component.ComponentContentProvider";
Uri CONTENT_URI = Uri.parse("content://" + authorities + "/test");
ContentValues values = new ContentValues();
values.put("title", "title11111111");
int result =MainActivity.this.getContentResolver().update(CONTENT_URI, values, "id=?", new String[]{"1"});
Log.e("TAG", "=====" + result);
}
});
findViewById(R.id.contentprovider_query).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.e("TAG","no impl");
}
});
}
}
按照这篇文章 Android插件化开发之OpenAtlas插件适配 进行适配工作。在build.gradle中加入flavors,让其同时支持单独运行以及插件运行。
productFlavors {
alone{
}
openatlas {
versionName "1.00x23"
}
}
这时候我们选择脱离插件模式运行,如图
运行成功后,我们从上到下依次点击按钮,
运行后查看日志输出。
可以看到单独运行的时候所有功能都能正常运行。
这时候我们要将其移植到宿主中,第一步就是将清单文件中的声明复制到宿主中,并做适当修改。
<activity
android:name="cn.edu.zafu.component.MainActivity"
>
</activity>
<service
android:name="cn.edu.zafu.component.ComponentService"
android:exported="false">
</service>
<receiver android:name="cn.edu.zafu.component.ComponentBroadcast">
<intent-filter>
<action
android:name="cn.edu.zafu.component"/>
</intent-filter>
</receiver>
<provider
android:name="cn.edu.zafu.atlasdemo.provider.ComponentProviderBridge"
android:authorities="cn.edu.zafu.component.ComponentContentProvider"/>
从以上声明可以看出,我们的provider的声明发生了变化,其实原因也很简单,这里我们做了一个桥。
而Provider需要做一些特殊处理,因为ContentProvider在Application onCreate之前初始化,因此做一个桥,告诉系统这个ContentProvider初始化完毕,都可以用了,实际上还没完成,只是一个空实现,当需要的类能加载的时候对正常的类进行实例化。
那么这个桥的实现是怎么样的呢,也很简单,继承ProviderProxy类,调用父类构造函数即可。调用构造函数传递的参数为真正的Provider的全类名。也就是原来插件的清单文件中的类名。
public class ComponentProviderBridge extends ProviderProxy{
public ComponentProviderBridge() {
super("cn.edu.zafu.component.ComponentContentProvider");
}
}
而宿主中的清单文件的类名对应变为桥的全类名。完成了以上工作后就可以将build方式修改为openAtlas的了。如图。
生成插件后还要生成插件信息列表,按照之前的文章进行生成即可。
然后在宿主中调用插件的入口Activity
findViewById(R.id.component).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent();
intent.setClassName(MainActivity.this, "cn.edu.zafu.component.MainActivity");
startActivity(intent);
}
});
运行后点击对应按钮进入插件,并按之前的操作那样,从上到下依次点击所有按钮测试功能。查看日志输出。
我们会发现和单独运行完全一样。
最后呢,还有一个细节问题需要自己注意一下,如果你在宿主中单独使用反射调用插件中的Fragment,如果不经过插件的四大组件,比如Activity,那么插件的Application将不会被调用,这里提供一种解决方法,就是手动进行Bundle的启动,在反射前进行调用,代码如下
BundleImpl bundle = (BundleImpl)Atlas.getInstance().getBundle("xxxxxxxx");
bundle.startBundle();
最后上源码