1.插件化定義
插件化,就是把一些核心復(fù)雜依賴度高的業(yè)務(wù)模塊封裝成插件,然后根據(jù)不同的業(yè)務(wù)進(jìn)行不同的組合,動(dòng)態(tài)進(jìn)行替換。
2.插件化跟組件化的差異
組件化:是將一個(gè)app分成多個(gè)模塊,每個(gè)模塊都是一個(gè)組件,開發(fā)過程中我們可以讓這些組件相互依賴或者單獨(dú)調(diào)試部分組件,但是最終發(fā)布的時(shí)候是將這些組件合并成一個(gè)統(tǒng)一的apk,這就是組件化。
插件化:跟組件化不同,插件化開發(fā)就是將整個(gè)app拆分成很多模塊,每個(gè)模塊都是apk(組件化每個(gè)模塊是一個(gè)lib),最終發(fā)布的時(shí)候宿主和插件apk分開打包,插件apk通過動(dòng)態(tài)下發(fā)的方式發(fā)送到宿主apk,這就是插件化。
3.插件化的優(yōu)勢(shì)
- 插件和宿主分開編譯,減少宿主apk的編譯時(shí)間
- 并發(fā)開發(fā),宿主和插件apk研發(fā)過程中可以獨(dú)立進(jìn)行,互不影響。
- 動(dòng)態(tài)更新插件,不需要安裝,下載之后就可以打開
- 可以解決65535問題
4.下面介紹插樁式插件化研發(fā)的流程
- 設(shè)計(jì)一套標(biāo)準(zhǔn),讓插件app能夠像安裝的app一樣運(yùn)行起來
- 根據(jù)標(biāo)準(zhǔn)編寫插件apk
- 加載插件apk
- 添加插樁點(diǎn)
編寫插件標(biāo)準(zhǔn),這個(gè)編寫一個(gè)獨(dú)立的model中,方便管理
public interface MSInterfaceActivity {
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();
}
根據(jù)這個(gè)標(biāo)準(zhǔn)編寫插件apk應(yīng)用的代碼,這個(gè)里邊包含兩個(gè)java文件一個(gè)是BaseActivity,一個(gè)是MainActivity
class BaseActivity extends AppCompatActivity implements MSInterfaceActivity {
protected Activity that;
@Override
public void attach(Activity proxyActivity) {
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);
}
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 void startActivity(Intent intent) {
Intent newIntent = new Intent();
newIntent.setClassName("className",intent.getComponent().getClassName());
that.startActivity(newIntent);
}
@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) {
}
}
插件的主頁面添加了一個(gè)ImageView,給控件設(shè)置了一個(gè)圖片并添加了一個(gè)點(diǎn)擊事件,點(diǎn)擊之后響應(yīng)Toast提示。
public class MainActivity extends BaseActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
View viewById = findViewById(R.id.image);
viewById.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Toast.makeText(that,"風(fēng)景",Toast.LENGTH_SHORT).show();
}
});
}
}
下面是宿主apk的內(nèi)容:
這里將插件apk放置在assets里邊,來模擬從網(wǎng)絡(luò)上下載下來的邏輯
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
添加文件的讀寫權(quán)限
public class PluginLoadManager {
private static final String TAG= PluginLoadManager.class.getSimpleName();
private static final PluginLoadManager ourInstance = new PluginLoadManager();
public static PluginLoadManager getInstance() {
return ourInstance;
}
private PackageInfo packageInfo;
private Resources resources;
//dex 類加載器
private DexClassLoader dexClassLoader;
private PluginLoadManager() {
}
/**
* 加載插件
* 加載插件的目的就是要得到Resource對(duì)象以及dexClassLoader
* @param context 上下文
* @param pluginPath plugin文件的絕對(duì)路徑
*/
public void loadPath(Context context,String pluginPath) {
// File filesDir = context.getDir("plugin", Context.MODE_PRIVATE);
Log.d(TAG,"pluginPath="+pluginPath);
PackageManager packageManager = context.getPackageManager();
//得到安裝包的信息
packageInfo = packageManager.getPackageArchiveInfo(pluginPath, PackageManager.GET_ACTIVITIES);
// activity 名字
File dexOutFile = context.getDir("dex", Context.MODE_PRIVATE);
//第一個(gè)參數(shù)是dex的路徑,第二個(gè)是優(yōu)化路徑 最后一個(gè)參數(shù)是父類加載器
dexClassLoader = new DexClassLoader(pluginPath,
dexOutFile.getAbsolutePath(), null, context.getClassLoader());
try {
//反射得到AssetManager
AssetManager assetManager = AssetManager.class.newInstance();
//方法名和參數(shù)類型 addAssetPath官方已經(jīng)不建議使用這個(gè)方法了,推薦使用setApkAssets
Method addAssetPath = AssetManager.class.getMethod("addAssetPath", String.class);
addAssetPath.invoke(assetManager,pluginPath); //方法在assetManager上面 需要傳遞一個(gè)路徑當(dāng)做參數(shù)
//創(chuàng)建一個(gè)Resource 第一個(gè)參數(shù)就是AssetManager 但是AssetManager的實(shí)例化被隱藏了,所以只能通過反射的方式得到
resources = new Resources(assetManager,
context.getResources().getDisplayMetrics(),
context.getResources().getConfiguration());
} catch (Exception e) {
e.printStackTrace();
}
}
public Resources getResources() {
return resources;
}
public DexClassLoader getDexClassLoader() {
return dexClassLoader;
}
public PackageInfo getPackageInfo() {
return packageInfo;
}
}
增加插件加載管理類,這個(gè)里邊的代碼邏輯我做了注釋,加載的過程中插件apk
- 通過getPackageArchiveInfo這個(gè)方法得到了安裝包的信息
- 通過DexClassLoader加載了插件apk的源碼,并對(duì)實(shí)例化DexClassLoader的參數(shù)做了介紹
- 通過反射得到了AssetManager,反射這個(gè)的目的是為了實(shí)例化Resources
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
requestPermission();
}
//申請(qǐng)文件讀寫權(quán)限
private void requestPermission() {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)!= PackageManager.PERMISSION_GRANTED) {
if (ActivityCompat.shouldShowRequestPermissionRationale(this,Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
Log.i(TAG,"用戶申請(qǐng)過權(quán)限,但是被拒絕了(不是徹底決絕)");
// 申請(qǐng)權(quán)限
ActivityCompat.requestPermissions(this,new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.READ_EXTERNAL_STORAGE},1);
} else {
Log.i(TAG,"申請(qǐng)過權(quán)限,但是被用戶徹底決絕了或是手機(jī)不允許有此權(quán)限(依然可以在此再申請(qǐng)權(quán)限)");
ActivityCompat.requestPermissions(this,new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.READ_EXTERNAL_STORAGE},1);
}
}
}
// 拷貝到
public void load(View view){
loadPlugin();
}
private void loadPlugin() {
File fileDir=this.getDir("plugin", Context.MODE_PRIVATE);
String name="plugindemo.apk";
String filePath=new File(fileDir, name).getAbsolutePath();
Log.d(TAG,filePath);
File file = new File(filePath);
if (file.exists()) {
file.delete();
}
InputStream is = null;
FileOutputStream os = null;
try {
//從assets里邊加載插件apk,模擬網(wǎng)絡(luò)下載
is = getAssets().open("plugindemo-debug.apk");
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, "插件下載完成", Toast.LENGTH_SHORT).show();
}
PluginLoadManager.getInstance().loadPath(this,filePath);
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
os.close();
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
//頁面跳轉(zhuǎn)
public void click(View view) {
Intent intent=new Intent(this,ProxyActivity.class);
intent.putExtra("className", PluginLoadManager.getInstance().getPackageInfo().activities[0].name);
startActivity(intent);
}
}
頁面里邊兩個(gè)按鈕,一個(gè)是加載插件一個(gè)是跳轉(zhuǎn)按鈕,頁面跳轉(zhuǎn)的部分通過className字段將真正需要加載的插件頁面全名通過參數(shù)進(jìn)行傳遞到插件頁面。
/**
* 代理Activity,這個(gè)Activity只是一個(gè)殼,插裝式插件化實(shí)現(xiàn)
*/
public class ProxyActivity extends AppCompatActivity {
//需要加載的類名
private String className;
private MSInterfaceActivity msInterfaceActivity;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
className = getIntent().getStringExtra("className");
try {
//得到Activity的Class
Class<?> activityClass = getClassLoader().loadClass(className);
//得到構(gòu)造方法
Constructor<?> constructor = activityClass.getConstructor(new Class[]{});
//得到這個(gè)類的對(duì)象
Object object = constructor.newInstance();
if (object instanceof MSInterfaceActivity){
msInterfaceActivity= (MSInterfaceActivity) object;
}
if (msInterfaceActivity!=null){
msInterfaceActivity.attach(this);
Bundle bundle=new Bundle();
msInterfaceActivity.onCreate(bundle);
}
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void startActivity(Intent intent) {
String className = intent.getStringExtra("className");
Intent newIntent=new Intent(this,ProxyActivity.class);
newIntent.setClassName("className",className);
super.startActivity(newIntent);
}
@Override
public ClassLoader getClassLoader() {
return PluginLoadManager.getInstance().getDexClassLoader();
}
@Override
public Resources getResources() {
return PluginLoadManager.getInstance().getResources();
}
@Override
protected void onStart() {
super.onStart();
msInterfaceActivity.onStart();
}
@Override
protected void onResume() {
super.onResume();
msInterfaceActivity.onResume();
}
@Override
protected void onPause() {
super.onPause();
msInterfaceActivity.onPause();
}
@Override
protected void onDestroy() {
super.onDestroy();
msInterfaceActivity.onDestroy();
}
}
這個(gè)是專門用來加載插件頁面的,通過解析得到的需要跳轉(zhuǎn)的className通過反射得到這個(gè)頁面的對(duì)象,并注入context以及調(diào)用onCreate方法。
運(yùn)行效果完美實(shí)現(xiàn),需要源碼學(xué)習(xí)留下你的郵箱。