編輯:關於android開發
前面兩篇博客,詳細分析了Native與Javascript通信的過程,可以滿足絕大部分場景下Native和Javascript的相互調用,但是仍然有不健全的情況。
比如Javascript層要實時獲取Native的一些狀態,就需要Native被動地向Javascript層通信了。這個過程區別於通信第一篇中Native主動向Javascript層通信,本篇博客就來研究下這樣一個被動回調的過程!
首先,從一個常用的場景開始分析。
假設前端開發者在Javascript的代碼中想要獲取APP的狀態,比如APP是否是處於前台(active),還是後台(background)。大概有兩種實現方式:
1、Native 在APP每次狀態切換的時候,調用callFunction將最新的狀態傳給Javascript層,然後由Javascript緩存起來,這樣開發者想要獲取狀態可以能直接使用這個緩存的值。
2、前端開發者在Javascript中想要獲取狀態時,先向Native端發起通信請求,表示想獲取狀態,然後由Native端把這個狀態作為通信應答返給Javascript層。
這兩種方案都有各自的使用性場景,並且在React-Native都有相應實現。第一種實現對開發者來說相對簡單,直接取緩存值,是一個完全同步的過程。第二種實現向Native發起通信請求,需要等待Native的應答,是一個異步的過程。
第一種方案實現原理在React-Native系列Android——Native與Javascript通信原理(一)中已經詳細分析過了,不再贅述,本篇博文重點來分析下第二種方案的實現原理。
1、JavaScript的請求
Native與JavaScript的通信,都是由Native主動發起,然後由JavaScript應答,但是JavaScript是無法向Native主動發起通信的。那麼,JavaScript如何才能向Native發起通信請求呢?
上一篇博文中講過,JavaScript應答Native是通過將應答數據包裝成JSON格式,然後在flushedQueue() 返給Bridge再返給Native的。如果JavaScript在這個應答信息加入通信請求的標識,那麼Native在解析應答信息時發現了其中包含JavaScript的通信標識,然後Native來應答這個請求,這樣不就完成了一次JavaScript請求Native的過程嗎?
第一步:在返給Native的應答信息中加入JavaScript的通信請求
同樣以在Javascript中獲取APP當前狀態為例,示范代碼如下:
var NativeModules = require('NativeModules');
var RCTAppState = NativeModules.AppState;
var logError = require('logError');
RCTAppState.getCurrentAppState(
(appStateData) => {
console.log('dev', 'current state: ' + appStateData.app_state);
},
logError
);
前一篇博文中分析過NativeModules的前世今生,它是一個動態初始化的類(具體請看前篇Native與Javascript通信原理(二),這裡略過),RCTAppState.getCurrentAppState實際上是調用的是MessageQueue.js的下面這段代碼:
function(...args) {
let lastArg = args.length > 0 ? args[args.length - 1] : null;
let secondLastArg = args.length > 1 ? args[args.length - 2] : null;
let hasSuccCB = typeof lastArg === 'function';
let hasErrorCB = typeof secondLastArg === 'function';
hasErrorCB && invariant(hasSuccCB, 'Cannot have a non-function arg after a function arg.');
let numCBs = hasSuccCB + hasErrorCB;
let onSucc = hasSuccCB ? lastArg : null;
let onFail = hasErrorCB ? secondLastArg : null;
args = args.slice(0, args.length - numCBs);
return self.__nativeCall(module, method, args, onFail, onSucc);
};
這裡面參數args具體化有兩個,一個是lambda表達式回調函數,一個是logError,都是function類型。解析的時候lastArg變量指logError,secondLastArg變量指回調函數。
所以調用__nativeCall函數時候傳遞的兩個參數onFail和onSucc,就分別指回調函數和logError。這裡明顯是React-Native的命名bug了,差點以為是兩個變量解析顛倒了,不過不影響整個流程(原因是Native代碼中解析參數時默認是onSucc在前面,又顛倒回來了,後面會分析到)。
接下來看__nativeCall
__nativeCall(module, method, params, onFail, onSucc) {
if (onFail || onSucc) {
...
onFail && params.push(this._callbackID);
this._callbacks[this._callbackID++] = onFail;
onSucc && params.push(this._callbackID);
this._callbacks[this._callbackID++] = onSucc;
}
...
this._queue[MODULE_IDS].push(module);
this._queue[METHOD_IDS].push(method);
this._queue[PARAMS].push(params);
...
}
this._queue的作用上篇分析過,是用來保存應答Native的數據的,這裡主要來看if裡面的判斷邏輯。
this._callbackID是作為this._callbacks集合的索引來標識回調函數的,同時這個索引會放到params裡面傳遞給Native端,Native端應答的時候會將這個索引傳回到Javascript端,這樣Javascript端就能通過索引找到事先存放在this._callbacks集合裡的回調函數了。所以,this._callbackID就是Javascript請求Native的標識了。
第二步:Native如何應答Javascript端
中間還有一步flushedQueue() 向Bridge層的傳遞過程,參考前文即可,這裡跳過。
前篇博文中分析過Native處理來自Javascript應答信息,都是通過moduleID+methodID映射到具體NativeModule組件的方法,然後解析參數,最後通過invoke反射方式完成調用的。
例子中,獲取APP當前狀態的組件在Native端對應的NativeModule類是AppStateModule。被映射到的方法是getCurrentAppState,它有兩個Callback類型的參數。
來看看NativeModule解析Callback類型參數時的代碼,位於其父類com.facebook.react.bridge.BaseJavaModule.java中
static final private ArgumentExtractor ARGUMENT_EXTRACTOR_CALLBACK =
new ArgumentExtractor() {
@Override
public @Nullable Callback extractArgument(
CatalystInstance catalystInstance, ReadableNativeArray jsArguments, int atIndex) {
if (jsArguments.isNull(atIndex)) {
return null;
} else {
int id = (int) jsArguments.getDouble(atIndex);
return new CallbackImpl(catalystInstance, id);
}
}
};
private ArgumentExtractor[] buildArgumentExtractors(Class[] paramTypes) {
ArgumentExtractor[] argumentExtractors = new ArgumentExtractor[paramTypes.length];
for (int i = 0; i < paramTypes.length; i += argumentExtractors[i].getJSArgumentsNeeded()) {
Class argumentClass = paramTypes[i];
...
if (argumentClass == Callback.class) {
argumentExtractors[i] = ARGUMENT_EXTRACTOR_CALLBACK;
}
...
}
return argumentExtractors;
}
對於Callback類型參數,使用的參數提取器是ARGUMENT_EXTRACTOR_CALLBACK,在其extractArgument方法裡面提取出由Javascript端傳來的callbackID,構造進CallbackImpl對象裡面。而這個構造出來的CallbackImpl對象,就是invoke反射getCurrentAppState方法裡的參數了。
下面來看一下被反射的getCurrentAppState方法,位於com.facebook.react.modules.appstate.AppStateModule.java
public class AppStateModule extends ReactContextBaseJavaModule
implements LifecycleEventListener {
public static final String APP_STATE_ACTIVE = "active";
public static final String APP_STATE_BACKGROUND = "background";
private String mAppState = "uninitialized";
public AppStateModule(ReactApplicationContext reactContext) {
super(reactContext);
}
@Override
public String getName() {
return "AppState";
}
@Override
public void initialize() {
getReactApplicationContext().addLifecycleEventListener(this);
}
@ReactMethod
public void getCurrentAppState(Callback success, Callback error) {
success.invoke(createAppStateEventMap());
}
@Override
public void onHostResume() {
mAppState = APP_STATE_ACTIVE;
sendAppStateChangeEvent();
}
@Override
public void onHostPause() {
mAppState = APP_STATE_BACKGROUND;
sendAppStateChangeEvent();
}
...
private WritableMap createAppStateEventMap() {
WritableMap appState = Arguments.createMap();
appState.putString("app_state", mAppState);
return appState;
}
...
}
當Activity生命周期變化的時候,會更新狀態到mAppState,createAppStateEventMap()將mAppState封裝在用於Native-Bridge間傳遞的WritableMap對象中。然後調用了success.invoke(),而這個Callback類型的 success參數就是前面ARGUMENT_EXTRACTOR_CALLBACK構造出來的CallbackImpl對象了,它內存保存著用於回調的標識callbackID。
所以,來看CallbackImpl的invoke方法,代碼在com.facebook.react.bridge.CallbackImpl.java
public final class CallbackImpl implements Callback {
private final CatalystInstance mCatalystInstance;
private final int mCallbackId;
public CallbackImpl(CatalystInstance bridge, int callbackId) {
mCatalystInstance = bridge;
mCallbackId = callbackId;
}
@Override
public void invoke(Object... args) {
mCatalystInstance.invokeCallback(mCallbackId, Arguments.fromJavaArgs(args));
}
}
其invoke方法裡面又調用了CatalystInstance.invokeCallback,通過前面兩篇博文我們知道CatalystInstance是Native向Javascript通信的入口,那麼這裡很明顯其CatalystInstance.invokeCallback就是Native對Javascript的應答了。裡面包含了標識callbackID和內容數據mAppState。
在CatalystInstance的實現類CatalystInstanceImpl內部,又是通過ReactBridge調用JNI的,這一點同
React-Native系列Android——Native與Javascript通信原理(一)中的callFunction原理完全一樣。
public class CatalystInstanceImpl implements CatalystInstance {
...
public void invokeCallback(final int callbackID, final NativeArray arguments) {
...
Assertions.assertNotNull(mBridge).invokeCallback(callbackID, arguments);
...
}
...
}
第三步:Bridge的中轉
上一步中通過JNI調用了invokeCallback方法,裡面有兩個參數:callbackID和arguments。callbackID是來自Javascript端的通信回調標識,arguments是Native應答Javascript請求的內容。Bridge的作用就是將這兩個參數中轉到Javascript端。
Bridge層的調用入口是react\jni\OnLoad.cpp,先來瞧瞧invokeCallback方法
static void invokeCallback(JNIEnv* env, jobject obj, JExecutorToken::jhybridobject jExecutorToken, jint callbackId,
NativeArray::jhybridobject args) {
auto bridge = extractRefPtr(env, obj);
auto arguments = cthis(wrap_alias(args));
try {
bridge->invokeCallback(
cthis(wrap_alias(jExecutorToken))->getExecutorToken(wrap_alias(jExecutorToken)),
(double) callbackId,
std::move(arguments->array)
);
} catch (...) {
translatePendingCppExceptionToJavaException();
}
}
調用的又是CountableBridge即Bridge對象的invokeCallback方法,代碼在react\Bridge.cpp中
void Bridge::invokeCallback(ExecutorToken executorToken, const double callbackId, const folly::dynamic& arguments) {
...
auto executorMessageQueueThread = getMessageQueueThread(executorToken);
if (executorMessageQueueThread == nullptr) {
...
return;
}
std::shared_ptr isDestroyed = m_destroyed;
executorMessageQueueThread->runOnQueue([=] () {
...
JSExecutor *executor = getExecutor(executorToken);
if (executor == nullptr) {
...
return;
}
...
executor->invokeCallback(callbackId, arguments);
});
}
在executorMessageQueueThread隊列線程裡面,執行的是JSExecutor的invokeCallback方法。
繼續來看react\JSCExecutor.cpp
void JSCExecutor::invokeCallback(const double callbackId, const folly::dynamic& arguments) {
std::vector call{
(double) callbackId,
std::move(arguments)
};
std::string calls = executeJSCallWithJSC(m_context, "invokeCallbackAndReturnFlushedQueue", std::move(call));
m_bridge->callNativeModules(*this, calls, true);
}
static std::string executeJSCallWithJSC(
JSGlobalContextRef ctx,
const std::string& methodName,
const std::vector& arguments) {
...
// Evaluate script with JSC
folly::dynamic jsonArgs(arguments.begin(), arguments.end());
auto js = folly::to(
"__fbBatchedBridge.", methodName, ".apply(null, ",
folly::toJson(jsonArgs), ")");
auto result = evaluateScript(ctx, String(js.c_str()), nullptr);
return Value(ctx, result).toJSONString();
}
這段代碼和callFunction非常相似,只不過executeJSCallWithJSC裡面第二個參數換成了invokeCallbackAndReturnFlushedQueue。
這一段生成的Javascript執行語句是
__fbBatchedBridge.invokeCallbackAndReturnFlushedQueue.apply(null, jsonArgs);
jsonArgs中包含callbackID和arguments,Webkit執行這段Javascript語句達到連接到Javascript端的目的。
當然,執行完Javascript語句後也有一個result返回,用來調用callNativeModules,作為後續的通信請求,流程和前篇完全一致!
第四步:Javascript接收Native的應答
參考React-Native系列Android——Native與Javascript通信原理(一),上一步中Bridge創建的Javascript執行語句
__fbBatchedBridge.invokeCallbackAndReturnFlushedQueue.apply(null, jsonArgs);
其實等同於
MessageQueue.invokeCallbackAndReturnFlushedQueue.apply(null, callbackID, args);
所以執行的是MessageQueue.js的invokeCallbackAndReturnFlushedQueue方法。
invokeCallbackAndReturnFlushedQueue(cbID, args) {
guard(() => {
this.__invokeCallback(cbID, args);
this.__callImmediates();
});
return this.flushedQueue();
}
這裡的cbID其實就是callbackID了,也就是第一步裡面的this._callbackID。這個值是由Javascript傳給Native的,現在又從Native傳回來了,完璧歸趙啊!
下面調用的是this.__invokeCallback
__invokeCallback(cbID, args) {
...
let callback = this._callbacks[cbID];
...
this._callbacks[cbID & ~1] = null;
this._callbacks[cbID | 1] = null;
callback.apply(null, args);
...
}
this._callbacks集合裡面以callbackID為索引保存著回調函數callback,這裡就可以通過cbID這個索引為key取出來了。這樣執行callback.apply(null, args)就等於執行回調函數了。
同時,還要清除this._callbacks集合裡面保存的回調函數。由於__nativeCall中封裝回調函數時,先後保存了兩個回調函數onFail(索引為偶數)和onSucc(索引為奇數,比前者+1),而取出來的callback並不確定是onFail還是onSucc。所以,cbID & ~1最低位置0,cbID | 1最低位置1,這樣無論cbID標識是onFail還是onSucc的索引,都能保證兩者完全清除。
獲取APP狀態例子中的回調函數是
function(appStateData){
console.log('dev', 'current state: ' + appStateData.app_state);
}
app_state變量的值就是當前APP的狀態了,與AppStateModule中的值的封裝恰好呼應
private WritableMap createAppStateEventMap() {
WritableMap appState = Arguments.createMap();
appState.putString("app_state", mAppState);
return appState;
}
這樣,整個通信流程差不多就到此完整了。
總結
Javascript請求Native再回調到Javascript中,一共經歷了如下流程:
一共Javascript->Bridge->Native->Bridge->Javascript五個步驟,callbackID是整個流程的關鍵點。
Javascript請求Native,需要先生成callbackID,並以callbackID為唯一鍵存儲回調函數。callbackID作為上一次通信請求的應答內容傳到Native端,Native接收到後通過反射NativeModule的處理方法,然後將callbackID及處理結果返給Javascript端,Javascript使用callbackID獲取到存儲的回調方法,然後執行。
流程圖表示如下:
硅谷社交3--登錄頁面,硅谷社交3-- 1.頁面布局 <?xml version=1.0 encoding=utf-8?> <LinearLayout
安卓開源項目周報0208,安卓開源項目0208 由OpenDigg 出品的安卓開源項目周報第七期來啦。我們的安卓開源周報集合了OpenDigg一周來
如果說Activity和服務都是實干派,那麼將Broadcast Receiv
IDA動態調試Android的DEX文件 0x00 我們以阿裡比賽第一題為例,來動態調試dex文件。參考IDA動態調試Android的DEX文件一文,首先Androi