組件化與插件化的區(qū)別:
組件化開發(fā)就是將一個(gè)app分成多個(gè)模塊驹饺,每個(gè)模塊都是一個(gè)組件(Module),開發(fā)的過(guò)程中我們可以讓這些組件相互依賴或者單獨(dú)調(diào)試部分組件等缴渊,但是最終發(fā)布的時(shí)候是將這些組件合并統(tǒng)一成一個(gè)apk赏壹,這就是組件化開發(fā)。
插件化開發(fā)和組件化開發(fā)略有不用衔沼,插件化開發(fā)時(shí)將整個(gè)app拆分成很多模塊蝌借,這些模塊包括一個(gè)宿主和多個(gè)插件,每個(gè)模塊都是一個(gè)apk(組件化的每個(gè)模塊是個(gè)lib)指蚁,最終打包的時(shí)候?qū)⑺拗鱝pk和插件apk分開打包菩佑。
插件化的幾種方式:插樁式、hook欣舵、按需加載擎鸠。
1.插樁式
大概步驟:
1.1 設(shè)計(jì)接納標(biāo)準(zhǔn)
1.2 開發(fā)插件時(shí)遵循這個(gè)標(biāo)準(zhǔn)
1.3 在宿主的清單文件用一個(gè)空的activity 插樁
1.4 反射獲得 插件的啟動(dòng)activity
1.5 加載
具體步驟:
設(shè)計(jì)插件的標(biāo)準(zhǔn)接口
創(chuàng)建一個(gè)lib module,創(chuàng)建一個(gè)接口,主要用來(lái)傳遞給插件Context和管理插件的生命周期。
activity接口
public interface PayInterfaceActivity {
//傳遞Context
public void attach(Activity proxyActivity);
/**
* 生命周期
* @param savedInstanceState
*/
public void onCreate(Bundle savedInstanceState);
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();
}
service接口
public interface PayInterfaceService {
public void onCreate();
public void onStart(Intent intent, int startId);
public int onStartCommand(Intent intent, int flags, int startId);
public void onDestroy();
public void onConfigurationChanged(Configuration newConfig);
public void onLowMemory();
public void onTrimMemory(int level);
public IBinder onBind(Intent intent);
public boolean onUnbind(Intent intent);
public void onRebind(Intent intent);
public void onTaskRemoved(Intent rootIntent);
//傳遞Context
public void attach(Service proxyService);
}
插件中
創(chuàng)建一個(gè)baseActivity 和baseServie 實(shí)現(xiàn)定一個(gè)的接口缘圈,接收context劣光,并重寫跟context有關(guān)的方法,插件中的Activity 和service都繼承各自的基類袜蚕。
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) {
that.setContentView(layoutResID);
}
@Override
public ComponentName startService(Intent service) {
Intent m = new Intent();
m.putExtra("serviceName", service.getComponent().getClassName());
return that.startService(m);
}
@Override
public View findViewById(int id) {
return that.findViewById(id);
}
@Override
public Intent getIntent() {
if(that!=null){
return that.getIntent();
}
return super.getIntent();
}
@Override
public ClassLoader getClassLoader() {
return that.getClassLoader();
}
@Override
public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
return that.registerReceiver(receiver, filter);
}
@Override
public void sendBroadcast(Intent intent) {
that.sendBroadcast(intent);
}
@Override
public void startActivity(Intent intent) {
// ProxyActivity --->className
Intent m = new Intent();
m.putExtra("className", intent.getComponent().getClassName());
that.startActivity(m);
}
@NonNull
@Override
public LayoutInflater getLayoutInflater() {
return that.getLayoutInflater();
}
@Override
public ApplicationInfo getApplicationInfo() {
return that.getApplicationInfo();
}
@Override
public Window getWindow() {
return that.getWindow();
}
@Override
public WindowManager getWindowManager() {
return that.getWindowManager();
}
@Override
public void onCreate(Bundle savedInstanceState) {
}
@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) {
}
}
public class BaseService extends Service implements PayInterfaceService {
private static final String TAG = "david";
private Service that;
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void attach(Service proxyService) {
this.that = proxyService;
}
@Override
public void onCreate() {
// TODO Auto-generated method stub
Log.d(TAG, TAG + " onCreate");
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
// TODO Auto-generated method stub
Log.d(TAG, TAG + " onStartCommand");
return Service.START_STICKY;
}
@Override
public void onDestroy() {
// TODO Auto-generated method stub
Log.d(TAG, TAG + " onDestroy");
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
// TODO Auto-generated method stub
Log.d(TAG, TAG + " onConfigurationChanged");
}
@Override
public void onLowMemory() {
// TODO Auto-generated method stub
Log.d(TAG, TAG + " onLowMemory");
}
@Override
public void onTrimMemory(int level) {
// TODO Auto-generated method stub
Log.d(TAG, TAG + " onTrimMemory");
}
@Override
public boolean onUnbind(Intent intent) {
// TODO Auto-generated method stub
Log.d(TAG, TAG + " onUnbind");
return false;
}
@Override
public void onRebind(Intent intent) {
// TODO Auto-generated method stub
Log.d(TAG, TAG + " onRebind");
}
@Override
public void onTaskRemoved(Intent rootIntent) {
// TODO Auto-generated method stub
Log.d(TAG, TAG + " onTaskRemoved");
}
}
宿主中
在清單文件AndroidManifest.xml中 插樁activity 和service及broadcastReveiver
<activity android:name=".ProxyActivity"/>
<service android:name=".ProxyService"/>
<receiver android:name=".ProxyBroadCast" />
創(chuàng)建一個(gè)PluginManager的單例類 管理插件方法,獲取插件的packageinfo,resources,dexClassLoader等,獲取Resources時(shí)要通過(guò)反射調(diào)用AssetManager中的addAssetPath方法將插件的路徑傳給assetmanager
public class PluginManager {
private PackageInfo packageInfo;
private Resources resources;
private Context context;
private DexClassLoader dexClassLoader;
private static final PluginManager ourInstance = new PluginManager();
public static PluginManager getInstance() {
return ourInstance;
}
private PluginManager() {
}
public void setContext(Context context) {
this.context = context;
}
public PackageInfo getPackageInfo() {
return packageInfo;
}
public void loadPath(Context context) {
File filesDir = context.getDir("plugin", Context.MODE_PRIVATE);
String name = "pluginb.apk";
String path = new File(filesDir, name).getAbsolutePath();
PackageManager packageManager=context.getPackageManager();
packageInfo=packageManager.getPackageArchiveInfo(path,PackageManager.GET_ACTIVITIES);
// activity 名字
File dexOutFile = context.getDir("dex", Context.MODE_PRIVATE);
dexClassLoader = new DexClassLoader(path, dexOutFile.getAbsolutePath()
, null, context.getClassLoader());
// resource
try {
AssetManager assetManager = AssetManager.class.newInstance();
Method addAssetPath=AssetManager.class.getMethod("addAssetPath", String.class);
addAssetPath.invoke(assetManager, path);
resources = new Resources(assetManager, context.getResources().getDisplayMetrics(), context.getResources().getConfiguration());
} catch ( Exception e) {
e.printStackTrace();
}
//解析app中清單文件中的broadcastReceiver
parseReceivers(context, path);
}
private void parseReceivers(Context context, String path) {
// Package對(duì)象
// PackageParser pp = new PackageParser();
// PackageParser.Package pkg = pp.parsePackage(scanFile, parseFlags);
try {
Class packageParserClass = Class.forName("android.content.pm.PackageParser");
Method parsePackageMethod = packageParserClass.getDeclaredMethod("parsePackage", File.class, int.class);
Object packageParser = packageParserClass.newInstance();
Object packageObj= parsePackageMethod.invoke(packageParser, new File(path), PackageManager.GET_ACTIVITIES);
Field receiverField=packageObj.getClass().getDeclaredField("receivers");
//拿到receivers 廣播集合 app存在多個(gè)廣播 集合 List<Activity> name ————》 ActivityInfo className
List receivers = (List) receiverField.get(packageObj);
Class<?> componentClass = Class.forName("android.content.pm.PackageParser$Component");
Field intentsField = componentClass.getDeclaredField("intents");
// 調(diào)用generateActivityInfo 方法, 把PackageParser.Activity 轉(zhuǎn)換成
Class<?> packageParser$ActivityClass = Class.forName("android.content.pm.PackageParser$Activity");
// generateActivityInfo方法
Class<?> packageUserStateClass = Class.forName("android.content.pm.PackageUserState");
Object defaltUserState= packageUserStateClass.newInstance();
Method generateReceiverInfo = packageParserClass.getDeclaredMethod("generateActivityInfo",
packageParser$ActivityClass, int.class, packageUserStateClass, int.class);
Class<?> userHandler = Class.forName("android.os.UserHandle");
Method getCallingUserIdMethod = userHandler.getDeclaredMethod("getCallingUserId");
int userId = (int) getCallingUserIdMethod.invoke(null);
for (Object activity : receivers) {
ActivityInfo info= (ActivityInfo) generateReceiverInfo.invoke(packageParser, activity,0, defaltUserState, userId);
BroadcastReceiver broadcastReceiver= (BroadcastReceiver) dexClassLoader.loadClass(info.name).newInstance();
List<? extends IntentFilter> intents= (List<? extends IntentFilter>) intentsField.get(activity);
for (IntentFilter intentFilter : intents) {
//動(dòng)態(tài)注冊(cè)廣播
context.registerReceiver(broadcastReceiver, intentFilter);
}
}
//generateActivityInfo
} catch (Exception e) {
e.printStackTrace();
}
}
public Resources getResources() {
return resources;
}
public DexClassLoader getDexClassLoader() {
return dexClassLoader;
}
}
在application 中提前加載插件绢涡,并將插件復(fù)制到app的私有目錄牲剃,
private void loadPlugin() {
File filesDir = this.getDir("plugin", Context.MODE_PRIVATE);
String name = "pluginb.apk";
String filePath = new File(filesDir, name).getAbsolutePath();
File file = new File(filePath);
if (file.exists()) {
file.delete();
}
InputStream is = null;
FileOutputStream os = null;
try {
Log.i(TAG, "加載插件 " + new File(Environment.getExternalStorageDirectory(), name).getAbsolutePath());
is = new FileInputStream(new File(Environment.getExternalStorageDirectory(), name));
os = new FileOutputStream(filePath);
int len = 0;
byte[] buffer = new byte[1024];
while ((len = is.read(buffer)) != -1) {
os.write(buffer, 0, len);
}
File f = new File(filePath);
if (f.exists()) {
Toast.makeText(this, "dex overwrite", Toast.LENGTH_SHORT).show();
}
PluginManager.getInstance().loadPath(this);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
try {
os.close();
is.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
從宿主跳轉(zhuǎn)到插件的activity時(shí),先跳轉(zhuǎn)到插樁的activity中并攜帶上實(shí)際要調(diào)轉(zhuǎn)的插件的activity類
Intent intent = new Intent(this, ProxyActivity.class);
intent.putExtra("className", PluginManager.getInstance().getPackageInfo().activities[0].name);
startActivity(intent);
在宿主中發(fā)送廣播
public void sendBroadCast(View view) {
Toast.makeText(getApplicationContext(), "我是宿主 插件插件!收到請(qǐng)回答!! 1", Toast.LENGTH_SHORT).show();
Intent intent = new Intent();
intent.setAction("xx.xxxx");
sendBroadcast(intent);
}
在插樁的activity中 解析實(shí)際要調(diào)轉(zhuǎn)的activity并反射獲取該類對(duì)象強(qiáng)轉(zhuǎn)為定義的接口,并將activity的生命周期在對(duì)應(yīng)的方法中調(diào)用傳遞給插件雄可。
public class ProxyActivity extends Activity {
// 需要加載插件的 類名
private String className;
PayInterfaceActivity payInterfaceActivity;
// com.dongnao.alvin.taopiaopiao.MainActivity
@Override
public void onCreate(@Nullable Bundle savedInstanceState ) {
super.onCreate(savedInstanceState );
className = getIntent().getStringExtra("className");
// class
try {
Class activityClass = getClassLoader().loadClass(className);
Constructor constructor = activityClass.getConstructor(new Class[]{});
Object instance= constructor.newInstance(new Object[]{});
// 轉(zhuǎn)為定義的接口
payInterfaceActivity = (PayInterfaceActivity) instance;
//傳遞給插件Context
payInterfaceActivity.attach(this);
Bundle bundle = new Bundle();
payInterfaceActivity.onCreate(bundle);
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void startActivity(Intent intent) {
//插件中實(shí)際要跳轉(zhuǎn)的activity凿傅,
String className1=intent.getStringExtra("className");
//跳轉(zhuǎn)到自己
Intent intent1 = new Intent(this, ProxyActivity.class);
intent1.putExtra("className", className1);
super.startActivity(intent1);
}
@Override
public ComponentName startService(Intent service) {
String serviceName = service.getStringExtra("serviceName");
Intent intent1 = new Intent(this, ProxyService.class);
intent1.putExtra("serviceName", serviceName);
return super.startService(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 onDestroy() {
super.onDestroy();
payInterfaceActivity.onDestroy();
}
@Override
protected void onPause() {
super.onPause();
payInterfaceActivity.onPause();
}
}
public class ProxyBroadCast extends BroadcastReceiver {
private String className;
PayInterfaceBroadcast payInterfaceBroadcast;
public ProxyBroadCast(String className,Context context) {
this.className = className;
try {
Class loadClass = PluginManager.getInstance().getDexClassLoader().loadClass(className);
Constructor<?> localConstructor =loadClass.getConstructor(new Class[] {});
Object instance = localConstructor.newInstance(new Object[] {});
payInterfaceBroadcast = (PayInterfaceBroadcast) instance;
payInterfaceBroadcast.attach(context);
} catch (Exception e) {
e.printStackTrace();
}
}
//class------> object--->payintenfaceBroadCast
@Override
public void onReceive(Context context, Intent intent) {
payInterfaceBroadcast.onReceive(context, intent);
}
}
插件中廣播的兩種接收方式
動(dòng)態(tài) 要實(shí)現(xiàn)定義的接口,不需要在清單文件注冊(cè)
public class MyReceiver extends BroadcastReceiver implements PayInterfaceBroadcast{
@Override
public void attach(Context context) {
Toast.makeText(context, "-----綁定上下文成功---->", Toast.LENGTH_SHORT).show();
}
@Override
public void onReceive(Context context, Intent intent) {
Toast.makeText(context, "-----插件收到廣播--->", Toast.LENGTH_SHORT).show();
Toast.makeText(context, "-----插件收到廣播1--->", Toast.LENGTH_SHORT).show();
Toast.makeText(context, "-----插件收到廣播2--->", Toast.LENGTH_SHORT).show();
Toast.makeText(context, "-----插件收到廣播3--->", Toast.LENGTH_SHORT).show();
}
}
靜態(tài) 不用實(shí)現(xiàn)定義的接口了,要在清單文件注冊(cè)
public class StaticReceiver extends BroadcastReceiver {
static final String ACTION = "com.xx.receivebrod.Receive1.PLUGIN_ACTION";
@Override
public void onReceive(Context context, Intent intent) {
Toast.makeText(context, "我是插件 收到宿主的消息 靜態(tài)注冊(cè)的廣播 收到宿主的消息----->", Toast.LENGTH_SHORT).show();
context.sendBroadcast(new Intent(ACTION));
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Toast.makeText(context, "休眠之后---->", Toast.LENGTH_SHORT).show();
}
}