這里有一個(gè)引導(dǎo) native-modules-setup,該引導(dǎo)適用于 android/iOS余黎,其實(shí)就是自動(dòng)創(chuàng)建原生模塊的模板哩簿,之后再去實(shí)現(xiàn)自己的邏輯就好了。
可以查看 native-modules-android 【翻譯 】耕赘、native-modules-ios【翻譯】 來(lái)了解 android 、IOS 下模塊開(kāi)發(fā)的一些 API 和 開(kāi)發(fā)流程膳殷。
官網(wǎng)給了一個(gè) Toasts 的簡(jiǎn)單示例操骡,這里就不用這個(gè)了,直接實(shí)戰(zhàn)化赚窃,以集成友盟 SDK 作為例子(備注:以下過(guò)程是在 rn 0.61 版本下進(jìn)行的册招,版本不同,可能過(guò)程也有不同)勒极。
友盟的產(chǎn)品有很多是掰,統(tǒng)計(jì)/推送/分享/廣告監(jiān)測(cè)等等,分享和廣告監(jiān)測(cè)不太是剛需辱匿,且會(huì)和其他功能交叉(比如分享冀惭,集成微信分享的同時(shí)一般還會(huì)集成支付功能)所有就不集成了,這里僅集成 統(tǒng)計(jì)/推送 功能掀鹅。
目前行業(yè)現(xiàn)狀:
統(tǒng)計(jì),目前國(guó)內(nèi)用的較多的是 友盟 和 騰訊移動(dòng)分析媒楼,二者在功能上乐尊,后者略有勝出,但也僅僅是略有勝出划址;
推送扔嵌,最好用的當(dāng)然是手機(jī)廠商的系統(tǒng)級(jí)推送通道限府,目前華為/小米/OPPO/vivo/魅族 都支持,這基本可以覆蓋國(guó)內(nèi) 70% (市場(chǎng)份額) 以上的機(jī)器了痢缎,第三方推送主要是處理剩余那 30% 的胁勺,據(jù)我個(gè)人了解,國(guó)內(nèi)口碑相對(duì)可以的是 極光 和 友盟独旷,況且友盟從 CNZZ 起家就開(kāi)始做統(tǒng)計(jì)了署穗,技術(shù)積累還是可以的。最后再考慮到注冊(cè)一個(gè)平臺(tái)就能用兩個(gè)功能了嵌洼,不用在不同平臺(tái)切換案疲,所以推送功能也選友盟。
一麻养、創(chuàng)建項(xiàng)目
// 安裝 create-react-native-module
yarn global add create-react-native-module
// 創(chuàng)建 rn 項(xiàng)目
react-native init sample
// 進(jìn)入 rn 目錄創(chuàng)建 module
cd sample
create-react-native-module uapp
打開(kāi) sample 文件夾可以看到已創(chuàng)建了一個(gè) react-native-uapp
文件夾褐啡,打開(kāi)這個(gè)文件夾看看目錄結(jié)構(gòu)
- android
- index.js
- ios
- package.json
- react-native-uapp.podspec
- README.md
很貼心,連 readme 都幫忙創(chuàng)建好了鳖昌,不過(guò)我看了一下备畦,介紹主要還是針對(duì) rn6.0 以下版本的,后面還是要自己寫(xiě)點(diǎn)使用方法的许昨,暫且不管了懂盐,下面的主要工作就是在 android 和 ios 兩個(gè)文件夾進(jìn)行了,最后搞一下 index.js 對(duì)外提供接口车要。
二允粤、Android
1、修改命名空間
先來(lái)做 android 版本的翼岁,進(jìn)入 android 文件夾类垫,還有一個(gè) readme, 打開(kāi)看一下琅坡,是介紹如何打包為 maven 依賴的悉患,這個(gè)就沒(méi)必要了,可以刪了榆俺。按照慣例和自己準(zhǔn)備用名稱售躁,先改一下 android 的命名空間。
修改
src/main/java/com/reactlibrary/UappModule.java
src/main/java/com/reactlibrary/UappPackage.java
頭部從 package com.reactlibrary;
改為 package com.umreact.uapp;
移動(dòng)文件夾:
src/main/java/com/reactlib/
-> src/main/java/com/umreact/uapp
最后修改
build.gradle
和 src/main/AndroidManifest.xml
茴晋,查找 com.reactlibrary
改為 com.umreact.uapp
2陪捷、安裝本地包
使用 rn 一般都是用 yarn
來(lái)管理包的,所以我這里用 yarn
來(lái)安裝本地包诺擅,如果你使用 npm
自行搜索一下市袖,應(yīng)該都是差不多的,在 sample
目錄執(zhí)行
yarn add file:./react-native-uapp
這樣就把這個(gè)本地包裝上了烁涌,對(duì)于 android 而言苍碟,由于 rn 0.60 之后酒觅,是 autolink 的,所以無(wú)需其他操作了微峰,可以先 react-native run-android
試一下舷丹,看能不能編譯成功。
【注意】: 此時(shí)真正使用的 react-native-uapp
目錄是在 node_modules 下蜓肆,yarn add
命令自動(dòng)復(fù)制了颜凯,所以后續(xù)的原生開(kāi)發(fā)應(yīng)該是在 node_modules/react-native-uapp
目錄下。但實(shí)際使用的 js 模塊卻還在 file:./react-native-uapp
目錄下
3症杏、設(shè)置友盟 sdk
第一步是修改 android/build.gradle
装获, 先來(lái)看看這個(gè)文件的結(jié)構(gòu)
// 內(nèi)置函數(shù), 用于安全獲取當(dāng)前使用的 android sdk 版本
// 嘗試使用主工程版本, 若無(wú), 則指定一個(gè)默認(rèn)版本
// 在 `android` 塊的配置中有使用
def safeExtGet(prop, fallback) {
..
}
// 把當(dāng)前包作為獨(dú)立工程的編譯配置
// 就是直接在當(dāng)前插件目錄執(zhí)行 `yarn install`,然后編譯當(dāng)前目錄
buildscript {
...
}
apply plugin: 'com.android.library'
apply plugin: 'maven'
// 當(dāng)前插件的編譯配置
android {
....
}
// 也是作為獨(dú)立工程的配置
// 關(guān)于 buildscript 和 repositories 和參考
// http://www.reibang.com/p/ee57e4de78a3
repositories {
....
}
// 當(dāng)前插件的依賴
dependencies {
...
}
// 在 afterEvaluate 中用到了
// 解析 package.json 中的包信息(名稱厉颤,作者穴豫,協(xié)議等)
def configureReactNativePom(def pom) {
....
}
// Gradle 構(gòu)建生命周期的鉤子
// 對(duì) android 不是特別熟悉,看這部分代碼像是打包逼友,可以發(fā)布到線上 lib 庫(kù)的
afterEvaluate { project ->
....
}
了解這個(gè)文件結(jié)構(gòu)后精肃,來(lái)改一下,就拿來(lái)做插件的帜乞,不需要做獨(dú)立工程司抱,可以去除相關(guān)代碼;也不需要發(fā)布為lib黎烈,所以也可以去除相關(guān)代碼习柠;當(dāng)然,這個(gè)修改不是必須的照棋,保留這些代碼也沒(méi)什么副作用资溃。
根據(jù)友盟的文檔 基礎(chǔ)組件集成、U-Push 集成 烈炭、U-App 集成 可以看出溶锭,集成 sdk 有自動(dòng)和手動(dòng)兩種模式,自動(dòng)使用 https://dl.bintray.com/umsdk/release
作為 maven 中心庫(kù)符隙,手動(dòng)則是在 SDK下載 頁(yè)面下載 sdk趴捅,我這里選擇自動(dòng)化集成,后期維護(hù)升級(jí)也必將方便霹疫;找到需要集成的 sdk拱绑,有:
com.umeng.umsdk:utdid:version
com.umeng.umsdk:common:version
com.umeng.umsdk:analytics:version
com.umeng.umsdk:push:version
可以在 https://dl.bintray.com/umsdk/release/ 查看當(dāng)下的最新版本,最終修改 build.gradle
的代碼為
def safeExtGet(prop, fallback) {
rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
}
def safeCfgGet(prop, fallback) {
return project.hasProperty(prop) ? project.getProperties().get(prop) : fallback
}
apply plugin: 'com.android.library'
android {
compileSdkVersion safeExtGet('compileSdkVersion', 28)
buildToolsVersion safeExtGet('buildToolsVersion', "28.0.3")
defaultConfig {
minSdkVersion safeExtGet('minSdkVersion', 16)
targetSdkVersion safeExtGet('targetSdkVersion', 28)
versionCode 1
versionName "1.0"
# 創(chuàng)建一個(gè)文件設(shè)置 與模塊相關(guān)的混淆規(guī)則
consumerProguardFiles "proguard.pro"
// +新增丽蝎,下面會(huì)進(jìn)行說(shuō)明
buildConfigField("int", "UMENG_DEVICE_TYPE", safeCfgGet("UMENG_DEVICE_TYPE", "1"))
buildConfigField("String", "UMENG_PUSH_SECRET", safeCfgGet("UMENG_PUSH_SECRET", "\"\""))
}
lintOptions {
abortOnError false
}
}
# 添加友盟的線上倉(cāng)庫(kù)地址
rootProject.allprojects {
repositories {
maven { url 'https://dl.bintray.com/umsdk/release' }
}
}
dependencies {
implementation 'com.facebook.react:react-native:+'
implementation 'com.umeng.umsdk:utdid:1.1.5.3'
implementation 'com.umeng.umsdk:common:2.1.0'
implementation 'com.umeng.umsdk:analytics:8.1.3'
implementation 'com.umeng.umsdk:push:6.0.1'
}
4猎拨、開(kāi)發(fā)
src/main/java/com/reactlibrary/UappPackage.java
無(wú)需改動(dòng)
主要是修改 src/main/java/com/reactlibrary/UappModule.java
通過(guò) 統(tǒng)計(jì)集成、推送集成 、RN 生命周期事件 可以了解到
載入的 sdk 需要在
application.onCreate
階段初始化統(tǒng)計(jì)模塊需要在 Activity 的 onResume 和 onPause 觸發(fā)統(tǒng)計(jì) 按照文檔其實(shí)在 auto 模式下是無(wú)需這一步的迟几,但實(shí)測(cè)了一下,發(fā)現(xiàn) auto 模式有問(wèn)題)
3.推送模塊需要在Activity 的 onCreate 調(diào)用 PushAgent.getInstance(context).onAppStart();
- RN 可以通過(guò)
addLifecycleEventListener
在 Module 中添加 active 生命周期的監(jiān)聽(tīng)函數(shù)
// context - application 上下文
// appkey - 友盟申請(qǐng)得到的 app key
// channel - 渠道栏笆,方便后期統(tǒng)計(jì)查看(比如小米商店类腮、華為商店),可以為不同市場(chǎng)打包不同apk
// deviceType - 設(shè)備類型蛉加,支持手機(jī)或平板
// pushSecret - 推送秘鑰
UMConfigure.init(Context context, String appkey, String channel, int deviceType, String pushSecret);
// 少了 appkey/channel蚜枢,使用這個(gè)調(diào)用的前提是
// 在AndroidManifest.xml中配置過(guò)appkey和channel值
UMConfigure.init(Context context, int deviceType, String pushSecret);
從初始化函數(shù)調(diào)用可以看到, appkey针饥、channel厂抽、deviceType、pushSecret 作為配置信息丁眼,肯定不能在模塊中定義的筷凤,需要在主工程中設(shè)置,主工程要如何傳遞參數(shù)呢苞七,找到了幾篇有幫助的文章:gradle-tips藐守、buildConfigField 和 gradle.properties
從這三篇文章可以看出,傳遞參數(shù)可以通過(guò) android 項(xiàng)目根目錄的 build.gradle
或 gradle.properties
蹂风,這兩個(gè)都是頂級(jí)配置文件卢厂,任何子工程(插件)都可以讀取配置值,很明顯的惠啄, gradle.properties
作為 kv 配置文件慎恒,特別適合做這個(gè)。
看完這三篇文章撵渡,應(yīng)該很容易理解上面 build.gradle
中 buildConfigField()
的作用了融柬,就是為了可以在 gradle.properties
配置友盟參數(shù),但沒(méi)有設(shè)置 appkey
和 channel
姥闭,因?yàn)檫@兩個(gè)可以直接在 AndroidManifest.xml
中設(shè)置丹鸿,并且這樣更容易進(jìn)行分渠道打包,如果有興趣棚品,可以看看 build-variants
結(jié)合上面的信息靠欢,最終修改 UappModule.java
package com.umreact.uapp;
import android.content.Context;
import com.facebook.react.bridge.Callback;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.LifecycleEventListener;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import android.util.Log;
import com.umeng.message.PushAgent;
import com.umeng.commonsdk.UMConfigure;
import com.umeng.analytics.MobclickAgent;
import com.umeng.message.IUmengRegisterCallback;
public class UappModule extends ReactContextBaseJavaModule implements LifecycleEventListener {
public static void init(Context appContext) {
// 開(kāi)啟 debug
UMConfigure.setLogEnabled(true);
// 初始化友盟
UMConfigure.init(appContext, BuildConfig.UMENG_DEVICE_TYPE, BuildConfig.UMENG_PUSH_SECRET);
// 設(shè)置 session 時(shí)長(zhǎng) (這里為了調(diào)試設(shè)置的比較小, 可以不設(shè)置,保持默認(rèn))
MobclickAgent.setSessionContinueMillis(1000);
// 設(shè)置統(tǒng)計(jì)模式 (不使用 auto 模式, 手工觸發(fā))
MobclickAgent.setPageCollectionMode(MobclickAgent.PageMode.MANUAL);
// 打印 測(cè)試統(tǒng)計(jì)設(shè)備 id
String[] devices = UMConfigure.getTestDeviceInfo(appContext);
Log.i("UMLog", "did:" + devices[0]);
Log.i("UMLog", "mac:" + devices[1]);
Log.i(
"UMLog",
"info:" + "{\"device_id\":\""+devices[0]+"\",\"mac\":\""+devices[1]+"\"}"
);
// 注冊(cè)推送
final PushAgent mPushAgent = PushAgent.getInstance(appContext);
mPushAgent.register(new IUmengRegisterCallback() {
@Override
public void onSuccess(String deviceToken) {
Log.i("UMLog","注冊(cè)成功:deviceToken:--------> " + deviceToken);
}
@Override
public void onFailure(String s, String s1) {
Log.e("UMLog","注冊(cè)失斖堋:--------> " + "s:" + s + ",s1:" + s1);
}
});
}
/**
* 不做特殊開(kāi)發(fā), 一般 rn 就一個(gè) MainActivity, 但這里也留一個(gè)接口
* 如果有多個(gè) active 的話, 在 active 的 onCreate 調(diào)用
* https://developer.umeng.com/docs/66632/detail/98581
* @param activity
*/
public static void onAppStart(Context activity) {
PushAgent.getInstance(activity).onAppStart();
}
@Override
public String getName() {
return "Uapp";
}
public UappModule(ReactApplicationContext reactContext) {
super(reactContext);
reactContext.addLifecycleEventListener(this);
// 這里調(diào)用, 設(shè)備總是處于離線狀態(tài), 無(wú)法接收推送消息
// https://developer.umeng.com/docs/66632/detail/98581
// 官方文檔提到: 務(wù)必在 Application類的 onCreate() 方法中做SDK代碼初始化工作
// init(reactContext.getApplicationContext());
}
@Override
public void onHostResume() {
MobclickAgent.onResume(getCurrentActivity());
}
@Override
public void onHostPause() {
MobclickAgent.onPause(getCurrentActivity());
}
@Override
public void onHostDestroy() {
// do nothing
}
}
代碼中調(diào)用的 BuildConfig.UMENG_DEVICE_TYPE
和 BuildConfig.UMENG_PUSH_SECRET
是在 gradle.properties
文件中配置的
UMENG_DEVICE_TYPE=1
UMENG_PUSH_SECRET="666"
appkey门怪、channel 則在主工程的 AndroidManifest.xml
中配置
<manifest ..>
....
// 添加友盟所需基本權(quán)限
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
<application ...>
....
<meta-data android:value="666" android:name="UMENG_APPKEY"/>
<meta-data android:value="Test" android:name="UMENG_CHANNEL"/>
...
</application>
...
</manifest>
還需要在 android/src/main/.../MainApplication.java
添加一行
...
import com.umreact.uapp.UappModule;
public class MainApplication extends Application implements ReactApplication {
...
@Override
public void onCreate() {
..
// 添加這一行
UappModule.init(this);
..
}
...
}
配置完成后,運(yùn)行 app锅纺;
1掷空、 從 android 的 log 中獲取到 {"device_id":"xxx","mac":"xxx"}
到 友盟測(cè)試設(shè)備 去添加一下,然后在 實(shí)時(shí)日志 查看是否成功統(tǒng)計(jì),沒(méi)統(tǒng)計(jì)的話坦弟,可以嘗試重新運(yùn)行一次 app护锤。
2、從 Log 中找到 deviceToken
酿傍,到 友盟推送工具 查詢?cè)O(shè)備狀態(tài)烙懦,如果是在線狀態(tài),可以到 測(cè)試模式 這里添加測(cè)試設(shè)備赤炒,并發(fā)送一條推送試一下氯析。
獲取 android log 有很多辦法,我們需要 filter 的關(guān)鍵詞為 UMLog
1莺褒、命令行: adb logcat *:S UMLog:V
2掩缓、打開(kāi) Android Studio 的 Logcat 窗口
3、使用 FB 的 flipper (這個(gè)可能需要 rn 版本大于0.60)
5遵岩、后記
最終的擴(kuò)展使用方法為
1你辣、在 gradle.properties
設(shè)置 UMENG_DEVICE_TYPE
和 UMENG_PUSH_SECRET
2、在 android/src/main/AndroidManifest.xml
設(shè)置 UMENG_APPKEY
和 UMENG_CHANNEL
3旷余、本想除友盟信息外零配置绢记,但奈何 android 開(kāi)發(fā)沒(méi)有經(jīng)驗(yàn),還需在 android/src/main/.../MainApplication.java
調(diào)用初始化方法
另外關(guān)于 MainApplication.java
中的配置正卧,可以看在 UappModule.java
的 UappModule()
函數(shù)的注釋蠢熄,一開(kāi)始是想在這里直接載入的,這樣就少了一個(gè)配置炉旷,但實(shí)測(cè)發(fā)現(xiàn)签孔,這樣做的話,可以注冊(cè)成功窘行,但設(shè)備總處于離線狀態(tài)饥追,無(wú)法接收推送消息变泄;并且在 推送接入文檔 中也有說(shuō)明:務(wù)必在工程的自定義Application類的 onCreate() 方法中做SDK代碼初始化工作幢码。所以最終也不再深究了,就先這么著吧(其實(shí)排查設(shè)備離線扶供,定位到是這里的問(wèn)題惶看,就花了我好幾個(gè)鐘頭捏顺,實(shí)在懶得繼續(xù)搞了)
上面的流程只是跑通了統(tǒng)計(jì)/推送功能,做一個(gè)能用的擴(kuò)展其實(shí)還有不少活要干纬黎,比如頁(yè)面統(tǒng)計(jì)幅骄、埋點(diǎn)統(tǒng)計(jì)、廠商推送通道集成本今、接收消息后的處理拆座、通過(guò) js 暴露接口給 rn jsx 使用主巍,本篇主要是為了記錄一下原生模塊開(kāi)發(fā)的流程,所有就不過(guò)多著墨了
三挪凑、iOS
待填坑....