最近在工作中接觸到了Android插件內(nèi)的開發(fā),發(fā)現(xiàn)自己這種技術(shù)還缺乏最基本的了解尊惰,以至于在一些基本問題上浪費(fèi)不少時(shí)間饲嗽,如插件Context和主工程Context的區(qū)別票罐,權(quán)限必須在主工程申明等,因此花了點(diǎn)時(shí)間了解了一下插件的歷史暮胧,并寫了兩個(gè)Demo作為總結(jié)锐借。本文旨在通過兩個(gè)實(shí)例直觀的說明插件的實(shí)現(xiàn)原理以加深對插件內(nèi)開發(fā)的理解问麸,因此不會深入探討背景和原理,代碼也盡量專注于核心邏輯钞翔。
原理與背景
Android插件化從技術(shù)上來說就是如何啟動未安裝的apk(主要是四大組件)里面的類口叙,主要問題涉及如何加載類、如何加載資源嗅战、如何管理組件生命周期妄田。
類加載
Android對于外部的dex文件,主要通過DexClassLoader
類加載驮捍,因此疟呐,只需要給定插件的路徑,就可以構(gòu)造對應(yīng)的類加載器:
private DexClassLoader createDexClassLoader(String apkPath) {
File dexOutputDir = mContext.getDir("dex", Context.MODE_PRIVATE);
DexClassLoader loader = new DexClassLoader(apkPath, dexOutputDir.getAbsolutePath(),
null, mContext.getClassLoader());
return loader;
}
資源加載
Android系統(tǒng)通過Resource對象加載資源,因此只需要添加資源(即apk文件)所在路徑到AssetManager
中东且,即可實(shí)現(xiàn)對插件資源的訪問。
AssetManager assetManager = AssetManager.class.newInstance();
Method addAssetPath = assetManager.getClass().getMethod(addAssetPathMethod, String.class);
addAssetPath.invoke(assetManager, apkPath);
Resources pluginRes = new Resources(assetManager,
mContext.getResources().getDisplayMetrics(),
mContext.getResources().getConfiguration());
pluginApk = new PluginApk(pluginRes);
pluginApk.classLoader = createDexClassLoader(apkPath);
生命周期
插件化中較為復(fù)雜的是對生命周期的管理珊泳,其中以Activity最為復(fù)雜鲁冯。早期的dynamic-load-apk采用的是代理的方式,通過一個(gè)空殼Activity作為代理(Proxy)色查,系統(tǒng)對該Activity的回調(diào)都會映射到插件Activity薯演,如此便可以實(shí)現(xiàn)通過系統(tǒng)來管理插件的生命周期。這種方式十分直觀秧了,但是需要所有的插件Activity都繼承這個(gè)用作代理的PluginActivity
(Demo中的命名)跨扮,侵入性強(qiáng),可結(jié)合后面的例子加深理解验毡。因此衡创,如何避免這種侵入性成了第二代插件化框架的目標(biāo),VirtualApk通過Hook少量系統(tǒng)類達(dá)到了這個(gè)目標(biāo)晶通,插件的開發(fā)和普通工程無異璃氢,接入成本極低。
了解了這些原理往往還不夠狮辽,知識往往需要經(jīng)過推導(dǎo)和實(shí)踐才能變成自己的一也,因此,接下來我們結(jié)合這些原理來實(shí)現(xiàn)一個(gè)插件化框架隘竭,不考慮兼容性和健壯性塘秦,純粹來實(shí)踐上面提及的原理。
代理實(shí)現(xiàn)
首先建立一個(gè)PluginManager
類來實(shí)現(xiàn)插件的加載:
public class PluginManager {
static class PluginMgrHolder {
static PluginManager sManager = new PluginManager();
}
private static Context mContext;
Map<String, PluginApk> sMap = new HashMap<>();
public static PluginManager getInstance() {
return PluginMgrHolder.sManager;
}
public PluginApk getPluginApk(String packageName) {
return sMap.get(packageName);
}
public static void init(Context context) {
mContext = context.getApplicationContext();
}
public final void loadApk(String apkPath) {
PackageInfo packageInfo = queryPackageInfo(apkPath);
if (packageInfo == null || TextUtils.isEmpty(packageInfo.packageName)) {
return;
}
// check cache
PluginApk pluginApk = sMap.get(packageInfo.packageName);
if (pluginApk == null) {
pluginApk = createApk(apkPath);
if (pluginApk != null) {
pluginApk.packageInfo = packageInfo;
sMap.put(packageInfo.packageName, pluginApk);
} else {
throw new NullPointerException("PluginApk is null");
}
}
}
private PluginApk createApk(String apkPath) {
String addAssetPathMethod = "addAssetPath";
PluginApk pluginApk = null;
try {
AssetManager assetManager = AssetManager.class.newInstance();
Method addAssetPath = assetManager.getClass().getMethod(addAssetPathMethod, String.class);
addAssetPath.invoke(assetManager, apkPath);
Resources pluginRes = new Resources(assetManager,
mContext.getResources().getDisplayMetrics(),
mContext.getResources().getConfiguration());
pluginApk = new PluginApk(pluginRes);
pluginApk.classLoader = createDexClassLoader(apkPath);
} catch (IllegalAccessException
| InstantiationException
| NoSuchMethodException
| InvocationTargetException e) {
e.printStackTrace();
}
return pluginApk;
}
private PackageInfo queryPackageInfo(String apkPath) {
PackageInfo packageInfo = mContext.getPackageManager().getPackageArchiveInfo(apkPath,
PackageManager.GET_ACTIVITIES | PackageManager.GET_SERVICES);
if (packageInfo == null) {
return null;
}
return packageInfo;
}
private DexClassLoader createDexClassLoader(String apkPath) {
File dexOutputDir = mContext.getDir("dex", Context.MODE_PRIVATE);
DexClassLoader loader = new DexClassLoader(apkPath, dexOutputDir.getAbsolutePath(),
null, mContext.getClassLoader());
return loader;
}
public void startActivity(Intent intent) {
Intent pluginIntent = new Intent(mContext, ProxyActivity.class);
Bundle extra = intent.getExtras();
// complicate if statement
if (extra == null || !extra.containsKey(Constants.PLUGIN_CLASS_NAME) && !extra.containsKey(Constants.PACKAGE_NAME)) {
try {
throw new IllegalAccessException("lack class of plugin and package name");
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
pluginIntent.putExtras(intent);
pluginIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mContext.startActivity(pluginIntent);
}
}
PluginApk
表示一個(gè)Apk文件:
public class PluginApk {
public PackageInfo packageInfo;
public DexClassLoader classLoader;
public Resources pluginRes;
public PluginApk(Resources pluginRes) {
this.pluginRes = pluginRes;
}
}
所有插件Activity都要繼承一個(gè)父類PluginActivity
:
public abstract class PluginActivity extends Activity implements Pluginable, Attachable<Activity> {
public final static String TAG = PluginActivity.class.getSimpleName();
protected Activity mProxyActivity;
private Resources mResources;
PluginApk mPluginApk;
@Override
public void attach(Activity proxy, PluginApk apk) {
mProxyActivity = proxy;
mPluginApk = apk;
mResources = apk.pluginRes;
}
@Override
public void setContentView(int layoutResID) {
mProxyActivity.setContentView(layoutResID);
}
@Override
public void setContentView(View view) {
mProxyActivity.setContentView(view);
}
@Override
public void setContentView(View view, ViewGroup.LayoutParams params) {
mProxyActivity.setContentView(view, params);
}
@Override
public View findViewById(int id) {
return mProxyActivity.findViewById(id);
}
@Override
public Resources getResources() {
return mResources;
}
@Override
public WindowManager getWindowManager() {
return mProxyActivity.getWindowManager();
}
@Override
public ClassLoader getClassLoader() {
return mProxyActivity.getClassLoader();
}
@Override
public Context getApplicationContext() {
return mProxyActivity.getApplicationContext();
}
@Override
public MenuInflater getMenuInflater() {
return mProxyActivity.getMenuInflater();
}
@Override
public Window getWindow() {
return mProxyActivity.getWindow();
}
@Override
public Intent getIntent() {
return mProxyActivity.getIntent();
}
@Override
public LayoutInflater getLayoutInflater() {
return mProxyActivity.getLayoutInflater();
}
@Override
public String getPackageName() {
return mPluginApk.packageInfo.packageName;
}
@Override
public void onCreate(Bundle bundle) {
// DO NOT CALL super.onCreate(bundle)
// following same
VLog.log(TAG + ": onCreate");
}
@Override
public void onStart() {
}
@Override
public void onResume() {
}
@Override
public void onStop() {
}
@Override
public void onPause() {
}
@Override
public void onDestroy() {
}
}
這個(gè)類只是一個(gè)殼动看,系統(tǒng)會通過ProxyActivity
觸發(fā)對應(yīng)的方法的具體實(shí)現(xiàn):
public class ProxyActivity extends Activity {
LifeCircleController mPluginController = new LifeCircleController(this);
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mPluginController.onCreate(getIntent().getExtras());
}
@Override
public Resources getResources() {
// construct when loading apk
Resources resources = mPluginController.getResources();
return resources == null ? super.getResources() : resources;
}
@Override
public Resources.Theme getTheme() {
Resources.Theme theme = mPluginController.getTheme();
return theme == null ? super.getTheme() : theme;
}
@Override
public AssetManager getAssets() {
return mPluginController.getAssets();
}
@Override
protected void onStart() {
super.onStart();
mPluginController.onStart();
}
@Override
protected void onResume() {
super.onResume();
mPluginController.onResume();
}
@Override
protected void onStop() {
super.onStop();
mPluginController.onStop();
}
@Override
protected void onPause() {
super.onPause();
mPluginController.onPause();
}
@Override
protected void onDestroy() {
super.onDestroy();
mPluginController.onDestroy();
}
}
這個(gè)類是系統(tǒng)實(shí)際啟動的類尊剔,其主要邏輯由LifeCircleController
負(fù)責(zé):
public class LifeCircleController implements Pluginable {
Activity mProxy;
PluginActivity mPlugin;
Resources mResources;
Resources.Theme mTheme;
PluginApk mPluginApk;
String mPluginClazz;
public LifeCircleController(Activity activity) {
mProxy = activity;
}
public void onCreate(Bundle bundle) {
mPluginClazz = bundle.getString(Constants.PLUGIN_CLASS_NAME);
String packageName = bundle.getString(Constants.PACKAGE_NAME);
mPluginApk = PluginManager.getInstance().getPluginApk(packageName);
try {
mPlugin = (PluginActivity) loadPluginable(mPluginApk.classLoader, mPluginClazz);
mPlugin.attach(mProxy, mPluginApk);
mResources = mPluginApk.pluginRes;
mPlugin.onCreate(bundle);
} catch (Exception e) {
VLog.log("Fail in LifeCircleController onCreate");
VLog.log(e.getMessage());
e.printStackTrace();
}
}
private Object loadPluginable(ClassLoader classLoader, String pluginActivityClass)
throws Exception {
Class<?> pluginClz = classLoader.loadClass(pluginActivityClass);
Constructor<?> constructor = pluginClz.getConstructor(new Class[] {});
constructor.setAccessible(true);
return constructor.newInstance(new Object[] {});
}
@Override
public void onStart() {
if (mPlugin != null) {
mPlugin.onStart();
}
}
@Override
public void onResume() {
if (mPlugin != null) {
mPlugin.onResume();
}
}
@Override
public void onStop() {
mPlugin.onStop();
}
@Override
public void onPause() {
mPlugin.onPause();
}
@Override
public void onDestroy() {
mPlugin.onDestroy();
}
public Resources getResources() {
return mResources;
}
public Resources.Theme getTheme() {
return mTheme;
}
public AssetManager getAssets() {
return mResources.getAssets();
}
}
有點(diǎn)像Activity源碼的外觀模式,內(nèi)部的分工和職責(zé)劃分對于使用者是不可見的菱皆。
最后在主工程啟動插件:
Intent intent = new Intent();
intent.putExtra(Constants.PACKAGE_NAME, PLUGIN_PACKAGE_NAME);
intent.putExtra(Constants.PLUGIN_CLASS_NAME, PLUGIN_CLAZZ_NAME);
mPluginManager.startActivity(intent);
插件Activity如下:
public class MainActivity extends PluginActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
setTitle("Plugin App");
((ImageView) findViewById(R.id.iv_logo)).setImageResource(R.drawable.android);
}
}
效果:
Hook實(shí)現(xiàn)
Hook的方式需要基本了解系統(tǒng)啟動一個(gè)Activity的過程须误,一般來說系統(tǒng)會先檢查Activity是否注冊挨稿,然后再去生成該Activity,那么我們只需要在檢查的時(shí)候用一個(gè)已經(jīng)注冊的Activity(樁京痢,通常表示為StubActivity)來給系統(tǒng)檢查奶甘,檢查通過后在生成的時(shí)候再替換成插件的就可以了。
首先要自己實(shí)現(xiàn)一個(gè)Instrumentation
祭椰,在里面做一些替換工作臭家,然后去Hook掉系統(tǒng)持有的對象:
public class HookedInstrumentation extends Instrumentation implements Handler.Callback {
public static final String TAG = "HookedInstrumentation";
protected Instrumentation mBase;
private PluginManager mPluginManager;
public HookedInstrumentation(Instrumentation base, PluginManager pluginManager) {
mBase = base;
mPluginManager = pluginManager;
}
/**
* 覆蓋掉原始Instrumentation類的對應(yīng)方法,用于插件內(nèi)部跳轉(zhuǎn)Activity時(shí)適配
*
* @Override
*/
public ActivityResult execStartActivity(
Context who, IBinder contextThread, IBinder token, Activity target,
Intent intent, int requestCode, Bundle options) {
if (Constants.DEBUG) Log.e(TAG, "execStartActivity");
mPluginManager.hookToStubActivity(intent);
try {
Method execStartActivity = Instrumentation.class.getDeclaredMethod(
"execStartActivity", Context.class, IBinder.class, IBinder.class,
Activity.class, Intent.class, int.class, Bundle.class);
execStartActivity.setAccessible(true);
return (ActivityResult) execStartActivity.invoke(mBase, who,
contextThread, token, target, intent, requestCode, options);
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("do not support!!!" + e.getMessage());
}
}
@Override
public Activity newActivity(ClassLoader cl, String className, Intent intent) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
if (Constants.DEBUG) Log.e(TAG, "newActivity");
if (mPluginManager.hookToPluginActivity(intent)) {
String targetClassName = intent.getComponent().getClassName();
PluginApp pluginApp = mPluginManager.getLoadedPluginApk();
Activity activity = mBase.newActivity(pluginApp.mClassLoader, targetClassName, intent);
activity.setIntent(intent);
ReflectUtil.setField(ContextThemeWrapper.class, activity, Constants.FIELD_RESOURCES, pluginApp.mResources);
return activity;
}
if (Constants.DEBUG) Log.e(TAG, "super.newActivity(...)");
return super.newActivity(cl, className, intent);
}
@Override
public boolean handleMessage(Message message) {
if (Constants.DEBUG) Log.e(TAG, "handleMessage");
return false;
}
@Override
public void callActivityOnCreate(Activity activity, Bundle icicle) {
if (Constants.DEBUG) Log.e(TAG, "callActivityOnCreate");
super.callActivityOnCreate(activity, icicle);
}
}
在負(fù)責(zé)啟動的execStartActivity
設(shè)置為啟動已注冊的Activity,再在newActivity
設(shè)置為實(shí)際要啟動的插件的Activity方淤。然后去Hook系統(tǒng)持有的該字段:
public class ReflectUtil {
public static final String METHOD_currentActivityThread = "currentActivityThread";
public static final String CLASS_ActivityThread = "android.app.ActivityThread";
public static final String FIELD_mInstrumentation = "mInstrumentation";
public static final String TAG = "ReflectUtil";
private static Instrumentation sInstrumentation;
private static Instrumentation sActivityInstrumentation;
private static Field sActivityThreadInstrumentationField;
private static Field sActivityInstrumentationField;
private static Object sActivityThread;
public static boolean init() {
//獲取當(dāng)前的ActivityThread對象
Class<?> activityThreadClass = null;
try {
activityThreadClass = Class.forName(CLASS_ActivityThread);
Method currentActivityThreadMethod = activityThreadClass.getDeclaredMethod(METHOD_currentActivityThread);
currentActivityThreadMethod.setAccessible(true);
Object currentActivityThread = currentActivityThreadMethod.invoke(null);
//拿到在ActivityThread類里面的原始mInstrumentation對象
Field instrumentationField = activityThreadClass.getDeclaredField(FIELD_mInstrumentation);
instrumentationField.setAccessible(true);
sActivityThreadInstrumentationField = instrumentationField;
sInstrumentation = (Instrumentation) instrumentationField.get(currentActivityThread);
sActivityThread = currentActivityThread;
sActivityInstrumentationField = Activity.class.getDeclaredField(FIELD_mInstrumentation);
sActivityInstrumentationField.setAccessible(true);
return true;
} catch (ClassNotFoundException
| NoSuchMethodException
| IllegalAccessException
| InvocationTargetException
| NoSuchFieldException e) {
e.printStackTrace();
}
return false;
}
public static Instrumentation getInstrumentation() {
return sInstrumentation;
}
public static Object getActivityThread() {
return sActivityThread;
}
public static void setInstrumentation(Object activityThread, HookedInstrumentation hookedInstrumentation) {
try {
sActivityThreadInstrumentationField.set(activityThread, hookedInstrumentation);
if (Constants.DEBUG) Log.e(TAG, "set hooked instrumentation");
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
public static void setActivityInstrumentation(Activity activity, PluginManager manager) {
try {
sActivityInstrumentation = (Instrumentation) sActivityInstrumentationField.get(activity);
HookedInstrumentation instrumentation = new HookedInstrumentation(sActivityInstrumentation, manager);
sActivityInstrumentationField.set(activity, instrumentation);
if (Constants.DEBUG) Log.e(TAG, "set activity hooked instrumentation");
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
public static void setField(Class clazz, Object target, String field, Object object) {
try {
Field f = clazz.getDeclaredField(field);
f.setAccessible(true);
f.set(target, object);
} catch (Exception e) {
e.printStackTrace();
}
}
}
PluginManager
同樣負(fù)責(zé)加載插件的類和資源等:
public class PluginManager {
private final static String TAG = "PluginManager";
private static PluginManager sInstance;
private Context mContext;
private PluginApp mPluginApp;
public static PluginManager getInstance(Context context) {
if (sInstance == null && context != null) {
sInstance = new PluginManager(context);
}
return sInstance;
}
private PluginManager(Context context) {
mContext = context;
}
public void hookInstrumentation() {
try {
Instrumentation baseInstrumentation = ReflectUtil.getInstrumentation();
final HookedInstrumentation instrumentation = new HookedInstrumentation(baseInstrumentation, this);
Object activityThread = ReflectUtil.getActivityThread();
ReflectUtil.setInstrumentation(activityThread, instrumentation);
} catch (Exception e) {
e.printStackTrace();
}
}
public void hookCurrentActivityInstrumentation(Activity activity) {
ReflectUtil.setActivityInstrumentation(activity, sInstance);
}
public void hookToStubActivity(Intent intent) {
if (Constants.DEBUG) Log.e(TAG, "hookToStubActivity");
if (intent == null || intent.getComponent() == null) {
return;
}
String targetPackageName = intent.getComponent().getPackageName();
String targetClassName = intent.getComponent().getClassName();
if (mContext != null
&& !mContext.getPackageName().equals(targetPackageName)
&& isPluginLoaded(targetPackageName)) {
if (Constants.DEBUG) Log.e(TAG, "hook " + targetClassName + " to " + Constants.STUB_ACTIVITY);
intent.setClassName(Constants.STUB_PACKAGE, Constants.STUB_ACTIVITY);
intent.putExtra(Constants.KEY_IS_PLUGIN, true);
intent.putExtra(Constants.KEY_PACKAGE, targetPackageName);
intent.putExtra(Constants.KEY_ACTIVITY, targetClassName);
}
}
public boolean hookToPluginActivity(Intent intent) {
if (Constants.DEBUG) Log.e(TAG, "hookToPluginActivity");
if (intent.getBooleanExtra(Constants.KEY_IS_PLUGIN, false)) {
String pkg = intent.getStringExtra(Constants.KEY_PACKAGE);
String activity = intent.getStringExtra(Constants.KEY_ACTIVITY);
if (Constants.DEBUG) Log.e(TAG, "hook " + intent.getComponent().getClassName() + " to " + activity);
intent.setClassName(pkg, activity);
return true;
}
return false;
}
private boolean isPluginLoaded(String packageName) {
// TODO 檢查packageNmae是否匹配
return mPluginApp != null;
}
public PluginApp loadPluginApk(String apkPath) {
String addAssetPathMethod = "addAssetPath";
PluginApp pluginApp = null;
try {
AssetManager assetManager = AssetManager.class.newInstance();
Method addAssetPath = assetManager.getClass().getMethod(addAssetPathMethod, String.class);
addAssetPath.invoke(assetManager, apkPath);
Resources pluginRes = new Resources(assetManager,
mContext.getResources().getDisplayMetrics(),
mContext.getResources().getConfiguration());
pluginApp = new PluginApp(pluginRes);
pluginApp.mClassLoader = createDexClassLoader(apkPath);
} catch (IllegalAccessException
| InstantiationException
| NoSuchMethodException
| InvocationTargetException e) {
e.printStackTrace();
}
return pluginApp;
}
private DexClassLoader createDexClassLoader(String apkPath) {
File dexOutputDir = mContext.getDir("dex", Context.MODE_PRIVATE);
return new DexClassLoader(apkPath, dexOutputDir.getAbsolutePath(),
null, mContext.getClassLoader());
}
public boolean loadPlugin(String apkPath) {
File apk = new File(apkPath);
if (!apk.exists()) {
return false;
}
mPluginApp = loadPluginApk(apkPath);
return mPluginApp != null;
}
public PluginApp getLoadedPluginApk() {
return mPluginApp;
}
}
在MainActivity中初始化钉赁,注意Hook的時(shí)機(jī):
public class MainActivity extends Activity implements View.OnClickListener {
// https://zhuanlan.zhihu.com/p/33017826
public static final boolean DEBUG = true;
public static final String TAG = "MainActivity";
private String mPluginPackageName = "top.vimerzhao.image";
private String mPluginClassName = "top.vimerzhao.image.MainActivity";
//讀寫權(quán)限
private static String[] PERMISSIONS_STORAGE = {Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE};
//請求狀態(tài)碼
private static int REQUEST_PERMISSION_CODE = 1;
private PluginManager mPluginManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initData();
initView();
initPlugin();
}
private void initPlugin() {
// !! must first
ReflectUtil.init();
mPluginManager = PluginManager.getInstance(getApplicationContext());
mPluginManager.hookInstrumentation();
mPluginManager.hookCurrentActivityInstrumentation(this);
}
private void initData() {
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP) {
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, PERMISSIONS_STORAGE, REQUEST_PERMISSION_CODE);
}
}
}
private void initView() {
(findViewById(R.id.tv_launch)).setOnClickListener(this);
}
@Override
protected void attachBaseContext(Context newBase) {
super.attachBaseContext(newBase);
// !!! 不要在此Hook,看源碼發(fā)現(xiàn)mInstrumentaion會在此方法后初始化
}
@Override
public void onClick(View view) {
if (Constants.DEBUG) Log.e(TAG, "click view id: " + view.getId());
if (view.getId() == R.id.tv_launch) {
// TODO launch plugin app
if (mPluginManager.loadPlugin(Constants.PLUGIN_PATH)) {
Intent intent = new Intent();
intent.setClassName(mPluginPackageName, mPluginClassName);
startActivity(intent);
}
}
}
}
現(xiàn)在插件Activity不會有任何限制:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
@Override
protected void onResume() {
super.onResume();
}
}
效果和上圖類似。
Demo地址
部分源碼無關(guān)核心邏輯携茂,沒有給到你踩,目錄結(jié)構(gòu)也沒有說明,詳見Demo源碼讳苦。
PluginDemo
總結(jié)
看著理論感覺似懂非懂带膜,實(shí)戰(zhàn)發(fā)現(xiàn)問題其實(shí)挺多的,尤其是Hook的時(shí)機(jī)鸳谜,照搬網(wǎng)上的文章發(fā)現(xiàn)根本不可行膝藕。插件化也不是一蹴而就的,而是在已有成果的基礎(chǔ)上一次一次的小創(chuàng)新積累起來的卿堂,跟著插件化發(fā)展的路徑自己動手實(shí)踐一遍還是能發(fā)現(xiàn)很多自己理解不夠深刻的地方的束莫。
以上懒棉。
參考
- 《Android源碼設(shè)計(jì)模式解析與實(shí)戰(zhàn)》
- 深入理解Android插件化技術(shù)