React native 第二彈 之 媽的這坑好多
目前遇到的坑真心不少,每次一個改版都是一場浩劫。但是官方又大概維持兩週一次的改版,第三方又不一定那麼勤勞。那爆掉的情況就不少啦。相信不少同仁都花不少時間一起在幫忙第三方開源 lib debug ,但我認為這就是開源軟體最大的優點。下面就隨手筆記一下。
1. Native module Android 範例
先說明情境,想寫個根據 package name 去抓 app label name。
react native – android
@ReactMethod
public void getLabel(
String packageName,
Callback errorCallback,
Callback successCallback) {
SharedPreferences spref = this.reactContext.
getSharedPreferences(this.reactContext.getPackageName() + "_preferences", Context.MODE_PRIVATE);
Log.i(TAG, "getLabelInvoke package :"+ packageName + " isChecked=" + spref.getBoolean(packageName, false));
try {
String appName = (String) reactContext.getPackageManager().getApplicationLabel(
reactContext.getPackageManager().getApplicationInfo(packageName, PackageManager.GET_META_DATA));
successCallback.invoke(appName,spref.getBoolean(packageName, spref.getBoolean(packageName, false)));
} catch (PackageManager.NameNotFoundException e) {
errorCallback.invoke(e.getMessage());
}
}
@ReactMethod
public void getLabel1(
String packageName,
Promise promise) {
SharedPreferences spref = this.reactContext.
getSharedPreferences(this.reactContext.getPackageName() + "_preferences", Context.MODE_PRIVATE);
Log.i(TAG, "getLabelInvoke package :"+ packageName + " isChecked=" + spref.getBoolean(packageName, false));
try {
String appName = (String) reactContext.getPackageManager().getApplicationLabel(
reactContext.getPackageManager().getApplicationInfo(packageName, PackageManager.GET_META_DATA));
WritableMap map = Arguments.createMap();
map.putString("label",appName);
map.putBoolean("enable",spref.getBoolean(packageName, false));
promise.resolve(map);
} catch (PackageManager.NameNotFoundException e) {
promise.reject(e);
}
}
react native – js
};
fetchAppLabel(packageName: string) {
self = this;
MessageListenerModule.getLabel(packageName,
(msg: string) => {
console.log('Fail to get labe , package:', msg);
},
(appLabel: string, enable: boolean) => {
console.log('getAppLabel app :', packageName, ':', appLabel, ':', enable);
}
);
}
async fetchAppLabel1(packageName: string) {
try {
let {
label,
enable,
} = await MessageListenerModule.getLabel1(packageName);
console.log('package:', packageName, ' label:', label);
} catch (e) {
console.log(e);
}
}
2. react-native-router-flux sub scene
裡面的 sub scene 使用方法超難懂。 不知道有沒有人在用 drawer 搭配多層 scene ,達到 drawer 上切換主類別,然後進入個子項目裡,按照範例的寫法,每次切換drawer 選項,都會失敗,如果有子分頁的話。以下面例子來說,每次只要在 drawer 上切換到 device config 頁面,你會發現外表是 device config 的 list page,但是內心他自己會以為自己是 settings about 子分頁,因此,你不管怎麼切換都不會成功。這很明顯不是我們要的。原因我也不知道。


後來發現解法,就是順他的毛做,加一個 main 最為子選項的第一項。
3. render right button
呈上,很多情況下,你會想要自己客製化 router 上的 right button,像是上面的程式碼中的 renderRightButton ,內容如下,要特別記住 renderRightButton 的範圍是 router 的左上方為起始點,儘管它的名稱叫做 renderRightButton。
為了讓他變成是 right button ,因此需要先設定 justifyContent : flex-end ,才會從右邊開始算,但是新的問題就發生了,就是你的 back button 點不下去了,這是因為 right button layout 的範圍蓋在 back button 上面,導致按不到,因此必須空出一個範圍給 back key button. 我的做法就是設定 marginLeft。
4. React native 的 phone call listener error
想要監聽 phone call 進來,然後傳到 js level, code 如下
EventReceiver.java
public class EventReceiver extends BroadcastReceiver {
private static final String TAG = "EventReceiver";
private static boolean incomingFlag = false;
private static String incoming_number = null;
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(TelephonyManager.ACTION_PHONE_STATE_CHANGED)) {
TelephonyManager tm = (TelephonyManager) context.getSystemService(Service.TELEPHONY_SERVICE);
switch (tm.getCallState()) {
case TelephonyManager.CALL_STATE_RINGING:
incomingFlag = true;
incoming_number = intent.getStringExtra("incoming_number");
Log.i(TAG, "RINGING :" + incoming_number);
sendIncallNotification(context, true, incoming_number);
break;
case TelephonyManager.CALL_STATE_OFFHOOK:
if (incomingFlag) {
Log.i(TAG, "incoming ACCEPT :" + incoming_number);
sendIncallNotification(context, false, incoming_number);
}
break;
case TelephonyManager.CALL_STATE_IDLE:
if (incomingFlag) {
Log.i(TAG, "incoming IDLE");
sendIncallNotification(context, false, incoming_number);
}
break;
}
} else if (intent.getAction().equals(Telephony.Sms.Intents.SMS_RECEIVED_ACTION)) {
Log.i(TAG, "Receive a new message");
Intent serviceIntent = new Intent();
serviceIntent.setClass(context, NListenerService.class);
serviceIntent.setAction(NListenerService.SEND_NOTIFICATION_SMS);
context.startService(serviceIntent);
} else if (intent.getAction().equals(CalendarContract.ACTION_EVENT_REMINDER)) {
Log.i(TAG, "Receive a calendar event");
Intent serviceIntent = new Intent();
serviceIntent.setClass(context, NListenerService.class);
serviceIntent.setAction(NListenerService.SEND_NOTIFICATION_EVENT);
context.startService(serviceIntent);
}
}
private void sendIncallNotification(Context context, boolean stauts, String phoneNumber) {
Intent serviceIntent = new Intent();
serviceIntent.setClass(context, NListenerService.class);
serviceIntent.setAction(NListenerService.SEND_NOTIFICATION_CALL);
serviceIntent.putExtra("status", stauts);
serviceIntent.putExtra("phoneNumber", phoneNumber);
context.startService(serviceIntent);
}
}
NListenerService.java
public class NListenerService extends NotificationListenerService {
private String TAG = "NListenerService";
public final static String SEND_NOTIFICATION_SMS = "com.android.notification.sendsms";
public final static String SEND_NOTIFICATION_EVENT = "com.android.notification.event";
public final static String SEND_NOTIFICATION_CALL = "com.android.notification.incomingcall";
private String mPhoneNumber = "";
@Override
public void onCreate() {
super.onCreate();
}
@Override
public void onDestroy() {
super.onDestroy();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (intent == null || intent.getAction() == null) {
return START_STICKY;
}
if (intent.getAction().equals(SEND_NOTIFICATION_SMS)) {
sendSmsNotification();
}
if (intent.getAction().equals(SEND_NOTIFICATION_EVENT)) {
sendEventNotification();
}
if (intent.getAction().equals(SEND_NOTIFICATION_CALL)) {
sendCallNotification(intent.getBooleanExtra("status", false), intent.getStringExtra("phoneNumber"));
mPhoneNumber = intent.getStringExtra("phoneNumber");
}
// We want this service to continue running until it is explicitly
// stopped, so return sticky.
return START_STICKY;
}
private void sendCallNotification(boolean b, String phoneNumber) {
SharedPreferences spref =
getSharedPreferences(getPackageName() + "_preferences", Context.MODE_PRIVATE);
Log.i(TAG, "sendCallNotification:" + b);
Log.i(TAG, "spref:" + spref.getBoolean(MessageListenerModule.PHONE_CALL_KEY, true));
if (spref.getBoolean(MessageListenerModule.PHONE_CALL_KEY, true)) {
if (phoneNumber != null) {
// TODO : Issue , if app is dismiss from system.
// Facebook arch cant init quickly when phone call, will cause error.
WritableMap params = Arguments.createMap();
params.putString("phoneNumber", phoneNumber);
sendEventToJS(MessageListenerModule.JS_EVENT_PHONE_CALL, params);
}
}
}
private void sendEventNotification() {
SharedPreferences spref =
getSharedPreferences(getPackageName() + "_preferences", Context.MODE_PRIVATE);
if (spref.getBoolean(MessageListenerModule.CALENDAR_KEY, true)) {
WritableMap params = Arguments.createMap();
sendEventToJS(MessageListenerModule.JS_EVENT_CALENDAR, params);
}
}
@TargetApi(Build.VERSION_CODES.KITKAT)
private void sendSmsNotification() {
SharedPreferences spref =
getSharedPreferences(this.getPackageName() + "_preferences", Context.MODE_PRIVATE);
if (spref.getBoolean(Telephony.Sms.getDefaultSmsPackage(this), false)) {
WritableMap params = Arguments.createMap();
params.putString("package", Telephony.Sms.getDefaultSmsPackage(this));
sendEventToJS(MessageListenerModule.JS_EVENT_SMS, params);
}
}
@TargetApi(Build.VERSION_CODES.KITKAT)
@Override
public void onNotificationPosted(StatusBarNotification sbn) {
Log.i(TAG, "onNotificationPosted:" + sbn.getPackageName());
String packageName = sbn.getPackageName();
SharedPreferences spref =
getSharedPreferences(this.getPackageName() + "_preferences", Context.MODE_PRIVATE);
Log.i(TAG, "onNotificationPosted:" + "isChecked=" + spref.getBoolean(packageName, false));
if (spref.getBoolean(packageName, false) || packageName.equals(Telephony.Sms.getDefaultSmsPackage(this))) {
WritableMap params = Arguments.createMap();
params.putString("package", packageName);
sendEventToJS(MessageListenerModule.JS_EVENT_NOTIFICATION, params);
}
}
@Override
public void onNotificationRemoved(StatusBarNotification sbn) {
Log.i(TAG, "********** onNOtificationRemoved:" + sbn.getPackageName());
}
private void sendEventToJS(String eventName,
@Nullable WritableMap params) {
ReactContext reactContext = ((CybertoolApplication) this.getApplicationContext()).getReactContext();
reactContext
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
.emit(eventName, params);
}
}
這程式碼沒有問題,只要 app 沒有被 kill 過,如果 app 被移出 memory , 那麼在 service 開始處理 phone call event 就會拋出 error ,主要就是phone call event 是屬於必須及時處理的 event , 但是這時候 app 被 kill ,那麼系統必須馬上 init android service ,到這階段沒有問題,但是 react native 的 module 卻無法來得及。
java.lang.ExceptionInInitializerError at com.facebook.react.bridge.ReactBridge.staticInit(ReactBridge.java:39) at com.facebook.react.bridge.NativeMap.(NativeMap.java:22) at com.facebook.react.bridge.Arguments.createMap(Arguments.java:29) at com.acer.android.message.NListenerService.sendCallNotification(NListenerService.java:72) at com.acer.android.message.NListenerService.onStartCommand(NListenerService.java:54) at android.app.ActivityThread.handleServiceArgs(ActivityThread.java:3067) at android.app.ActivityThread.access$2200(ActivityThread.java:154) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1489) at android.os.Handler.dispatchMessage(Handler.java:102) at android.os.Looper.loop(Looper.java:224) at android.app.ActivityThread.main(ActivityThread.java:5526) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616) Caused by: java.lang.RuntimeException: SoLoader.init() not yet called at com.facebook.soloader.SoLoader.assertInitialized(SoLoader.java:234) at com.facebook.soloader.SoLoader.loadLibrary(SoLoader.java:169) at com.facebook.react.bridge.ReactBridge.staticInit(ReactBridge.java:39) at com.facebook.react.bridge.ReactBridge.(ReactBridge.java:31)
解法來看,目前沒有?等官方?不然就是都在 android level 解決。
感謝收看,歡迎分享




