React Native 开发笔记(长期更新)

目录
  1. 1. RN 技巧
    1. 1.1. 单独启动 android studio 模拟器(Linux)
    2. 1.2. 文字过长隐藏的问题
    3. 1.3. 获取窗体大小
    4. 1.4. 关于相对单位
    5. 1.5. 阴影效果
    6. 1.6. 组件通信
    7. 1.7. 上传文件
    8. 1.8. 拨打电话
    9. 1.9. 富文本效果
    10. 1.10. 在使用<FlatList />组件时,如果数据源是对象时,更新数据源数组里某项的某个字段的值时,页面 UI 不进行更新。
    11. 1.11. ios 系统 WebView 在底部渲染意外出现黑色边框
  2. 2. RN Bug
    1. 2.1. Android 键盘顶起底部导航的问题
    2. 2.2. iOS12 beta 版闪退
    3. 2.3. 触摸组件在 Android 和 iOS 表现不一致
    4. 2.4. ScrollView 的一些坑
    5. 2.5. 填坑 - onMessage failing when there are JS warnings and errors on the page
    6. 2.6. borderStyle: “dashed” 在安卓无效的解决方案
    7. 2.7. 某些 RN 版本的 TextInput 在 iOS 上无法输入中文的问题
    8. 2.8. watch ENOSPC (linux)
    9. 2.9. WebView 中文显示乱码的问题
    10. 2.10. android 编译时提示编译工具版本不匹配的问题
    11. 2.11. 在 windows 编译时 JS 报错说路径问题
    12. 2.12. fetch 如何设置“请求超时”
  3. 3. navigation 在登录视图的特殊处理
  4. 4. navigation 使用笔记
  5. 5. 为 R-N 配置 TypeScript 开发环境
  6. 6. 用数据线链接调试
  7. 7. 用 adb 模拟菜单键
  8. 8. RN 打包
    1. 8.1. android
    2. 8.2. 下载 android 依赖慢
    3. 8.3. 找不到 android-sdk
    4. 8.4. 打包时候报错:app:lintVitalRelease
    5. 8.5. NDK 找不到 mipsel/mips64el
  9. 9. 友盟分享、第三方登陆、推送集成
    1. 9.1. Android 端集成
    2. 9.2. iOS 端集成
      1. 9.2.1. 1、下载相关 SDK
      2. 9.2.2. 2、集成分享和授权登陆
      3. 9.2.3. 3、集成消息推送
  10. 10. 制作 Android 启动页
  11. 11. 第三方控件推荐
    1. 11.1. 字母表控件
    2. 11.2. 原生渲染富文本
    3. 11.3. 轮播
    4. 11.4. 查看大图
    5. 11.5. 图片上传、拍照、图片压缩
  12. 12. react-native Android 全面屏手机 底部留有一大块黑屏
  13. 13. Could not find com.android.tools.build:aapt2:3.2.0-alpha14-4748712
  14. 14. 为 R-N 配置 TypeScript 开发环境
  15. 15. Gradle DSL method not found

RN 技巧

单独启动 android studio 模拟器(Linux)

./emulator -netdelay none -netspeed full -avd Nexus_5X_API_25

文字过长隐藏的问题

CSS3 中大家可能都会用到 text-oveflow,然而 RN 的 Text 并没有这个属性,不过我们可以通过设置 numberOfLIne 或者 JS 自动计算来实现:

<Text numberOfLines={1}>your long text here<Text>

获取窗体大小

import { Dimensions } from "react-native";
export default {
  ScreenWidth: Dimensions.get("window").width, // 注意,如果这里的参数传递"screen",会将顶部和底部虚拟按键的高也算进去。
  ScreenHeight: Dimensions.get("window").height,
};

关于相对单位

// 模仿 web 端的 VW,VH,rem
export default {
  ScreenWidth: Dimensions.get("window").width,
  ScreenHeight: Dimensions.get("window").height,
  VW: value => (Dimensions.get("window").width / 100) * value,
  VH: value => (Dimensions.get("window").height / 100) * value,
  rem: value => (Dimensions.get("window").width / 750) * value,
};

阴影效果

android 和 ios 的阴影设置不一样,ios 可以使用 shadow 来直接设置,android 则需要用 elevation 来设置。

const styles = {
  container: {
    backgroundColor: "#fff",
    shadowColor: "#000",
    shadowOffset: { h: 10, w: 10 },
    shadowRadius: 3,
    shadowOpacity: 0.3,
    elevation: 4, // 安卓自带的阴影
  },
};

组件通信

如果是普通的组件通信可以用 props 解决,但如果是 react-navigation 等管理的组件,这招就不太好用了,比如返回时触发刷新页面等等。

解决方案有 2 种:

第一种是使用共享的状态,即 redux 这类框架去管理状态,这样状态和视图是保持同步的,不需要去告知页面需要刷新。redux 的使用方式这里不再阐述,可以看看我之前写的博文。

第二种是使用 RN 中的 DeviceEventEmitter

// 订阅事件
this.subscription = DeviceEventEmitter.addListener("reloadList", () => this.getList());
// 移出
this.subscription.remove();
// 发射事件
DeviceEventEmitter.emit("reloadList");

上传文件

以上传图片为例:

使用 RN 中自带的 fetch 方法实现

export function uploadImage(imgAry) {
  const formData = new FormData();
  for (let i = 0; i < imgAry.length; i++) {
    let file = { uri: imgAry[i], type: "multipart/form-data", name: "image.jpg" };
    formData.append("file", file); // 这里的 files 就是后台需要的 key
  }
  console.log("开始上传图片");
  return fetch(server + "/common/uploadFile.do", {
    method: "POST",
    headers: {
      "Content-Type": "multipart/form-data",
    },
    body: formData,
  }).then(response => response.json());
}

拨打电话

import { Linking } from "react-native";
function callMe() {
  return Linking.openURL("tel:10086");
}

富文本效果

RN 的Text组件 默认没有提供实现富文本效果的属性

效果图:

1.png

一开始想通过布局样式实现上图效果,但发现不行

2.png

最后通过多个Text组件来实现

<Text style={{ lineHeight: 16 }}>
  <Text style={{ fontSize: 13, color: "red" }}>{"温馨提示:  "}</Text>
  <Text
    style={{
      fontSize: Unity.SmallFont,
      marginLeft: 5,
      color: Unity.DetailColor,
    }}
  >
    {"你可以对帖子的相关问题进行提问,商家会第一时间回答您的问题!你的咨询" +
      "可能会在上方展示供其他网友参考,登录后咨询你可在个人中心-消息提醒中及时查看商家" +
      "的回复。"}
  </Text>
</Text>

在使用<FlatList />组件时,如果数据源是对象时,更新数据源数组里某项的某个字段的值时,页面 UI 不进行更新。

需要给<FlatList />组件指定 数据源

<FlatList
  extraData={this.state} // 指定 数据源
  keyExtractor={(item, index) => index.toString()}
  data={this.state.dataList}
  renderItem={data => <ListItem data={data} cellClick={item => this.cellClick(item)} />}
/>

或者在对数据源进行setState操作时,进行深复制也可以解决

ios 系统 WebView 在底部渲染意外出现黑色边框

将 WebView 的 backgroundColor 设置为透明即可

<Webview style={{backgroundColor: 'transparent'}} .../>

RN Bug

Android 键盘顶起底部导航的问题

解决方案: 打开 android 工程,在 AndroidManifest.xml 中配置如下:

 <activity
    android:name=".MainActivity"
    android:label="@string/app_name"
    android:launchMode="singleTask"
    android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
    android:windowSoftInputMode="stateAlwaysHidden|adjustPan">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>

iOS12 beta 版闪退

(使用友盟分享 SDK6.8.0 和统计版本 5.4.0 时必现的闪退)

闪退的原因:

友盟的重大 bug 导致,在使用分享 SDK6.8.0 和统计版本 5.4.0 进行注册友盟 key 的时候奔溃,必现。

(注释:使用 [UMConfigure initWithAppkey:KH_UM_APP_ID channel:@"App Store"]; 注册友盟 key,这是一个综合注册友盟平台的 API)

奔溃信息如下:

[<UIStatusBarTimeItemView 0x101c29cd0> valueForUndefinedKey:]: this class is not key value coding-compliant for the

原因是 Xcode 在 v10 之后合并了一些底层库文件,导致友盟崩溃,推荐的解决办法是去友盟升级 SDK

对应的解析日志如下:

[UMInternalUtil getSysDateFormate]

[UMInternalUtil Value2]

[UMEnvelopeBuild buildInternalData]

[UMWorkDispatch dispatchEvent:inComponent:]

解决办法:

第一种:升级友盟 SDK 中的 UMCommon.framework, 升级地址, 注:记得下载原生模块下的依赖包。

第二种:遗弃友盟 😄😄

触摸组件在 Android 和 iOS 表现不一致

在 Android 上表现为虚拟组件,即最终渲染后不存在。而 iOS 表现为实体组件。

解决方式:可以在 Android 上嵌套一个 View。

import React, { Component } from "react";
import { View, TouchableNativeFeedback, TouchableOpacity, Platform } from "react-native";

export default class Touch extends Component {
  render() {
    if (Platform.OS === "ios") {
      return (
        <TouchableOpacity activeOpacity={1} style={this.props.style} onPress={this.props.onPress}>
          {this.props.children}
        </TouchableOpacity>
      );
    } else {
      return (
        <TouchableNativeFeedback onPress={this.props.onPress}>
          <View style={this.props.style}>{this.props.children}</View>
        </TouchableNativeFeedback>
      );
    }
  }
}

ScrollView 的一些坑

第一个坑:iOS 上 ScrollView 无法直接指定尺寸,必须要通过父级 View。但 Android 上没有此问题。

第二个坑:iOS 键盘弹起时的遮挡问题

推荐使用组件: react-native-keyboard-aware-scroll-view

填坑 - onMessage failing when there are JS warnings and errors on the page

在某些低版本 RN 上会出现

注入一段补丁代码:

(function() {
  var originalPostMessage = window.postMessage;

  var patchedPostMessage = function(message, targetOrigin, transfer) {
    originalPostMessage(message, targetOrigin, transfer);
  };

  patchedPostMessage.toString = function() {
    return String(Object.hasOwnProperty).replace("hasOwnProperty", "postMessage");
  };

  window.postMessage = patchedPostMessage;
})();
const patchPostMessageFunction = function() {
  var originalPostMessage = window.postMessage;

  var patchedPostMessage = function(message, targetOrigin, transfer) {
    originalPostMessage(message, targetOrigin, transfer);
  };

  patchedPostMessage.toString = function() {
    return String(Object.hasOwnProperty).replace('hasOwnProperty', 'postMessage');
  };

  window.postMessage = patchedPostMessage;
};

const patchPostMessageJsCode = '(' + String(patchPostMessageFunction) + ')();';

...

<WebView injectedJavaScript={patchPostMessageJsCode} />

borderStyle: “dashed” 在安卓无效的解决方案

const style = {
  frame: {
    borderWidth: 1,
    borderStyle: "dashed", // 虚线样式对安卓无效
    borderColor: "#dcdcdc",
    height: 160,
    borderRadius: 0.1, // 加了 borderRadius 虚线就有效了, 原因未知
  },
};

某些 RN 版本的 TextInput 在 iOS 上无法输入中文的问题

解决方式如下,重新包装 TextInput:

import React, { Component } from "react";
import { Platform, TextInput } from "react-native";

class MyTextInput extends Component {
  shouldComponentUpdate(nextProps) {
    return (
      Platform.OS !== "ios" ||
      (this.props.value === nextProps.value && !nextProps.defaultValue) ||
      (this.props.defaultValue === nextProps.defaultValue && !nextProps.value)
    );
  }

  render() {
    return <TextInput {...this.props} />;
  }
}

export default MyTextInput;

但是这个解决方案存在值无法回填的 bug,官方解决方案(修改原生代码): https://github.com/facebook/react-native/pull/18456/files?diff=split

watch ENOSPC (linux)

linux 在启动 react-native start 后,过几秒就会出现 watch * * * * * * * * * * *ENOSPC 这个问题

解决方案:

echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf && sudo sysctl -p

或重启电脑

WebView 中文显示乱码的问题

某些安卓上出现此类问题

解决方式方式是在 source 中填写 baseUrl: '',具体原因不明。

例如

source={{html: userProtocolStr, baseUrl: ''}}

android 编译时提示编译工具版本不匹配的问题

The SDK Build Tools revision X is too low for project ‘:react-native-picker’. Minimum require X

在根目录下的 gradle 文件中加入以下代码,这样就把每个自工程的 buildToolsVersion 的版本改为要求的版本了

subprojects {
    afterEvaluate {project ->
        if (project.hasProperty("android")) {
            android {
                compileSdkVersion 25
                buildToolsVersion '25.0.0'
            }
        }
    }
}

在 windows 编译时 JS 报错说路径问题

解决方案:使用 PowerShell 编译 JS,不要使用 CMD。

fetch 如何设置“请求超时”

目前也没有很好的办法解决超时,看网上很多方案都不可靠,主流的方式是抛弃这次 http 请求。

setTimeout() 方法,抛弃这次请求。

我看到另外一种,引入一个 polyfill 的文件, 然后在自己的网络请求方法里,采用它定义的 fetch 方法,就可以设置 timeout 参数。

import fetch from "react-native-fetch-polyfill";

fetch(url, { timeout: 30 * 1000 })
  .then(response => {
    // a successful response
  })
  .catch(error => {
    // an error when the request fails, such as during a timeout
  });

navigation 在登录视图的特殊处理

https://github.com/react-navigation/react-navigation/issues/1815

由于我所做的 app 是先进入空白页面,再判断显示主页还是登录页面,空白页面上用来处理 redux 逻辑,以及显示 loading。为了防止可以退回空白页面,需要做如下处理:

const prevGetStateForAction = RootStack.router.getStateForAction;

RootStack.router.getStateForAction = (action, state) => {
  if (action.type === "Navigation/BACK" && state && state.routes[state.routes.length - 1].routeName === "Login") {
    return null;
  }
  if (action.type === "Navigation/BACK" && state && state.routes[state.routes.length - 1].routeName === "Main") {
    return null;
  }
  // Do not allow to go back to Login
  if (action.type === "Navigation/BACK" && state) {
    const newRoutes = state.routes.filter(r => r.routeName !== "Login");
    const newIndex = newRoutes.length - 1;
    return prevGetStateForAction(action, { index: newIndex, routes: newRoutes });
  }
  return prevGetStateForAction(action, state);
};

navigation 使用笔记

传送门

为 R-N 配置 TypeScript 开发环境

传送门

用数据线链接调试

adb reverse tcp:8081 tcp:8081

用 adb 模拟菜单键

adb shell input keyevent 82

RN 打包

android

先用 Java 工具生成 my-release-key.keystore

keytool -genkey -v -keystore my-release-key.keystore -alias my-key-alias -keyalg RSA -keysize 2048 -validity 10000

把 my-release-key.keystore 文件放到你工程中的 android/app 文件夹下。

打开 android/app 中的 build.gradle 文件和 android/gradle.properties

build.gradle

signingConfigs {
    release {
        storeFile file(MYAPP_RELEASE_STORE_FILE)
        storePassword MYAPP_RELEASE_STORE_PASSWORD
        keyAlias MYAPP_RELEASE_KEY_ALIAS
        keyPassword MYAPP_RELEASE_KEY_PASSWORD
    }
}

buildTypes {
    release {
        minifyEnabled enableProguardInReleaseBuilds
        proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
        signingConfig signingConfigs.release
    }
}

android/gradle.properties

MYAPP_RELEASE_STORE_FILE=my-release-key.keystore
MYAPP_RELEASE_KEY_ALIAS=my-key-alias
MYAPP_RELEASE_STORE_PASSWORD=123456
MYAPP_RELEASE_KEY_PASSWORD=123456

进入 /android/ 目录,cmd 执行 gradlew assembleRelease

下载 android 依赖慢

修改 build.gradle

// Top-level build file where you can add configuration options common to all sub-projects/modules.

buildscript {
    repositories {
        maven{ url 'http://maven.aliyun.com/nexus/content/groups/public/' }
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:2.2.3'

        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}

allprojects {
    repositories {
        maven{ url 'http://maven.aliyun.com/nexus/content/groups/public/' }
        jcenter()
        maven {
            // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm
            url "$rootDir/../node_modules/react-native/android"
        }
    }
}

找不到 android-sdk

可以在 android/local.properties 中手动配置,如果没有这个文件,手动创建一个。

## This file must *NOT* be checked into Version Control Systems,
# as it contains information specific to your local configuration.
#
# Location of the SDK. This is only used by Gradle.
# For customization when using a Version Control System, please read the
# header note.
#Mon Jul 23 17:38:05 CST 2018
sdk.dir=/media/wolfx/HDD/android/sdk

打包时候报错:app:lintVitalRelease

android/app/build.gradle 中加入 lintOptions

android {
    lintOptions {
        checkReleaseBuilds false
        abortOnError false
    }
    ...
}

NDK 找不到 mipsel/mips64el

新版 NDK 去掉了 mipsel,需要下载旧版(16b)替换。
https://developer.android.google.cn/ndk/downloads/older_releases

友盟分享、第三方登陆、推送集成

最好下载原生版本的 SDK,不要下载 RN 版本,RN 版本对新系统存在兼容问题。友盟分享的集成也是。
然后下载 GitHub 上的官方 DEMO,复制必要的文件到自己的项目。

基本配置可以参考官方,下面是接收推送的自定义参数:

Android 端集成

修改 MainActivity.java

package com.enjoybuild;

import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.util.Log;

import com.facebook.react.ReactActivity;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.modules.core.DeviceEventManagerModule;
import com.umeng.message.PushAgent;
import com.umeng.socialize.UMShareAPI;

import java.util.Set;

public class MainActivity extends ReactActivity {

    /**
     * Returns the name of the main component registered from JavaScript.
     * This is used to schedule rendering of the component.
     */
    @Override
    protected String getMainComponentName() {
        return "EnjoyBuild";
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        PushModule.initPushSDK(this);
        PushAgent.getInstance(this).onAppStart();
        ShareModule.initSocialSDK(this);
    }

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        UMShareAPI.get(this).onActivityResult(requestCode, resultCode, data);
    }

    @Override
    public void onNewIntent(Intent intent) {
        super.onNewIntent(intent);
        setIntent(intent);
    }

    @Override
    protected void onResume() {
        // 获取推送传递的参数
        // https://developer.umeng.com/docs/66632/detail/66744#h2-u81EAu5B9Au4E49u53C2u657013
        Bundle bun = getIntent().getExtras();
        if (bun != null) {
            WritableMap params = Arguments.createMap();
            Set<String> keySet = bun.keySet();
            for (String key : keySet) {
                String value = bun.getString(key);
                params.putString(key, value);
            }
            try {
                sendEvent(MyModule.reactContext, "notification", params);
            } catch (Exception e) {
                Log.e("error", e.getMessage());
            }
        }
        super.onResume();
    }

    private void sendEvent(ReactContext reactContext,
                           String eventName,
                           @Nullable WritableMap params) {
        reactContext
                .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
                .emit(eventName, params);
    }
}

MyModule.java 是自己定义的模块,为了获取 RN 的上下文。

package com.enjoybuild;

import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;

/**
 * Created by wolfx on 18-11-6.
 */

public class MyModule extends ReactContextBaseJavaModule {
    public static ReactContext reactContext;

    public MyModule(ReactApplicationContext rc) {
        super(rc);
        reactContext = rc;
    }

    @Override
    public String getName() {
        return "MyModule";
    }
}

iOS 端集成

1、下载相关 SDK

1.1、下载用于友盟集成的RN SDK

UTOOLS1557762431548.png

1.2、下载UMCommon.framework

UTOOLS1557762459680.png

注: 这个记得一定要下载 iOS 选项下面的 SDK,后面集成的时候会用到,之前按教程集成的时候没有找到这个文件,就去友盟 github 的 demo 里面复制了这个文件,结果在集成的时候由于不是最新的版本,导致在 Xcode10 上面一直闪退,崩溃提示是[<UIStatusBarTimeItemView 0x101c29cd0> valueForUndefinedKey:]: this class is not key value coding-compliant for the...,后面的朋友如果碰到 iOS 升级之后项目编译异常的也可以考虑升级 SDK 试试。

2、集成分享和授权登陆

2.1、首先进入项目的 ios 目录,进入对应的工程名目录,新建两个文件夹:UMReactBridgeUMComponents

UTOOLS1557762497479.png

2.2、将1.2步骤下载的文件iOS目录下的 common 中的 framework,拷入UMComponents中。

UTOOLS1557762526611.png

1.1步骤下载的文件iOS目录下中share目录中的最后所有文件,拷入UMComponents中的新建文件夹UMShare中,在将push文件里最后的所有文件拷入UMComponents文件夹中。看下图:

UTOOLS1557762561374.png

UTOOLS1557762586561.png

集成之后在 xcode 中的目录结构是这样的:

UTOOLS1557762607401.png

2.3、进入1.1步骤下载文件的 ReactNative 目录,找到common、 share、push目录中对应的common_ios平台中的桥接.h 和 .m文件,全部拷贝至我们项目刚刚新建的UMReactBridge文件夹中,看下图:

UTOOLS1557762648241.png

UTOOLS1557762673808.png

UTOOLS1557762692164.png

集成之后 xcode 中的目录结构为:

UTOOLS1557762708476.png

2.4、在xcode中打开工程目录,右键黄色项目名Add Files to "xxx"options 中选中Create groups 、 Copy items if needed找到我们新建的UMReactBridgeUMComponents,最后点击add添加。看下图

UTOOLS1557762724765.png

2.5、Linked Frameworks and Libraries 加入系统依赖库:可参照链接

libsqlite3.tbd
CoreGraphics.framework
SystemConfiguration.framework
CoreTelephony.framework
libc++.tbd
libz.tbd

2.6 配置 SSO 白名单,右键 info.plist 选择 source code 打开(plist 具体设置在 Build Setting -> Packaging -> Info.plist File 可获取 plist 路径)请根据选择的平台对以下配置进行裁剪:

<key>LSApplicationQueriesSchemes</key>
<array>
    <!-- 微信 URL Scheme 白名单-->
    <string>wechat</string>
    <string>weixin</string>

    <!-- 新浪微博 URL Scheme 白名单-->
    <string>sinaweibohd</string>
    <string>sinaweibo</string>
    <string>sinaweibosso</string>
    <string>weibosdk</string>
    <string>weibosdk2.5</string>

    <!-- QQ、Qzone URL Scheme 白名单-->
    <string>mqqapi</string>
    <string>mqq</string>
    <string>mqqOpensdkSSoLogin</string>
    <string>mqqconnect</string>
    <string>mqqopensdkdataline</string>
    <string>mqqopensdkgrouptribeshare</string>
    <string>mqqopensdkfriend</string>
    <string>mqqopensdkapi</string>
    <string>mqqopensdkapiV2</string>
    <string>mqqopensdkapiV3</string>
    <string>mqqopensdkapiV4</string>
    <string>mqzoneopensdk</string>
    <string>wtloginmqq</string>
    <string>wtloginmqq2</string>
    <string>mqqwpa</string>
    <string>mqzone</string>
    <string>mqzonev2</string>
    <string>mqzoneshare</string>
    <string>wtloginqzone</string>
    <string>mqzonewx</string>
    <string>mqzoneopensdkapiV2</string>
    <string>mqzoneopensdkapi19</string>
    <string>mqzoneopensdkapi</string>
    <string>mqqbrowser</string>
    <string>mttbrowser</string>
    <string>tim</string>
    <string>timapi</string>
    <string>timopensdkfriend</string>
    <string>timwpa</string>
    <string>timgamebindinggroup</string>
    <string>timapiwallet</string>
    <string>timOpensdkSSoLogin</string>
    <string>wtlogintim</string>
    <string>timopensdkgrouptribeshare</string>
    <string>timopensdkapiV4</string>
    <string>timgamebindinggroup</string>
    <string>timopensdkdataline</string>
    <string>wtlogintimV1</string>
    <string>timapiV1</string>
</array>

UTOOLS1557762776354.png

2.7、配置 URL Scheme

URL Scheme 是通过系统找到并跳转对应 app 的一类设置,通过向项目中的 info.plist 文件中加入 URL types 可使用第三方平台所注册的 appkey 信息向系统注册你的 app,当跳转到第三方应用授权或分享后,可直接跳转回你的 app。

UTOOLS1557762805820.png

2.8 初始化AppDelegate.m设置友盟appkeyappSecret(如果有推送功能的话需要加)以及各个平台的appkeysecret.

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
  /* 打开调试日志 */
  [[UMSocialManager defaultManager] openLog:YES];

  /* 设置友盟appkey */
  [RNUMConfigure initWithAppkey:@"5bd*********0db" channel:@"App Store"];
  /*
   * 关闭强制验证https,可允许http图片分享,但需要在info.plist设置安全域名
   */
  [UMSocialGlobal shareInstance].isUsingHttpsWhenShareContent = NO;

  /*
   设置微信的appKey和appSecret
   [微信平台从U-Share 4/5升级说明]http://dev.umeng.com/social/ios/%E8%BF%9B%E9%98%B6%E6%96%87%E6%A1%A3#1_1
   */
  [[UMSocialManager defaultManager] setPlaform:UMSocialPlatformType_WechatSession appKey:@"wxd******" appSecret:@"e7b246a7bdcfe75100e77eb9e999bf37" redirectURL:nil];

  /* 设置分享到QQ互联的appID
   * U-Share SDK为了兼容大部分平台命名,统一用appKey和appSecret进行参数设置,而QQ平台仅需将appID作为U-Share的appKey参数传进即可。
   100424468.no permission of union id
   [QQ/QZone平台集成说明]http://dev.umeng.com/social/ios/%E8%BF%9B%E9%98%B6%E6%96%87%E6%A1%A3#1_3
   */

  [[UMSocialManager defaultManager] setPlaform:UMSocialPlatformType_QQ appKey:@"*****"/*设置QQ平台的appID*/  appSecret:@"*******" redirectURL:@"http://mobile.umeng.com/social"];

  // Push's basic setting 推送设置!!
  UMessageRegisterEntity * entity = [[UMessageRegisterEntity alloc] init];
  //type是对推送的几个参数的选择,可以选择一个或者多个。默认是三个全部打开,即:声音,弹窗,角标
  entity.types = UMessageAuthorizationOptionBadge|UMessageAuthorizationOptionAlert|UMessageAuthorizationOptionSound;
  [UNUserNotificationCenter currentNotificationCenter].delegate=self;

  [UMessage registerForRemoteNotificationsWithLaunchOptions:launchOptions Entity:nil completionHandler:^(BOOL granted, NSError * _Nullable error) {
    if (granted) {
      NSLog(@"deviceToken+++++++++");
    } else {
    }
  }];
return YES;
}

//从支付、分享、第三方登陆等跳转会本APP时的回调,必须实现,否则收不到分享成功时的回调!!!!
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation
  {
    BOOL result = [[UMSocialManager defaultManager] handleOpenURL:url];
    if (!result) {
      // 其他如支付等SDK的回调
    }
    return result;
  }

- (BOOL)application:(UIApplication *)application handleOpenURL:(NSURL *)url
  {
    BOOL result = [[UMSocialManager defaultManager] handleOpenURL:url];
    if (!result) {
      // 其他如支付等SDK的回调
    }
    return result;
  }

2.9、提供给 RN 调用的方法在UMShareModule.m中查看.

2.10、进入下载目录的ReactNative找到common下的js中的ShareUtil.js(包含分享和登陆的方法),拷贝到我们RN目录下,即可在 RN 中导入使用分享的方法啦。

UTOOLS1557762840547.png

2.11、在 RN 中调用

import ShareUtil from './ShareUtil';
//分享
 ShareUtile.shareboard(text,img,url,title,list,(code,message) =>{
   if (code == 200){
       this.setState({result:message});
   }
 });
text 为分享内容
img 为图片地址,可以为链接,本地地址以及res图片(如果使用res,请使用如下写法:res/icon.png)
url 为分享链接,可以为空
title 为分享链接的标题
list 为分享平台数组,如:var list = [0,1,2]
callback中code为错误码,当为0时,标记成功。message为错误信息

//第三方登陆
ShareUtile.auth(0,(code,result,message) =>{
        if (code == 200){// iOS平台
          //登陆成功,返回第三方账号的信息,
          //比如openID、nickName、headPath等信息,这时就可以传给自己的后台小伙伴入库啦!!!
        }
  });

注:android 与 ios 平台成功回调中的 code 值不一致,ios 成功时 code===200,android 成功时 code===0,文档写的是 0,当时还被小坑啦一把。

3、集成消息推送

SDK 端集成已经在分享和登陆步骤中已经集成,都有截图参考,下面直接进入功能实现步骤吧:

  • 其实对于推送功能,本质还是集成原生系统端推送功能,大部分代码逻辑还是在原生端实现,比如 注册推送权限、获取 DeviceToken、设置推送提醒的类型,设置收到推送时的回调等。
  • 比较烦的是对推送证书的配置,注: 如果做推送的话必须是付费的开发者账号生成证书才可以,否则在 Xcode 里设置开启推送权限的时候会找不到该菜单项。看下图
    UTOOLS1557762871589.png
  • 具体的集成步骤以及证书生成参考友盟官网教程:
    证书配置
    集成步骤
  • 在推送的时候,可以通过设置不通的 tag 值对用户分组进行推送,也可以通过用户 id 进行精准推送,RN 也提供来对应的方法 PushUtil.addTag(tag,(code,remain) =>{ })和 PushUtil.addAlias(alias,type,(code) =>{ }),具体参考文档
  • 还有待优化的功能: 关于 iOS 如何在接收到推送后打开指定页面, 官方给的地址以及找不到了, 我自己的实现方法是在原生端收到推送消息后参考 RN 文档中原生端调用 RN 的方法使用通知来实现,期待朋友们有好的方法,期待赐教。
    推送主要代码:

AppDelegate.h页面

#import <UIKit/UIKit.h>
#import <UMPush/UMessage.h>

//!!!!一定要实现这个代理方法 :UNUserNotificationCenterDelegate
@interface AppDelegate : UIResponder <UIApplicationDelegate,UNUserNotificationCenterDelegate>

@property (nonatomic, strong) UIWindow *window;

@end

AppDelegate.m页面

#import "AppDelegate.h"
#import <React/RCTBundleURLProvider.h>
#import <React/RCTRootView.h>

#import "RNUMConfigure.h"
#import <UMShare/UMShare.h>

#import "YouMengNotice.h"

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
  // Push's basic setting
  UMessageRegisterEntity * entity = [[UMessageRegisterEntity alloc] init];
  //type是对推送的几个参数的选择,可以选择一个或者多个。默认是三个全部打开,即:声音,弹窗,角标
  entity.types = UMessageAuthorizationOptionBadge|UMessageAuthorizationOptionAlert|UMessageAuthorizationOptionSound;
// 需要实现代理,用于下面代理方法的实现。
  [UNUserNotificationCenter currentNotificationCenter].delegate=self;

  [UMessage registerForRemoteNotificationsWithLaunchOptions:launchOptions Entity:nil completionHandler:^(BOOL granted, NSError * _Nullable error) {
    if (granted) {
      NSLog(@"deviceToken+++++++++");
    } else {
    }
  }];

}

#pragma mark - Push
//在这个方法打印出 deviceToken, 便于在友盟后台添加测试设备进行测试
-(void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken{
  [UMessage registerDeviceToken:deviceToken];

  NSLog(@"deviceToken+++++++++%@",[[[[deviceToken description] stringByReplacingOccurrencesOfString: @"<" withString: @""]
                stringByReplacingOccurrencesOfString: @">" withString: @""]
               stringByReplacingOccurrencesOfString: @" " withString: @""]);
}

- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error{

}
// 次方法在iOS10以后,已经不再支持
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo
{
  //关闭友盟自带的弹出框
  [UMessage setAutoAlert:NO];
  [UMessage didReceiveRemoteNotification:userInfo];

  [YouMengNotice OCsendMessageToReactNative:userInfo];

}

//iOS10新增:处理前台收到通知的代理方法
-(void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler{
  NSDictionary * userInfo = notification.request.content.userInfo;
  if([notification.request.trigger isKindOfClass:[UNPushNotificationTrigger class]]) {

    //应用处于前台时的远程推送接受
    //关闭友盟自带的弹出框
    [UMessage setAutoAlert:NO];
    //必须加这句代码
    [UMessage didReceiveRemoteNotification:userInfo];

    // 在这里自己实现来在接收到远程推送时,主动调用RN端的,把通知内容传给RN,这样就可以控制RN收到推送时跳转到指定页面来。
    [YouMengNotice OCsendMessageToReactNative:userInfo];

  }else{
    //应用处于前台时的本地推送接受
  }
  completionHandler(UNNotificationPresentationOptionSound|UNNotificationPresentationOptionBadge|UNNotificationPresentationOptionAlert);
}

//iOS10新增:处理后台点击通知的代理方法
//这个方法是在APP接收到通知之后,点击通知栏跳转到APP时才调用的, 注意::!!如果直接点击APP是没用的
//知识点!! iOS的通知 分APP在前台时显示和点击通知栏跳转到APP显示, 如果直接点击APP的话是不会走上面的两个回调方法的,之前理解错误
-(void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)())completionHandler{
  NSDictionary * userInfo = response.notification.request.content.userInfo;
  if([response.notification.request.trigger isKindOfClass:[UNPushNotificationTrigger class]]) {

    //应用处于后台时的远程推送接受
    //必须加这句代码
    [UMessage didReceiveRemoteNotification:userInfo];
    [YouMengNotice performSelector:@selector(OCsendMessageToReactNative:) withObject:userInfo afterDelay:15];
//    [YouMengNotice OCsendMessageToReactNative:userInfo];


  }else{
    //应用处于后台时的本地推送接受
  }
}

RN 端实现:

import PushUtil from "../Tools/PushUtil.js";
import { NativeModules, NativeEventEmitter } from "react-native";
#
const { YouMengNotice } = NativeModules;
const myNativeEvt = new NativeEventEmitter(YouMengNotice);  //创建自定义事件接口

componentDidMount() {
  //收到原生端发来的推送消息,这里处理页面跳转逻辑
   myNativeEvt.addListener("OCSendToRN", (e) => {
      console.log("原生端的消息");
      msg(JSON.stringify(e));
    });
//添加tag,即分组,在推送时可以对不同分组用户进行推送
    // PushUtil.addTag("ccj",(code, res) =>{
    //   Alert.alert("提示", ""+code+JSON.stringify(res));
    // })

    PushUtil.addTag("个人用户", (code, res) => {
      // Alert.alert("提示", ""+code+JSON.stringify(res));
    });

}

制作 Android 启动页

1.首先编写样式文件 res/values/style.xml 加入:

<style name="Dialog_Fullscreen">
    <item name="android:windowTranslucentStatus">true</item>
    <item name="android:windowTranslucentNavigation">true</item>
</style>

2.编写 res/layout/activity_launch.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@drawable/ic_launcher">
</LinearLayout>

3.编写界面 SplashScreen.java

import android.app.Activity;
import android.app.Dialog;

public class SplashScreen {
    private static Dialog mSplashDialog; // 显示启动页

    public static void show(final Activity activity) {
        mSplashDialog = new Dialog(activity, R.style.Dialog_Fullscreen);
        mSplashDialog.setContentView(R.layout.activity_launch);
        mSplashDialog.setCancelable(false);
        mSplashDialog.show();
    }

    public static void hide(Activity activity) {
        if (mSplashDialog != null) {
            mSplashDialog.dismiss();
        }
        mSplashDialog = null;
    }
}

4.定义暴露给 RN 桥接的方法

import android.content.Context;

import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;

public class ModuleHideSplash extends ReactContextBaseJavaModule {
    private Context context;

    public ModuleHideSplash(ReactApplicationContext reactContext) {
        super(reactContext);
        context = reactContext;
    }

    @Override
    public String getName() {
        return "SplashScreen";
    }

    @ReactMethod
    public void show() {
        SplashScreen.show(getCurrentActivity());
    }

    @ReactMethod
    public void hide() {
        SplashScreen.hide(getCurrentActivity());
    }

}

5.定义桥接包:

import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class MyPackage implements ReactPackage {
    @Override
    public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
        return Collections.emptyList();
    }

    @Override
    public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
        List<NativeModule> modules = new ArrayList<>();
        modules.add(new ModuleHideSplash(reactContext));
        return modules;
    }
}

6.初始化桥接包:

MainApplication.java 中添加 MyPackage

@Override
protected List<ReactPackage> getPackages() {
    return Arrays.<ReactPackage>asList(
            ...,
            new MyPackage()
    );
}

7.设置启动时展示:

MainActivity.java 中加入

@Override
public void onCreate(Bundle savedInstanceState) {
    SplashScreen.show(this);
    super.onCreate(savedInstanceState);
}

8.RN 端调用

import {NativeModules} from "react-native";
...
NativeModules.SplashScreen.hide();

第三方控件推荐

字母表控件

5cd98b972ff0f35877.gif

https://github.com/i6mi6/react-native-alphabetlistview

原生渲染富文本

https://github.com/jsdf/react-native-htmlview

轮播

react-native-swiper@nart/react-native-swiper,后者是修正 Android 上无法垂直滚动的版本。

查看大图

推荐组件 react-native-image-zoom-viewer

import ImageViewer from "react-native-image-zoom-viewer";
// ...
<ImageViewer
  imageUrls={imagePathFormat(this.state.contractPicture).map(url => {
    return { url: url };
  })}
  index={this.state.imageIndex}
  enableImageZoom={true}
/>;

图片上传、拍照、图片压缩

推荐组件 react-native-image-picker

下面代码是我们项目中的代码片段,可以配合上面的文件上传代码使用。

import React, { Component } from "react";
import { Alert, Image, TouchableOpacity, View } from "react-native";
import ImagePicker from "react-native-image-picker";

// 图片选择器参数设置
const options = {
  title: "请选择图片来源",
  cancelButtonTitle: "取消",
  takePhotoButtonTitle: "拍照",
  chooseFromLibraryButtonTitle: "相册图片",
  quality: 0.7,
  maxWidth: 800,
  maxHeight: 800,

  // 此参数用于照片是否存储照片到手机相册
  // storageOptions: {
  //   skipBackup: true,
  //   path: 'images'
  // }
};

const styles = {
  frame: {
    borderTopWidth: 1,
    borderTopColor: "#707070",
    borderLeftWidth: 1,
    borderLeftColor: "#707070",
    borderRightWidth: 1,
    borderRightColor: "#707070",
    borderBottomWidth: 1,
    borderBottomColor: "#707070",
    justifyContent: "center",
    alignItems: "center",
    width: 80,
    height: 80,
  },
};

export default class MyImagePicker extends Component {
  selectImage() {
    ImagePicker.showImagePicker(options, response => this.selectImageCallback(response));
  }

  selectImageCallback(response) {
    if (response.didCancel) {
      Alert.alert("提示", "用户取消了选择!");
    } else if (response.error) {
      Alert.alert("ImagePicker发生错误:", response.error);
    } else {
      const source = { uri: response.uri };
      // You can also display the image using data:
      // let source = { uri: 'data:image/jpeg;base64,' + response.data };
      this.props.onChange(source);
    }
  }

  remove(index) {
    Alert.alert(
      "提示",
      "确认移除这张图?",
      [
        {
          text: "确认",
          onPress: () => {
            const newFiles = this.props.files;
            newFiles.splice(index, 1);
            this.props.onChange(newFiles);
          },
        },
        { text: "取消", onPress: () => {}, style: "cancel" },
      ],
      { cancelable: false },
    );
  }

  render() {
    return (
      <View style={{ flexDirection: "row" }}>
        {this.props.files.map((file, index) => {
          return (
            <TouchableOpacity onPress={() => this.remove(index)}>
              <Image
                resizeMode={"cover"}
                style={{ width: 80, height: 80, marginLeft: index === 0 ? 0 : 10, marginBottom: 10 }}
                source={{ uri: file.url }}
              />
            </TouchableOpacity>
          );
        })}
        <TouchableOpacity onPress={() => this.selectImage()}>
          {this.props.selectable && (
            <View style={[styles.frame, { marginLeft: this.props.files.length > 0 ? 10 : 0 }]}>
              <Image
                resizeMode={"contain"}
                style={{ width: 50, height: 50 }}
                source={require("../Assets/imgs/plus.png")}
              />
            </View>
          )}
        </TouchableOpacity>
      </View>
    );
  }
}

react-native Android 全面屏手机 底部留有一大块黑屏

解决方案:在 AndroidManifest.xml 中 配置

<meta-data android:name="android.max_aspect" android:value="2.1" />

Could not find com.android.tools.build:aapt2:3.2.0-alpha14-4748712

在最上级的 builde.gralde 增加谷歌库 解决问题

allprojects {
    repositories {
        google()
        jcenter()
    }
}

为 R-N 配置 TypeScript 开发环境

typescript 是 javascript 的超集,在 javascript 的基础上添加了可选的静态类型,非常适合团队开,这次我们尝试使用 typescript 来开发 react-native 应用。
搭建 react-native 开发环境

安装 yarn 和 react-native 命令行工具

npm install -g yarn react-native-cli

Yarn 是 Facebook 提供的替代 npm 的工具,可以加速 node 模块的下载。React Native 的命令行工具用于执行创建、初始化、更新项目、运行打包服务(packager)等任务。

React Native 目前需要 Xcode 7.0 或更高版本。你可以通过 App Store 或是到 Apple 开发者官网上下载。这一步骤会同时安装 Xcode IDE 和 Xcode 的命令行工具。

虽然一般来说命令行工具都是默认安装了,但你最好还是启动 Xcode,并在 Xcode | Preferences | Locations 菜单中检查一下是否装有某个版本的 Command Line Tools。Xcode 的命令行工具中也包含一些必须的工具,比如 git 等。
搭建 typescript 开发环境

先安装 typescript

npm install -g typescript

接下来安装 typings
typings 是 typescript 的依赖管理器,如果你使用 sublime text 或者 vscode,会非常方便的补全代码

npm install -g typings

typings install 安装缓慢:追加参数 --registry=https://registry.npm.taobao.org 解决

使用 react-native 命令行工具初始化 react-native 项目

react-native init ReactNativeApp

等待片刻后,进入刚刚新建的项目,创建一个名为”tsconfig.json” 的文件。tsconfig.json 是一个 typescript 项目的配置文件,可以通过读取它来设置 ts 编译器的编译参数
内容如下:

{
  "compilerOptions": {
    "target": "es6",
    "allowJs": true,
    "jsx": "react",
    "outDir": "./dist",
    "sourceMap": true,
    "noImplicitAny": false
  },
  "include": ["typings/**/*.d.ts", "src/**/*.ts", "src/**/*.tsx"],
  "exclude": ["node_modules"]
}

在项目下新建一个目录”src”,typescripe 源代码就放在这里

现在安装 typings 依赖

typings install npm~react --save
typings install dt~react-native --globals --save

编写 Hello world

在 src 目录下新建 myview.tsx,内容如下

import * as React from "react";
import { Text } from "react-native";

/**
 * Hello
 */
export default class Hello extends React.Component<null, null> {
  render() {
    return <Text>Hello world!</Text>;
  }
}

返回根目录,编译刚刚写好的 tsx 文件

tsc

修改 index.ios.js

import React, { Component } from "react";
import Hello from "./dist/myview";
import { AppRegistry, StyleSheet, Text, View } from "react-native";

export default class ReactNativeApp extends Component {
  render() {
    return (
      <View style={styles.container}>
        <Hello />
      </View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: "center",
    alignItems: "center",
    backgroundColor: "#F5FCFF",
  },
});

AppRegistry.registerComponent("ReactNativeApp", () => ReactNativeApp);

运行 run-ios 试试效果

react-native run-ios

Gradle DSL method not found

错误提示:

Gradle DSL method not found: 'compileOnly()' or Gradle DSL method not found: 'implementation()'

解决方式有两种:

  • 升级 gradle 版本
  • 把对应的方法更改成当前 gradle 中的方法。比如,compileOnly()implementation() 都可以换成compile()