產品為了提高推送送達率,提了一個需求:在 APP 推送關閉的情況下顯示一個小 TIP,點選 TIP 跳轉到 APP 訊息設定介面。
我們的 APP 是基於 React Native 開發的,這些功能 Facebook 官方沒有提供,需要我們開發對應的原生模組。
因為開發原生模組屬於比較深入的內容了,寫這篇文章時我就預設閱讀者已經具有一定的 Objective-C
和 Java
開發能力,下面就直接貼程式碼說思路了。
開發一個原生模組的基礎知識可以直接看官方文件,寫的很詳細,我這裡就不多重複了。
下面開始分析實現。
第一步:獲取 APP 推送狀態
這裡我主要參考極光推送。因為公司內部有統一的推送 SDK
(主要整合了市面上多家推送服務公司和手機廠商的推送服務),一些極光推送很方便的功能暫時用不了,只能自己參考實現。
在我的實現裡,獲取 APP 推送狀態主要做了兩件事:
- 相容多個系統版本(這部分都是極光推送開發者的功勞);
- 以
Promise
的形式進行封裝(極光推送是基於callback
的)
getSystemNoticeStatus()
這個函式,在 APP 推送開啟的情況下返回 true
,未開啟情況返回 false
。
iOS 程式碼如下:
RCT_EXPORT_METHOD( getSystemNoticeStatus: (RCTPromiseResolveBlock) resolve
rejecter: (RCTPromiseRejectBlock) reject )
{
dispatch_async( dispatch_get_main_queue(), ^{
float systemVersion = [[UIDevice currentDevice].systemVersion floatValue];
if ( systemVersion >= 8.0 )
{
UIUserNotificationSettings *settings = [[UIApplication sharedApplication] currentUserNotificationSettings];
UIUserNotificationType type = settings.types;
if ( type == UIUserNotificationTypeNone )
{
return(resolve (@NO) );
}else {
return(resolve (@YES) );
}
}else if ( systemVersion >= 10.0 )
{
[[UNUserNotificationCenter currentNotificationCenter] getNotificationSettingsWithCompletionHandler: ^ (UNNotificationSettings * _Nonnull settings) {
switch ( settings.authorizationStatus )
{
case UNAuthorizationStatusDenied:
case UNAuthorizationStatusNotDetermined:
return(resolve (@NO) );
break;
case UNAuthorizationStatusAuthorized:
return(resolve (@YES) );
break;
}
}];
}
} );
}
複製程式碼
Android 程式碼如下:
參考連結:
/**
* 獲取 APP 系統通知狀態
*
*
*/
@ReactMethod
public void getSystemNoticeStatus(Promise promise) {
promise.resolve(hasPermission("OP_POST_NOTIFICATION"));
}
private boolean hasPermission(String appOpsServiceId) {
Context context = getReactApplicationContext();
if (Build.VERSION.SDK_INT >= 24) {
NotificationManager mNotificationManager = (NotificationManager) context.getSystemService(
Context.NOTIFICATION_SERVICE);
return mNotificationManager.areNotificationsEnabled();
}else if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT){
AppOpsManager mAppOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
ApplicationInfo appInfo = context.getApplicationInfo();
String pkg = context.getPackageName();
int uid = appInfo.uid;
Class appOpsClazz;
try {
appOpsClazz = Class.forName(AppOpsManager.class.getName());
Method checkOpNoThrowMethod = appOpsClazz.getMethod("checkOpNoThrow", Integer.TYPE, Integer.TYPE,
String.class);
Field opValue = appOpsClazz.getDeclaredField(appOpsServiceId);
int value = opValue.getInt(Integer.class);
Object result = checkOpNoThrowMethod.invoke(mAppOps, value, uid, pkg);
return Integer.parseInt(result.toString()) == AppOpsManager.MODE_ALLOWED;
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
return false;
}
複製程式碼
然後我們在 JavaScript側直接引用就可
import {
Platform,
NativeModules,
} from 'react-native';
function getSystemNoticeStatus() {
NativeModules.appName.getSystemNoticeStatus().then((isOpen) => {
console.log('getSystemNotice', isOpen) //
}).catch((e) => {
console.log('getSystemNoticeStatus error', e)
});
}
複製程式碼
第二步:跳轉到 APP 設定介面
跳轉到 APP 設定介面也要考慮不同系統版本的相容。比如說 iOS11+ 現在只允許跳轉到系統設定首頁/該應用的設定介面,Android 還要考慮不同廠商對 APP 設定頁面的魔改,很是頭疼。
首先 iOS 適配,我們直接跳轉到該應用的設定首頁,就是下圖:
這個開發比較簡單,直接在 React Native 中引用 Linking.openURL('app-settings:')
就行;
Android 就要多些一些程式碼了,具體的適配可以看註釋:
/**
*
* 跳轉到系統通知設定介面
* this.appContext 表示檔案/應用的上下文環境
*
*/
@ReactMethod
public void openSystemNoticeView(){
try {
// 跳轉到通知設定介面
Intent intent = new Intent();
intent.setAction(Settings.ACTION_APP_NOTIFICATION_SETTINGS);
//這種方案適用於 API 26, 即8.0(含8.0)以上可以用
intent.putExtra(EXTRA_APP_PACKAGE, this.appContext.getPackageName());
intent.putExtra(EXTRA_CHANNEL_ID, this.appContext.getApplicationInfo().uid);
//這種方案適用於 API21——25,即 5.0——7.1 之間的版本可以使用
intent.putExtra("app_package", this.appContext.getPackageName());
intent.putExtra("app_uid", this.appContext.getApplicationInfo().uid);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
this.appContext.startActivity(intent);
} catch (Exception e) {
e.printStackTrace();
// 出現異常則跳轉到應用設定介面:錘子
Intent intent = new Intent();
//下面這種方案是直接跳轉到當前應用的設定介面。
//https://blog.csdn.net/ysy950803/article/details/71910806
intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
Uri uri = Uri.fromParts("package", this.appContext.getPackageName(), null);
intent.setData(uri);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
this.appContext.startActivity(intent);
}
}
複製程式碼
然後我們在 JavaScript 側做一些相容處理:
import {
Linking,
Platform,
} from 'react-native';
/**
* 跳轉到 APP 訊息設定頁面
*
*/
export function openSystemNoticeSetting() {
if (Platform.OS === "android") {
NativeModules.appName.openSystemNoticeView();
} else {
Linking.openURL('app-settings:')
.catch(err => console.log('openSystemSetting error', err));
}
}
複製程式碼
需要跳轉時,我們直接用`openSystemNoticeSetting()` 這個函式就行了。
上面就是開發中遇到的兩個難點,如果此篇文章你認為對你有用,可以點個贊表示對我的鼓勵,謝謝。