React Native 跳轉到 APP 推送頁面並獲取推送狀態

產品爲了提升推送送達率,提了一個需求:在 APP 推送關閉的狀況下顯示一個小 TIP,點擊 TIP 跳轉到 APP 消息設置界面javascript


咱們的 APP 是基於 React Native 開發的,這些功能 Facebook 官方沒有提供,須要咱們開發對應的原生模塊。java

由於開發原生模塊屬於比較深刻的內容了,寫這篇文章時我就默認閱讀者已經具備必定的 Objective-CJava 開發能力,下面就直接貼代碼說思路了。react


開發一個原生模塊的基礎知識能夠直接看官方文檔,寫的很詳細,我這裏就很少重複了。android

React Native 開發 Android 原生模塊ios

React Native 開發 iOS 原生模塊git


下面開始分析實現。github


第一步:獲取 APP 推送狀態

這裏我主要參考極光推送。由於公司內部有統一的推送 SDK(主要整合了市面上多家推送服務公司和手機廠商的推送服務),一些極光推送很方便的功能暫時用不了,只能本身參考實現。objective-c


在個人實現裏,獲取 APP 推送狀態主要作了兩件事:
  1. 兼容多個系統版本(這部分都是極光推送開發者的功勞);
  2. Promise 的形式進行封裝(極光推送是基於 callback 的)

getSystemNoticeStatus() 這個函數,在 APP 推送開啓的狀況下返回 true,未開啓狀況返回 falsereact-native


iOS 代碼以下:promise

參考連接:github.com/jpush/jpush…

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 代碼以下:

參考連接:

github.com/jpush/jpush…

/** * 獲取 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 APP 設置主頁面,右圖是 iOS 推送設置子頁面

首先 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()` 這個函數就好了。



上面就是開發中遇到的兩個難點,若是此篇文章你認爲對你有用,能夠點個贊表示對個人鼓勵,謝謝。

相關文章
相關標籤/搜索