前言
android技術(shù)特別成熟了窜管,熱修復(fù),組件化......等框架已經(jīng)層出不窮韧涨,如果還僅限于使用框架牍戚,技術(shù)永遠(yuǎn)很難得到成長(zhǎng),只有我們懂得他的原理虑粥,能夠娓娓道來如孝,能夠自己動(dòng)手的寫出來,技術(shù)才會(huì)越來越好娩贷,與其想著未來怎么辦第晰,不如把握現(xiàn)在。這一篇文章教大家手寫出插件化框架,插件化技術(shù)是Android工程師必備的技術(shù)之一茁瘦,我們要懂其思想罗岖,知其原理。
那么在 android中腹躁,什么是「 插件化 」桑包,顧名思義啊,就是把一些核心復(fù)雜依賴度高的業(yè)務(wù)模塊封裝成獨(dú)立的插件纺非,然后根據(jù)不同業(yè)務(wù)需求進(jìn)行不同組合哑了,動(dòng)態(tài)進(jìn)行替換,可對(duì)插件進(jìn)行管理烧颖、更新弱左,后期對(duì)插件也可進(jìn)行版本管理等操作。在插件化中有兩個(gè)概念需要講解下:
宿主:
所謂宿主炕淮,就是需要能提供運(yùn)行環(huán)境拆火,給資源調(diào)用提供上下文環(huán)境,一般也就是我們主 APK 涂圆,要運(yùn)行的應(yīng)用们镜,它作為應(yīng)用的主工程所在,實(shí)現(xiàn)了一套插件的加載和管理的框架润歉,插件都是依托于宿主的APK而存在的模狭。
插件:
插件可以想象成每個(gè)獨(dú)立的功能模塊封裝為一個(gè)小的 APK ,可以通過在線配置和更新實(shí)現(xiàn)插件 APK 在宿主 APK 中的上線和下線踩衩,以及動(dòng)態(tài)更新等功能嚼鹉。
那么為何要使用插件化技術(shù),它有何優(yōu)勢(shì)驱富,能給我們帶來什么樣<typo id="typo-522" data-origin="好處" ignoretag="true">好處</typo>锚赤,這里簡(jiǎn)單列舉了以下幾點(diǎn):
讓用戶不用重新安裝 APK 就能升級(jí)應(yīng)用功能,減少發(fā)版本頻率褐鸥,增加用戶體驗(yàn)线脚。 提供一種快速修復(fù)線上 BUG 和更新的能力。 按需加載不同的模塊晶疼,實(shí)現(xiàn)靈活的功能配置酒贬,減少服務(wù)器對(duì)舊版本接口兼容壓力。 模塊化翠霍、解耦合锭吨、并行開發(fā)、 65535 問題寒匙。
簡(jiǎn)單描述一下入門的知識(shí)
首先我們要知道插件化技術(shù)是屬于比較復(fù)雜一個(gè)領(lǐng)域零如,復(fù)雜點(diǎn)在于它涉及知識(shí)點(diǎn)廣泛躏将,不僅僅是上層做應(yīng)用架構(gòu)能力,還要求我們對(duì) Android 系統(tǒng)底層知識(shí)需要有一定的認(rèn)知考蕾,這里簡(jiǎn)單羅列了其中會(huì)涉及的知識(shí)點(diǎn):
然后這一篇文章我?guī)Т蠹襾硎煜ぜ皩?shí)現(xiàn)廣播插件祸憋,廣播主要分兩種,一種動(dòng)態(tài)廣播,一種靜態(tài)廣播。
插件中動(dòng)態(tài)廣播的實(shí)現(xiàn):
啟動(dòng)插件中的動(dòng)態(tài)廣播其實(shí)和啟動(dòng)Activity和Service是一樣的流程.
首先我們需要在 通用庫里面新建一個(gè)接口,插件中的廣播都要實(shí)現(xiàn)此接口
public interface PluginInterfaceBroadcast {
void attach(Context context);
void onReceive(Context context, Intent intent);
}
然后在BaseActivity中重寫registerReceiver 和sendBroadcast肖卧、unregisterReceiver,因?yàn)檫@兩個(gè)方法都用到了上下文(Context),我們需要用宿主(app)的Context來注冊(cè)和發(fā)送廣播.代碼如下:
@Override
public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
if (mActivity != null) {
return mActivity.registerReceiver(receiver, filter);
}
return super.registerReceiver(receiver, filter);
}
@Override
public void sendBroadcast(Intent intent) {
if (null != mActivity) {
mActivity.sendBroadcast(intent);
} else {
super.sendBroadcast(intent);
}
}
@Override
public void unregisterReceiver(BroadcastReceiver receiver) {
if (null != mActivity) {
mActivity.unregisterReceiver(receiver);
} else {
super.unregisterReceiver(receiver);
}
}
上述代碼其實(shí)就是,調(diào)用了宿主(app)的里面方法,就是啟動(dòng)了宿主定義好的第一個(gè)空殼的廣播,然后通過DexClassLoader反射插件中的廣播類,然后通過繼承的接口,來進(jìn)行方法的調(diào)用和參數(shù)的傳遞.
public class ProxyBroadcast extends BroadcastReceiver {
private String className;
private PluginInterfaceBroadcast bordcast;
private static final String TAG = "ProxyBroadcast";
public ProxyBroadcast(String name, Context context) {
this.className = name;
Class loadClass = null;
try {
loadClass = PluginManager.getInstance().getClassLoader().loadClass(className);
Constructor constructor = loadClass.getConstructor(new Class[]{});
bordcast = (PluginInterfaceBroadcast) constructor.newInstance(new Object[]{});
bordcast.attach(context);
} catch (Exception e) {
e.printStackTrace();
}
}
//class --- object --- p
@Override
public void onReceive(Context context, Intent intent) {
Log.e(TAG, "ProxyBroadcast onReceive: ");
if (bordcast != null) {
bordcast.onReceive(context, intent);
}
}
}
同理,在宿主方法中需要做一些處理,new ProxyBroadcast 然后注冊(cè)此廣播,實(shí)際上就是注冊(cè)了宿主的空殼的一個(gè)廣播:
private List<ProxyBroadcast> proxyBroadcastList = new ArrayList<>();
@Override
public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
//重寫真正注冊(cè)的是ProxyBroadcast 轉(zhuǎn)發(fā)
IntentFilter filter1 = new IntentFilter();
for (int i = 0; i < filter.countActions(); i++) {
filter1.addAction(filter.getAction(i));
Log.e(TAG, "sendBroadcast: 注冊(cè)插件的廣播 -> " + filter1.getAction(i));
}
ProxyBroadcast proxyBroadcast = new ProxyBroadcast(receiver.getClass().getName(), this);
proxyBroadcastList.add(proxyBroadcast);
return super.registerReceiver(proxyBroadcast, filter1);
}
@Override
public void unregisterReceiver(BroadcastReceiver receiver) {
if (proxyBroadcastList != null && proxyBroadcastList.size() > 0) {
for (ProxyBroadcast proxyBroadcast : proxyBroadcastList) {
if (proxyBroadcast.getClass().getName().equals(receiver.getClass().getName())) {
super.unregisterReceiver(proxyBroadcast);
}
}
} else {
super.unregisterReceiver(receiver);
}
}
我們來看一個(gè)插件中的廣播實(shí)現(xiàn),通過繼承PluginInterfaceBroadcast,宿主調(diào)用接口的方法:
public class MyReceive extends BroadcastReceiver implements PluginInterfaceBroadcast {
@Override
public void attach(Context context) {
Toast.makeText(context, "注冊(cè)廣播成功", Toast.LENGTH_SHORT).show();
}
@Override
public void onReceive(Context context, Intent intent) {
Toast.makeText(context, "接收廣播成功", Toast.LENGTH_SHORT).show();
}
}
這樣我們就實(shí)現(xiàn)了,插件動(dòng)態(tài)廣播的實(shí)現(xiàn),是不是<typo id="typo-4649" data-origin="的" ignoretag="true">的</typo>非常簡(jiǎn)單,幾乎沒有什么難點(diǎn),接下來我們來看插件靜態(tài)廣播如何來實(shí)現(xiàn)
插件靜態(tài)廣播實(shí)現(xiàn)原理:
我們知道任何插件都是沒有安裝到手機(jī)上的,靜態(tài)廣播是注冊(cè)在AndroidManifest中,那么,我們就不能通過上述那樣輕松的拿到類名進(jìn)行反射了,如下
<receiver android:name=".StaticBroadcastReceiver">
<intent-filter>
<action android:name="com.prim.plugin.a" />
</intent-filter>
</receiver>
需要通過PMS來解析xml,拿到在xml注冊(cè)的類名,和intent-filter,然后和上面講的動(dòng)態(tài)廣播一樣,宿主動(dòng)態(tài)注冊(cè)插件中的靜態(tài)廣播,以達(dá)到這樣的效果.實(shí)際上宿主就是做了動(dòng)態(tài)注冊(cè)靜態(tài)廣播,都是天馬行空的想象.
APK安裝時(shí)做了什么呢?
- -將apk文件復(fù)制到data/app目錄
- ---使用PackageManager的installPackage接口
- ---之后調(diào)用installPackageAsUser蚯窥。installPackageAsUser方法中主要完成兩件事情。
- ------是權(quán)限檢查
- ------是構(gòu)建InstallParams塞帐,然后發(fā)送INIT_COPY的msg,這個(gè)mHandler運(yùn)行在一個(gè)HandlerThread中,INIT_COPY主要是確保DefaultContainerService 已 bound,DefaultContainerService是一個(gè)應(yīng)用服務(wù)拦赠,具體負(fù)責(zé)實(shí)現(xiàn)APK等相關(guān)資源文件在內(nèi)部或外部存儲(chǔ)器上的存儲(chǔ)工作。而MCS_BOUND中則執(zhí)行 params.startCopy()
- ------HandlerParams.startCopy該方法中除了檢查重試次數(shù)外只是簡(jiǎn)單的調(diào)用了handleStartCopy()及handleReturnCode()方法葵姥。 -解析apk信息
- ---copy到data/app目錄的操作后,就到了 handleReturnCode,這個(gè)方法又跳轉(zhuǎn)到processPendingInstall()方法 ---這個(gè)方法有幾個(gè)關(guān)鍵步驟,一是installPackageLI(args, res);,這個(gè)方法具體執(zhí)行了解析package和后續(xù)操作,而再installPackageLI(args, res);執(zhí)行完畢 后會(huì)走到bm.restoreAtInstall(res.pkg.applicationInfo.packageName, token);,會(huì)調(diào)用backupservice的restoreAtInstall方法,而restoreAtInstall方法最 終又會(huì)調(diào)用PMS的finishPackageInstall()方法,完成安裝荷鼠。
- -dexopt操作
- -更新權(quán)限信息
- -完成安裝,發(fā)送廣播(Intent.ACTION_PACKAGE_ADDED)
apk安裝時(shí)并有做很多操作,那么它是如何真正的加載靜態(tài)廣播? 真正的加載廣播,是在系統(tǒng)啟動(dòng)時(shí)發(fā)生的, 系統(tǒng)啟動(dòng)就可以理解成 將所有app重新安裝一遍到系統(tǒng)中榔幸,會(huì)重復(fù)上述過程.
PMS安裝APK原理
為了方便閱讀,我已經(jīng)將PackageManagerService 和PackageParser的源碼放到了代碼中,結(jié)合起來閱讀本文更容易理解.
PMS 全稱(PackageManagerService) ,當(dāng)安裝apk時(shí)會(huì)調(diào)用PackageManagerService的main方法
public static final PackageManagerService main(Context context, Installer installer,
boolean factoryTest, boolean onlyCore) {
PackageManagerService m = new PackageManagerService(context, installer,
factoryTest, onlyCore);
ServiceManager.addService("package", m);
return m;
}
很顯然上述代碼,new PackageManagerService,我們接著看PackageManagerService的構(gòu)造方法.然后我們可以看到data app 這樣創(chuàng)建的目錄,著重關(guān)注mAppInstallDir,字面意思就是app的安裝路徑
public PackageManagerService(Context context, Installer installer,
boolean factoryTest, boolean onlyCore) {
.....
synchronized (mPackages) {
mHandlerThread = new ServiceThread(TAG,
Process.THREAD_PRIORITY_BACKGROUND, true /*allowIo*/);
mHandlerThread.start();
mHandler = new PackageHandler(mHandlerThread.getLooper());
Watchdog.getInstance().addThread(mHandler, WATCHDOG_TIMEOUT);
//TODO
File dataDir = Environment.getDataDirectory();
mAppDataDir = new File(dataDir, "data");
mAppInstallDir = new File(dataDir, "app");
mAppLib32InstallDir = new File(dataDir, "app-lib");
mAsecInternalPath = new File(dataDir, "app-asec").getPath();
mUserAppDataDir = new File(dataDir, "user");
mDrmAppPrivateInstallDir = new File(dataDir, "app-private");
......
if (!mOnlyCore) {
EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_DATA_SCAN_START,
SystemClock.uptimeMillis());
scanDirLI(mAppInstallDir, 0, scanFlags, 0);
scanDirLI(mDrmAppPrivateInstallDir, PackageParser.PARSE_FORWARD_LOCK,
scanFlags, 0);
.....
}
}
接著會(huì)調(diào)用scanDirLI去掃面apk文件,點(diǎn)擊去看一下它做了什么? 內(nèi)部有調(diào)用scanPackageLI方法.
private void scanDirLI(File dir, int parseFlags, int scanFlags, long currentTime) {
for (File file : files) {
final boolean isPackage = (isApkFile(file) || file.isDirectory())
&& !PackageInstallerService.isStageName(file.getName());
if (!isPackage) {
// Ignore entries which are not packages
continue;
}
try {
scanPackageLI(file, parseFlags | PackageParser.PARSE_MUST_BE_APK,
scanFlags, currentTime, null);
} catch (PackageManagerException e) {
Slog.w(TAG, "Failed to parse " + file + ": " + e.getMessage());
// Delete invalid userdata apps
if ((parseFlags & PackageParser.PARSE_IS_SYSTEM) == 0 &&
e.error == PackageManager.INSTALL_FAILED_INVALID_APK) {
logCriticalInfo(Log.WARN, "Deleting invalid package at " + file);
if (file.isDirectory()) {
FileUtils.deleteContents(file);
}
file.delete();
}
}
}
}
接著點(diǎn)scanPackageLI,從字面意思上看是解析包,我們看看它做了什么?返回了一個(gè)Package,這個(gè)Package是不是我們想要的呢?
private PackageParser.Package scanPackageLI(File scanFile, int parseFlags, int scanFlags,
long currentTime, UserHandle user) throws PackageManagerException {
....
final PackageParser.Package pkg;
try {
pkg = pp.parsePackage(scanFile, parseFlags);
} catch (PackageParserException e) {
throw PackageManagerException.from(e);
}
.....
}
看一下Package 里面有什么? 很顯然它是我們想要的Package存放的就是AndroidManifest.xml 注冊(cè)的四大組件.
public final static class Package {
....
public final ArrayList<Activity> activities = new ArrayList<Activity>(0);
public final ArrayList<Activity> receivers = new ArrayList<Activity>(0);
public final ArrayList<Provider> providers = new ArrayList<Provider>(0);
public final ArrayList<Service> services = new ArrayList<Service>(0);
......
}
注意這里的Activity不是四大組件的Activity而是PackageParser的一個(gè)內(nèi)部類,activity 與receivers 在 AndroidManifest里面屬性都差不多一樣,很顯然Google復(fù)用了Activity.
我們來看一下Activity里面存放了什么?
public final static class Activity extends Component<ActivityIntentInfo> {
public final ActivityInfo info;
public Activity(final ParseComponentArgs args, final ActivityInfo _info) {
super(args, _info);
info = _info;
info.applicationInfo = args.owner.applicationInfo;
}
public void setPackageName(String packageName) {
super.setPackageName(packageName);
info.packageName = packageName;
}
.......
}
Activity中只存放了ActivityInfo,繼續(xù)點(diǎn)進(jìn)去ActivityInfo中是否有我們想要的類名等信息
ActivityInfo extends ComponentInfo
ComponentInfo extends PackageItemInfo
/**
* Public name of this item. From the "android:name" attribute.
*/
public String name;
PackageItemInfo中找到了這樣的一個(gè)屬性,終于找到了我們想要的類名了.只要我們拿到ActivityInfo就可以拿到類名
<receiver android:name=".StaticBroadcastReceiver">
<intent-filter>
<action android:name="com.prim.plugin.a" />
</intent-filter>
</receiver>
但是還忽略了一點(diǎn),intent-filter 還沒有找到,從代碼中看
final static class Activity extends Component<ActivityIntentInfo>
public static class Component<II extends IntentInfo> {
public final Package owner;
public final ArrayList<II> intents;
public final String className;
public Bundle metaData;
}
ArrayList intents 看起來像是intent-filter,II泛型 --> IntentInfo
public static class IntentInfo extends IntentFilter {
public boolean hasDefault;
public int labelRes;
public CharSequence nonLocalizedLabel;
public int icon;
public int logo;
public int banner;
public int preferred;
}
可以看到IntentInfo 繼承 IntentFilter,找到了IntentFilter.
中途總結(jié)
從上述代碼中,我們可以知道通過,parsePackage解析包,得到Package,就可以拿到AndroidManifeast的信息.
PackageParser pp = new PackageParser();
final PackageParser.Package pkg;
//解析apk 得到pkg
pkg = pp.parsePackage(scanFile, parseFlags);
但是很不幸的是,Google將這個(gè)類寫成了@hide 隱藏的API那我們就只能通過反射去獲取了,感覺瞬間腦殼痛了.
沒有辦法嘞,我們就只能硬來咯
首先我們實(shí)例化PackageParser然后調(diào)用parsePackage得到Package
//反射獲取解析apk包的類
Class packageParserClass = Class.forName("android.content.pm.PackageParser");
//獲取方法
Method parsePackage = packageParserClass.getDeclaredMethod("parsePackage",
File.class, int.class);
//實(shí)例化PackageParser類
Object packageParser = packageParserClass.newInstance();
//Package 得到
Object packageObj = parsePackage.invoke(packageParser, new File(absolutePath), PackageManager.GET_ACTIVITIES);
//拿到注冊(cè)的靜態(tài)廣播
然后通過Package拿到xml注冊(cè)的靜態(tài)廣播
//拿到注冊(cè)的靜態(tài)廣播
Field receiversField = packageObj.getClass().getDeclaredField("receivers");
//獲取List<Activity>
List receivers = (List) receiversField.get(packageObj);
然后循環(huán)Activity,拿到類名和intent-fliter
//循環(huán)receivers
for (Object activity : receivers) {
//拿到ActivityInfo
ActivityInfo info = (ActivityInfo) generateReceiverInfo.invoke(packageParser, activity, 0, defaltUserState, userId);
//根據(jù)ActivityInfo允乐,拿到BroadCastReceiver
BroadcastReceiver broadcastReceiver = (BroadcastReceiver) classLoader.loadClass(info.name).newInstance();
//拿到intentFilter
List<? extends IntentFilter> intentFilters = (List<? extends IntentFilter>) intentsField.get(activity);
for (IntentFilter filter : intentFilters) {
//動(dòng)態(tài)注冊(cè)插件中的靜態(tài)廣播
context.registerReceiver(broadcastReceiver, filter);
}
}
這樣看起來,實(shí)現(xiàn)思路很簡(jiǎn)單啊,一臉的懵逼啊.
SystemService -> main
->scanDirLI 掃描apk的文件 --》scanPackageLI 解析apk PackageParser -> parsePackage get Package (一個(gè)apk對(duì)應(yīng)一個(gè)package)
-> parseBaseApk loadApkIntoAssetManager -> parseBaseApk -> parseBaseApplication -> parseActivity -> parseIntent
核心完整代碼如下:
/**
* 解析xml靜態(tài)注冊(cè)的廣播
*
* @param context
* @param absolutePath
*/
private void parserReceive(Context context, String absolutePath) {
try {
//反射獲取解析apk包的類
Class packageParserClass = Class.forName("android.content.pm.PackageParser");
//獲取方法
Method parsePackage = packageParserClass.getDeclaredMethod("parsePackage",
File.class, int.class);
//實(shí)例化PackageParser類
Object packageParser = packageParserClass.newInstance();
//Package 得到
Object packageObj = parsePackage.invoke(packageParser, new File(absolutePath), PackageManager.GET_ACTIVITIES);
//拿到注冊(cè)的靜態(tài)廣播
Field receiversField = packageObj.getClass().getDeclaredField("receivers");
//獲取List<Activity>
List receivers = (List) receiversField.get(packageObj);
//public final static class Activity extends Component<ActivityIntentInfo>
//獲取Component
Class<?> componentClass = Class.forName("android.content.pm.PackageParser$Component");
//獲取intents
Field intentsField = componentClass.getDeclaredField("intents");
//generatePackageInfo(PackageParser.Package p,
// int gids[], int flags, long firstInstallTime, long lastUpdateTime,
// HashSet<String> grantedPermissions, PackageUserState state, int userId)
// 調(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);
//獲取userID
Class<?> userHandler = Class.forName("android.os.UserHandle");
Method getCallingUserIdMethod = userHandler.getDeclaredMethod("getCallingUserId");
int userId = (int) getCallingUserIdMethod.invoke(null);
//循環(huán)receivers
for (Object activity : receivers) {
//拿到ActivityInfo
ActivityInfo info = (ActivityInfo) generateReceiverInfo.invoke(packageParser, activity, 0, defaltUserState, userId);
//根據(jù)ActivityInfo,拿到BroadCastReceiver
BroadcastReceiver broadcastReceiver = (BroadcastReceiver) classLoader.loadClass(info.name).newInstance();
//拿到intentFilter
List<? extends IntentFilter> intentFilters = (List<? extends IntentFilter>) intentsField.get(activity);
for (IntentFilter filter : intentFilters) {
//動(dòng)態(tài)注冊(cè)插件中的靜態(tài)廣播
context.registerReceiver(broadcastReceiver, filter);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
插件中的靜態(tài)廣播就不需要實(shí)現(xiàn)接口PluginInterfaceBroadcast
public class StaticBroadcastReceiver extends BroadcastReceiver {
private static final String ACTION = "com.prim.plugin.host";
@Override
public void onReceive(Context context, Intent intent) {
Toast.makeText(context, "我是插件削咆,收到發(fā)送的廣播,我將向宿主發(fā)送廣播", Toast.LENGTH_SHORT).show();
//接收到廣播牍疏,然后給宿主發(fā)送廣播
context.sendBroadcast(new Intent(ACTION));
}
}
最后
如果前面我說的還有點(diǎn)不理解,我在舉個(gè)例子:
(以下的靜A 表示靜態(tài)廣播接收器态辛,同理動(dòng)B麸澜。)
1、 靜A (優(yōu)先級(jí)1)
2奏黑、 動(dòng)B(優(yōu)先級(jí)1)
3、 靜C (優(yōu)先級(jí)2编矾,后掃描)
4熟史、靜D (優(yōu)先級(jí)2,先掃描)
5 窄俏、動(dòng)E (優(yōu)先級(jí)2蹂匹,先注冊(cè))
6、 動(dòng)F (優(yōu)先級(jí)2凹蜈,后注冊(cè))
當(dāng)來了一個(gè) 有序廣播限寞,接收順序如下:動(dòng)E > 動(dòng)F > 靜D > 靜C > 動(dòng)B > 靜A
當(dāng)來了一個(gè) 普通廣播,接收順序如下:動(dòng)E > 動(dòng)F > 動(dòng)B > 靜D > 靜C > 靜A
Android插件化的發(fā)展方向
Android插件化方向主要有2個(gè)方向:
- 1.結(jié)合組件化技術(shù)仰坦,成為一個(gè)大中型app的基礎(chǔ)框架履植。
- 2.完全模擬app運(yùn)行環(huán)境的沙盒系統(tǒng)。