大家好,我係蒼王。
以下是我這個系列的相關文章,有興趣可以參考一下,可以給個喜歡或者關注我的文章。
[Android]如何做一個崩潰率少於千分之三噶應用app--章節列表
Google爸爸,聽說要將一些外掛化hook系統的變數屬性禁用,Android P之後很可能將會不再有外掛化、熱更新、主題變換、資源加固等騷操作。試圖hook,你將會看到 NoSuchFieldException 或者 NoSuchMethodException 等錯誤提示。
可見文章Android P 呼叫隱藏API限制原理中對api隱藏說明
具體通過@hide的註釋讓屬性提示變數不存在。
這樣就會要求app上線前測試更加嚴謹,而不是在上線後通過各種修復替換功能等方式,每週發版的日子,將不會出現了,不停歇的加班。
RN技術其原理涉及到view的渲染,暫時並未受到波及。
現在國內,有繼續走RN的,各大廠有走類似小程式方向的快應用,都是使用js語法,寫web還能拯救一堆程式猿啊。
接下來說一下程式通訊,其實任何的程式通訊方式,都可以在元件化開發中使用。
Android中程式間通訊的方式
1.Aidl
2.Messenger
3.Content provider
4.Socket
5.檔案共享
前三個都是基於binder機制實現的。
本節想要介紹的是使用aidl做的程式通訊,單單使用aidl進行通訊其實並不難。原理也有很多文章介紹過,但是如何設計一個通用的aidl通訊架構,就需要考究了。
這裡介紹的是ModularizationArchitecture中使用的aidl的通訊架構。
這裡ModularizationArchitecture架構使用了aidl作為路由傳輸的實現。
1.每個需要通訊的module,都需要繼承MaProvider類,然後在BaseApplicationLogic啟動的時候註冊。
2.MaAction作為觸發的事件,繼承出來改寫其方法,invoke方法是事件實現。需要在MaProvider中註冊事件。
3.MaActionResult是事件結果回撥。
4.LocalRouter是當前程式呼叫MaAction中的invoke執行方法。
5.WideRouter是跨程式呼叫時使用,需要在MaApplication啟動的時候註冊將module中的的LocalRouterConnectService。
註冊提供內容
註冊程式路由資訊到廣域路由中
public class MyApplication extends MaApplication {
//註冊程式路由資訊到廣域路由
@Override
public void initializeAllProcessRouter() {
WideRouter.registerLocalRouter("com.spinytech.maindemo",MainRouterConnectService.class);
WideRouter.registerLocalRouter("com.spinytech.maindemo:music",MusicRouterConnectService.class);
WideRouter.registerLocalRouter("com.spinytech.maindemo:pic",PicRouterConnectService.class);
}
//初始化程式啟動
@Override
protected void initializeLogic() {
registerApplicationLogic("com.spinytech.maindemo",999, MainApplicationLogic.class);
registerApplicationLogic("com.spinytech.maindemo",998, WebApplicationLogic.class);
registerApplicationLogic("com.spinytech.maindemo:music",999, MusicApplicationLogic.class);
registerApplicationLogic("com.spinytech.maindemo:pic",999, PicApplicationLogic.class);
}
//是否使用多程式
@Override
public boolean needMultipleProcess() {
return true;
}
}
複製程式碼
程式初始化的時候註冊MaProvider到程式路由中
public class MainApplicationLogic extends BaseApplicationLogic {
@Override
public void onCreate() {
super.onCreate();
//註冊Provider
LocalRouter.getInstance(mApplication).registerProvider("main",new MainProvider());
}
}
複製程式碼
為每個Provider繫結可以觸發的Action任務
public class MainProvider extends MaProvider {
@Override
protected void registerActions() {
registerAction("sync",new SyncAction());
registerAction("async",new AsyncAction());
registerAction("attachment",new AttachObjectAction());
}
}
複製程式碼
下面是程式內同步通訊
程式內呼叫
RouterResponse response = LocalRouter.getInstance(MaApplication.getMaApplication()) //程式中單例LocalRouter
.route(MainActivity.this, RouterRequest.obtain(MainActivity.this) //在快取池中獲取請求
.provider("main") //設定provider
.action("sync") //設定呼叫的action
.data("1", "Hello") //設定資料
.data("2", "World"));
複製程式碼
通過註冊的內容找到相應的action,然後呼叫action中的invoke方法
public RouterResponse route(Context context, @NonNull RouterRequest routerRequest) throws Exception {
Logger.d(TAG, "Process:" + mProcessName + "\nLocal route start: " + System.currentTimeMillis());
RouterResponse routerResponse = new RouterResponse();
// Local request
//檢查domain是不是在同一個程式
if (mProcessName.equals(routerRequest.getDomain())) {
HashMap<String, String> params = new HashMap<>();
Object attachment = routerRequest.getAndClearObject();
params.putAll(routerRequest.getData());
Logger.d(TAG, "Process:" + mProcessName + "\nLocal find action start: " + System.currentTimeMillis());
//通過provider索引到action
MaAction targetAction = findRequestAction(routerRequest);
routerRequest.isIdle.set(true);
Logger.d(TAG, "Process:" + mProcessName + "\nLocal find action end: " + System.currentTimeMillis());
routerResponse.mIsAsync = attachment == null ? targetAction.isAsync(context, params) : targetAction.isAsync(context, params, attachment);
// Sync result, return the result immediately
// 同步呼叫.
if (!routerResponse.mIsAsync) {
//呼叫action的實現
MaActionResult result = attachment == null ? targetAction.invoke(context, params) : targetAction.invoke(context, params, attachment);
//包裝response
routerResponse.mResultString = result.toString();
routerResponse.mObject = result.getObject();
Logger.d(TAG, "Process:" + mProcessName + "\nLocal sync end: " + System.currentTimeMillis());
}
複製程式碼
下面是程式內非同步通訊
route方法中在mIsAsync設定是否非同步
public RouterResponse route(Context context, @NonNull RouterRequest routerRequest) throws Exception {
Logger.d(TAG, "Process:" + mProcessName + "\nLocal route start: " + System.currentTimeMillis());
RouterResponse routerResponse = new RouterResponse();
// Local request
//檢查domain是不是在同一個程式
if (mProcessName.equals(routerRequest.getDomain())) {
...
// Sync result, return the result immediately
// 同步呼叫.
if (!routerResponse.mIsAsync) {
...
}
// Async result, use the thread pool to execute the task.
//非同步呼叫
else {
//建立非同步任務
LocalTask task = new LocalTask(routerResponse, params,attachment, context, targetAction);
//通過執行緒池呼叫
routerResponse.mAsyncResponse = getThreadPool().submit(task);
}
複製程式碼
非同步呼叫任務是使用Callback任務
//使用Future Callable的方式使用執行緒池
private class LocalTask implements Callable<String> {
private RouterResponse mResponse;
private HashMap<String, String> mRequestData;
private Context mContext;
private MaAction mAction;
private Object mObject;
public LocalTask(RouterResponse routerResponse, HashMap<String, String> requestData,Object object, Context context, MaAction maAction) {
this.mContext = context;
this.mResponse = routerResponse;
this.mRequestData = requestData;
this.mAction = maAction;
this.mObject = object;
}
@Override
public String call() throws Exception {
//呼叫action中的invoke方法
MaActionResult result = mObject == null ? mAction.invoke(mContext, mRequestData) : mAction.invoke(mContext, mRequestData, mObject);
mResponse.mObject = result.getObject();
Logger.d(TAG, "Process:" + mProcessName + "\nLocal async end: " + System.currentTimeMillis());
return result.toString();
}
}
複製程式碼
下面是跨程式通訊
使用aidl呼叫廣域的WideRouter
//檢查domain是不是在同一個程式
if (mProcessName.equals(routerRequest.getDomain())) {
...
}
// IPC request
else {
//獲取程式domain
String domain = routerRequest.getDomain();
String routerRequestString = routerRequest.toString();
routerRequest.isIdle.set(true);
//檢查是不已經繫結了廣域路由WideRouter
if (checkWideRouterConnection()) {
Logger.d(TAG, "Process:" + mProcessName + "\nWide async check start: " + System.currentTimeMillis());
//If you don't need wide async check, use "routerResponse.mIsAsync = false;" replace the next line to improve performance.
//檢查是同步還是非同步
routerResponse.mIsAsync = mWideRouterAIDL.checkResponseAsync(domain, routerRequestString);
Logger.d(TAG, "Process:" + mProcessName + "\nWide async check end: " + System.currentTimeMillis());
}
// Has not connected with the wide router.
else {
//呼叫連線廣域路由WideRouter
routerResponse.mIsAsync = true;
ConnectWideTask task = new ConnectWideTask(routerResponse, domain, routerRequestString);
routerResponse.mAsyncResponse = getThreadPool().submit(task);
return routerResponse;
}
//同步呼叫
if (!routerResponse.mIsAsync) {
//aidl傳輸給相關程式的LocalRouterConnectService
routerResponse.mResultString = mWideRouterAIDL.route(domain, routerRequestString);
Logger.d(TAG, "Process:" + mProcessName + "\nWide sync end: " + System.currentTimeMillis());
}
// Async result, use the thread pool to execute the task.
//非同步呼叫
else {
//設定廣域呼叫任務
WideTask task = new WideTask(domain, routerRequestString);
routerResponse.mAsyncResponse = getThreadPool().submit(task);
}
}
//返回ReouterResponse
return routerResponse;
複製程式碼
廣域路由連線檢測,呼叫aidl連線到WideRouterConnectService
private class ConnectWideTask implements Callable<String> {
private RouterResponse mResponse;
private String mDomain;
private String mRequestString;
public ConnectWideTask(RouterResponse routerResponse, String domain, String requestString) {
this.mResponse = routerResponse;
this.mDomain = domain;
this.mRequestString = requestString;
}
@Override
public String call() throws Exception {
Logger.d(TAG, "Process:" + mProcessName + "\nBind wide router start: " + System.currentTimeMillis());
//繫結WideRouterConnectService
connectWideRouter();
int time = 0;
while (true) {
//等待廣域路由繫結完成
if (null == mWideRouterAIDL) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
time++;
} else {
break;
}
//超過30秒就放棄,丟擲錯誤
if (time >= 600) {
ErrorAction defaultNotFoundAction = new ErrorAction(true, MaActionResult.CODE_CANNOT_BIND_WIDE, "Bind wide router time out. Can not bind wide router.");
MaActionResult result = defaultNotFoundAction.invoke(mApplication, new HashMap<String, String>());
mResponse.mResultString = result.toString();
return result.toString();
}
}
Logger.d(TAG, "Process:" + mProcessName + "\nBind wide router end: " + System.currentTimeMillis());
//呼叫關於路由傳輸到對應的程式路由
String result = mWideRouterAIDL.route(mDomain, mRequestString);
Logger.d(TAG, "Process:" + mProcessName + "\nWide async end: " + System.currentTimeMillis());
return result;
}
}
複製程式碼
WideRouterConnectService對對應的程式路由分發通訊,監聽返回。
//廣域路由處理
IWideRouterAIDL.Stub stub = new IWideRouterAIDL.Stub() {
@Override
public boolean checkResponseAsync(String domain, String routerRequest) throws RemoteException {
//檢查是否非同步調動
return WideRouter.getInstance(MaApplication.getMaApplication())
.answerLocalAsync(domain, routerRequest);
}
@Override
public String route(String domain, String routerRequest) {
try {
//廣域路由分發到對應的程式路由中
return WideRouter.getInstance(MaApplication.getMaApplication())
.route(domain, routerRequest)
.mResultString;
} catch (Exception e) {
e.printStackTrace();
return new MaActionResult.Builder()
.code(MaActionResult.CODE_ERROR)
.msg(e.getMessage())
.build()
.toString();
}
}
@Override
public boolean stopRouter(String domain) throws RemoteException {
//停止連線程式路由
return WideRouter.getInstance(MaApplication.getMaApplication())
.disconnectLocalRouter(domain);
}
};
複製程式碼
根據domain分發到對應程式的ILocalRouterAIDL
public RouterResponse route(String domain, String routerRequest) {
Logger.d(TAG, "Process:" + PROCESS_NAME + "\nWide route start: " + System.currentTimeMillis());
RouterResponse routerResponse = new RouterResponse();
//是否已經被要求停止任務
if (mIsStopping) {
MaActionResult result = new MaActionResult.Builder()
.code(MaActionResult.CODE_WIDE_STOPPING)
.msg("Wide router is stopping.")
.build();
routerResponse.mIsAsync = true;
routerResponse.mResultString = result.toString();
return routerResponse;
}
//廣域路由不能作為呼叫物件
if (PROCESS_NAME.equals(domain)) {
MaActionResult result = new MaActionResult.Builder()
.code(MaActionResult.CODE_TARGET_IS_WIDE)
.msg("Domain can not be " + PROCESS_NAME + ".")
.build();
routerResponse.mIsAsync = true;
routerResponse.mResultString = result.toString();
return routerResponse;
}
//獲取對應程式路由的物件
ILocalRouterAIDL target = mLocalRouterAIDLMap.get(domain);
if (null == target) {
//是否已經繫結了本地路由,沒有就啟動繫結
if (!connectLocalRouter(domain)) {
MaActionResult result = new MaActionResult.Builder()
.code(MaActionResult.CODE_ROUTER_NOT_REGISTER)
.msg("The " + domain + " has not registered.")
.build();
routerResponse.mIsAsync = false;
routerResponse.mResultString = result.toString();
Logger.d(TAG, "Process:" + PROCESS_NAME + "\nLocal not register end: " + System.currentTimeMillis());
return routerResponse;
} else {
// Wait to bind the target process connect service, timeout is 30s.
Logger.d(TAG, "Process:" + PROCESS_NAME + "\nBind local router start: " + System.currentTimeMillis());
int time = 0;
//等待完成繫結程式連線
while (true) {
target = mLocalRouterAIDLMap.get(domain);
if (null == target) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
time++;
} else {
Logger.d(TAG, "Process:" + PROCESS_NAME + "\nBind local router end: " + System.currentTimeMillis());
break;
}
//設定30s超時
if (time >= 600) {
MaActionResult result = new MaActionResult.Builder()
.code(MaActionResult.CODE_CANNOT_BIND_LOCAL)
.msg("Can not bind " + domain + ", time out.")
.build();
routerResponse.mResultString = result.toString();
return routerResponse;
}
}
}
}
try {
Logger.d(TAG, "Process:" + PROCESS_NAME + "\nWide target start: " + System.currentTimeMillis());
//對應程式呼叫返回
String resultString = target.route(routerRequest);
routerResponse.mResultString = resultString;
Logger.d(TAG, "Process:" + PROCESS_NAME + "\nWide route end: " + System.currentTimeMillis());
} catch (RemoteException e) {
e.printStackTrace();
MaActionResult result = new MaActionResult.Builder()
.code(MaActionResult.CODE_REMOTE_EXCEPTION)
.msg(e.getMessage())
.build();
routerResponse.mResultString = result.toString();
return routerResponse;
}
return routerResponse;
}
複製程式碼
基本原理就介紹到這裡了。
1.aidl是google為Android程式通訊提供的方式,使用了代理模式,其內部整合了IBinder,使用了Binder的方式通訊,已經成為套路的規則,維護成本低。
2.當序列化後的資料單元過大時,就會出問題,報出android.os.TransactionTooLargeException。其資料量限制為1M
3.原理上說就是binder只拷貝一次,使用虛擬記憶體和實體記憶體頁對映,比socket高效,也安全。
4.這裡介紹的框架,其中是通過本地路由和廣域路由間的傳送和切換來完成。只能交流變數和呼叫方法,無法通過aidl獲取資源。本地路由能力並未有ARouter使用的方便,程式內對無法提供獲取Fragment View等資源獲取,可以考慮擴充。但是此本地和廣域路由設計非常優秀。
5.wutongke有出了一個框架加上編譯時註解的優化版github.com/wutongke/Mo…
下一節將會繼續介紹Messenger程式通訊框架,敬請期待。