使用React Native开发App时会遇到需要用到一些Android原生模块,比如:访问相册、通讯录、日历等等。下面主要是以获取Android手机通讯录的数据为例,讲解React Native Android原生模块的使用。

注:数据格式可自行做修改。
React Native Android原生模块的主要流程
在这里我将构建React Native Android原生模块的流程划分为两大步:
1.编写Android原生模块的相关Java代码;
2.注册与导出React Native原生模块
编写Android原生模块的相关Java代码
我们需要用到Android Studio。 首先我们用Android Studio打开React Native项目根目录下的android目录。项目初始化成功之后在Android Studio的工具栏中可以看到一个名为“app”的一个可运行的模块(注:用Android Studio第一次打开这个Android项目的时候,Android Studio会下载一些此项目所需要的依赖,比如项目所依赖的Gradle版本等)

接下来呢,我们开始编写Java代码。
1.创建一个ContactInfo.java(用于实现获取Android手机通讯录数据的功能)
import android.Manifest; import android.app.Activity; import android.content.ContentResolver; import android.content.Context; import android.content.pm.PackageManager; import android.database.Cursor; import android.net.Uri; import android.os.SystemClock; import android.text.TextUtils; import android.widget.Toast;
import com.facebook.react.bridge.Promise; import com.nativedemo.utils.PermissionUtils;
import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject;
import java.lang.ref.WeakReference;
public class ContactInfo { private static WeakReference<Activity> mActivity; private static Promise mPromise;
1public static void init(Activity activity) { 2 if (activity == null) return; 3 mActivity = new WeakReference<>(activity); 4} 5//获取通讯录 6public static void getContactInfo(Context context, Promise promise){ 7 if (context==null||mActivity==null){ 8 promise.reject("获取失败"); 9 return; 10 }
//权限检查(大于等于6.0版本需要动态添加) if(PermissionUtils.checkPermission(mActivity.get(),Manifest.permission.READ_CONTACTS) && PermissionUtils.checkPermission(mActivity.get(),Manifest.permission.READ_EXTERNAL_STORAGE)){ String contactInfoStr=getAllContactInfo(context); if (TextUtils.isEmpty(contactInfoStr)){ promise.reject("获取失败"); }else { promise.resolve(contactInfoStr); } }else {//请求权限(大于等于6.0版本需要动态添加) mPromise=promise; PermissionUtils.requestPermission(mActivity.get(),new String[]{Manifest.permission.READ_CONTACTS, Manifest.permission.READ_EXTERNAL_STORAGE}); }
1} 2//获取所有通讯录信息 3private static String getAllContactInfo (Context context) { 4 SystemClock.sleep(3000); 5 // ArrayList<HashMap<String, String>> list = new ArrayList<HashMap<String, String>>(); 6 JSONArray jsonArray=new JSONArray(); 7 // 1.获取内容解析者 8 ContentResolver resolver = context.getContentResolver(); 9 // 2.获取内容提供者的地址:com.android.contacts 10 // raw\_contacts表的地址 :raw\_contacts 11 // view\_data表的地址 : data 12 // 3.生成查询地址 13 Uri raw\_uri = Uri.parse("content://com.android.contacts/raw\_contacts"); 14 Uri date\_uri = Uri.parse("content://com.android.contacts/data"); 15 // 4.查询操作,先查询raw\_contacts,查询contact\_id 16 // projection : 查询的字段 17 Cursor cursor = resolver.query(raw\_uri, new String\[\]{"contact\_id"}, null, null, null); 18 try { 19 // 5.解析cursor 20 if (cursor != null) { 21 while (cursor.moveToNext()) { 22 // 6.获取查询的数据 23 String contact\_id = cursor.getString(0); 24 // cursor.getString(cursor.getColumnIndex("contact\_id"));//getColumnIndex 25 // : 查询字段在cursor中索引值,一般都是用在查询字段比较多的时候 26 // 判断contact\_id是否为空 27 if (!TextUtils.isEmpty(contact\_id)) {//null "" 28 // 7.根据contact\_id查询view\_data表中的数据 29 // selection : 查询条件 30 // selectionArgs :查询条件的参数 31 // sortOrder : 排序 32 // 空指针: 1.null.方法 2.参数为null 33 Cursor c = resolver.query(date\_uri, new String\[\]{"data1", 34 "mimetype"}, "raw\_contact\_id=?", 35 new String\[\]{contact\_id}, null); 36 //HashMap<String, String> map = new HashMap<String, String>(); 37 JSONObject object=new JSONObject(); 38 // 8.解析c 39 if (c != null) { 40 while (c.moveToNext()) { 41 // 9.获取数据 42 String data1 = c.getString(0); 43 String mimetype = c.getString(1); 44 // 10.根据类型去判断获取的data1数据并保存 45 if (mimetype.equals("vnd.android.cursor.item/phone\_v2")) { 46 // 电话 47 object.put("phone", data1); 48 } else if (mimetype.equals("vnd.android.cursor.item/name")) { 49 // 姓名 50 object.put("name", data1); 51 } 52 } 53 } 54 // 11.添加到集合中数据 55 // list.add(map); 56 jsonArray.put(object); 57 // 12.关闭cursor 58 if (c != null) { 59 c.close(); 60 } 61 } 62 } 63 } 64 } catch (JSONException e) { 65 e.printStackTrace(); 66 } finally { 67 // 12.关闭cursor 68 if (cursor != null) { 69 cursor.close(); 70 } 71 } 72 return jsonArray.toString(); 73} 74//权限回调 75public static void onRequestPermissionsResult(int requestCode, String\[\] permissions, int\[\] grantResults) { 76 if (requestCode == PermissionUtils.RC\_REQUEST\_PERMISSIONS) { 77 for (int i = 0, j = permissions.length; i < j; i++) { 78 if(TextUtils.equals(permissions\[i\],Manifest.permission.READ\_CONTACTS)|| 79 TextUtils.equals(permissions\[i\],Manifest.permission.READ\_EXTERNAL\_STORAGE)){ 80 if (grantResults\[i\] == PackageManager.PERMISSION\_GRANTED) { 81 getContactInfo(mActivity.get(),mPromise); 82 }else { 83 if(mActivity==null)return; 84 Toast.makeText(mActivity.get(),"没有使用权限",Toast.LENGTH\_SHORT).show(); 85 } 86 } 87 } 88 } 89}
}
获取手机通讯录,需要添加READ_CONTACTS和READ_EXTERNAL_STORAGE权限。Android6.0版本以下的添加权限在AndroidManifest.xml文件中添加如下:
<uses-permission android:name="android.permission.READ\_EXTERNAL\_STORAGE"/> <uses-permission android:name="android.permission.READ\_CONTACTS"/>Android6.0以上版本,部分权限则需动态添加,ContactInfo.java代码示例中有添加。其中涉及到的PermissionUtils.java文件代码如下:
(注:下面只是进行简单的封装,对于权限的管理需自行去查阅)
import android.app.Activity; import android.content.Context; import android.content.pm.PackageManager; import android.support.v4.app.ActivityCompat; import android.support.v4.content.ContextCompat;
public class PermissionUtils { public static final int RC_REQUEST_PERMISSIONS=6006; //检查是否拥有某一权限 public static boolean checkPermission(Context context,String permission){ int result = ContextCompat.checkSelfPermission(context, permission); return result == PackageManager.PERMISSION_GRANTED;
1} 2//请求获取权限 3public static void requestPermission(Activity activity, String\[\] permissions){ 4 ActivityCompat.requestPermissions(activity,permissions, RC\_REQUEST\_PERMISSIONS); 5}
}
2.在MainActivity文件中(具体路径:android/app/src/main/java/com/your-app-name/MainActivity.java)添加如下代码:
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ContactInfo.init(this); } @Override public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); ContactInfo.onRequestPermissionsResult(requestCode,permissions,grantResults); }
备注:红色区域的代码,示例中主要用于权限的添加使用
3.创建ContactInfoModule.java文件,是一个继承了**ReactContextBaseJavaModule**的Java类,实现一些JavaScript所需的功能,代码如下:
import com.facebook.react.bridge.Promise; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReactContextBaseJavaModule; import com.facebook.react.bridge.ReactMethod;
public class ContactInfoModule extends ReactContextBaseJavaModule { public ContactInfoModule(ReactApplicationContext reactContext) { super(reactContext); }
1@Override 2public String getName() { 3 return "ContactInfo"; 4} 5 6@ReactMethod 7public void getAllContactInfo(Promise promise){ 8 ContactInfo.getContactInfo(getReactApplicationContext(),promise); 9}
}
**ReactContextBaseJavaModule要求派生类实现getName**方法。这个函数用于返回一个字符串名字,这个名字在JavaScript端标记这个模块。
要导出一个方法给JavaScript使用,Java方法需要使用注解**@ReactMethod。方法的返回类型必须为void**。
下面的参数类型在**@ReactMethod**注明的方法中,会被直接映射到它们对应的JavaScript类型。
1Boolean -> Bool 2Integer -> Number 3Double -> Number 4Float -> Number 5String -> String 6Callback -> function 7ReadableMap -> Object 8ReadableArray -> Array
React Native的跨语言访问是异步进行的,所以想要给JavaScript返回一个值的唯一办法是使用回调函数或者发送事件。(示例代码中使用的是Promises,参见下面更多的描述)
注册与导出React Native原生模块
1.创建ContactInfoReactPackage.java文件,为了向React Native注册我们刚才创建的原生模块,我们需要实现**ReactPackage,ReactPackage**主要为注册原生模块所存在,只有已经向React Native注册的模块才能在js模块使用。
public class ContactInfoReactPackage implements ReactPackage { @Override public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) { List<NativeModule> modules = new ArrayList<>();
1 modules.add(new ContactInfoModule(reactContext)); 2 return modules; 3} 4 5@Override 6public List<Class<? extends JavaScriptModule>> createJSModules() { 7 return Collections.emptyList(); 8} 9 10@Override 11public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) { 12 return Collections.emptyList(); 13}
}
2.接下来呢,我们还需要在android/app/src/main/java/com/your-app-name/MainApplication.java中注册我们的ContactInfoReactPackage:
@Override protected List<ReactPackage> getPackages() { return Arrays.<ReactPackage>asList( new MainReactPackage(), new ContactInfoReactPackage() ); }
3.为了让你的功能从JavaScript端访问起来更为方便,通常我们都会把原生模块封装成一个JavaScript模块。这不是必须的,但省下了每次都从**NativeModules中获取对应模块的步骤。这个JS文件也可以用于添加一些其他JavaScript端实现的功能.创建Contacts.js**文件,代码如下:
import { NativeModules } from 'react-native'; export default NativeModules.ContactInfo;
现在,在别处的JavaScript代码中可以这样调用你的方法:
import Contacts from './Contacts'; ......省略代码 Contacts.getAllContactInfo().then(result => { this.setState({ result: result }) }).catch(e => { this.setState({ result: e }) });
更多
Callbacks
原生模块还支持一种特殊的参数——回调函数。它提供了一个函数来把返回值传回给JavaScript。
1public class UIManagerModule extends ReactContextBaseJavaModule { 2 3... 4 5 @ReactMethod 6 public void measureLayout( 7 int tag, 8 int ancestorTag, 9 Callback errorCallback, 10 Callback successCallback) { 11 try { 12 measureLayout(tag, ancestorTag, mMeasureBuffer); 13 float relativeX = PixelUtil.toDIPFromPixel(mMeasureBuffer[0]); 14 float relativeY = PixelUtil.toDIPFromPixel(mMeasureBuffer[1]); 15 float width = PixelUtil.toDIPFromPixel(mMeasureBuffer[2]); 16 float height = PixelUtil.toDIPFromPixel(mMeasureBuffer[3]); 17 successCallback.invoke(relativeX, relativeY, width, height); 18 } catch (IllegalViewOperationException e) { 19 errorCallback.invoke(e.getMessage()); 20 } 21 } 22 23...
这个函数可以在JavaScript里这样使用:
1UIManager.measureLayout( 2 100, 3 100, 4 (msg) => { 5 console.log(msg); 6 }, 7 (x, y, width, height) => { 8 console.log(x + ':' + y + ':' + width + ':' + height); 9 } 10);
原生模块通常只应调用回调函数一次。但是,它可以保存callback并在将来调用。
请务必注意callback并非在对应的原生函数返回后立即被执行——注意跨语言通讯是异步的,这个执行过程会通过消息循环来进行。
Promises
原生模块还可以使用promise来简化代码,搭配ES2016(ES7)标准的async/await语法则效果更佳。如果桥接原生方法的最后一个参数是一个Promise,则对应的JS方法就会返回一个Promise对象。
我们把上面的代码用promise来代替回调进行重构:
1import com.facebook.react.bridge.Promise; 2 3public class UIManagerModule extends ReactContextBaseJavaModule { 4 5... 6 7 @ReactMethod 8 public void measureLayout( 9 int tag, 10 int ancestorTag, 11 Promise promise) { 12 try { 13 measureLayout(tag, ancestorTag, mMeasureBuffer); 14 15 WritableMap map = Arguments.createMap(); 16 17 map.putDouble("relativeX", PixelUtil.toDIPFromPixel(mMeasureBuffer[0])); 18 map.putDouble("relativeY", PixelUtil.toDIPFromPixel(mMeasureBuffer[1])); 19 map.putDouble("width", PixelUtil.toDIPFromPixel(mMeasureBuffer[2])); 20 map.putDouble("height", PixelUtil.toDIPFromPixel(mMeasureBuffer[3])); 21 22 promise.resolve(map); 23 } catch (IllegalViewOperationException e) { 24 promise.reject(e.getMessage()); 25 } 26 } 27 28...
现在JavaScript端的方法会返回一个Promise。这样你就可以在一个声明了async的异步函数内使用await关键字来调用,并等待其结果返回。(虽然这样写着看起来像同步操作,但实际仍然是异步的,并不会阻塞执行来等待)。
1async function measureLayout() { 2 try { 3 var { 4 relativeX, 5 relativeY, 6 width, 7 height, 8 } = await UIManager.measureLayout(100, 100); 9 10 console.log(relativeX + ':' + relativeY + ':' + width + ':' + height); 11 } catch (e) { 12 console.error(e); 13 } 14} 15 16measureLayout();
发送事件到JavaScript
原生模块可以在没有被调用的情况下往JavaScript发送事件通知,最简单的办法就是通过RCTDeviceEventEmitter,这可以通过ReactContext来获得对应的引用,像这样:
1... 2private void sendEvent(ReactContext reactContext, 3 String eventName, 4 @Nullable WritableMap params) { 5 reactContext 6 .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) 7 .emit(eventName, params); 8} 9... 10WritableMap params = Arguments.createMap(); 11... 12sendEvent(reactContext, "keyboardWillShow", params);
JavaScript模块可以通过使用DeviceEventEmitter模块来监听事件:
1import { DeviceEventEmitter } from 'react-native'; 2... 3componentWillMount: function() { 4 DeviceEventEmitter.addListener('keyboardWillShow', function(e: Event) { 5 // handle event. 6 }); 7} 8...