今天記錄一下我的一個React Native Demo:
具體實現了對Android原生UI的封裝和Android原生模塊的封裝,並且集成騰訊雲點播的Android SDK。
功能:第一個頁面顯示騰訊雲點播的視頻播放控件,並播放一段視頻。點擊視頻控件跳轉到第二個頁面。
步驟:
一、創建React Native項目;
react-native init TestDemo
二、封裝Android原生模塊;
在TestDemo/android下創建一個GoToActivity.java文件,此類繼承ReactContextBaseJavaModule,實現其中的方法。
三、封裝Android原生UI;
在TestDemo/android下創建一個自定義View,命名爲CustomView;
再創建一個ReactCustomViewManager.java文件,此類繼承SimpleViewManager<CustomView>,泛型是我上面自定義的View,目的就是封裝這個自定義View,給React Native使用。
四、把寫好的原生模塊、原生UI進行註冊;
在TestDemo/android下創建一個MyReactPackage.java文件,此類繼承ReactPackage,實現其中的方法,在createNativeModules方法中註冊原生模塊,在createViewManagers方法中註冊原生UI。然後把MyReactPackage註冊到MyApplication中getPackages方法中。
五、下載騰訊雲點播Android SDK(下載地址:http://download-1252463788.cossh.myqcloud.com/RTMPSDKAndroid2.0.2.2801.zip);
將SDK的jniLibs文件夾拷貝到TestDemo/android/app/src/main/下;
導入jar包,在Android Studio工程中找到剛纔的jniLibs目錄,展開目錄,可以看到txrtmpsdk.jar,點擊右鍵選擇“Add As Library...”;
在AndroidManifest.xml中配置APP的權限,音視頻類APP一般需要以下權限:
<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" />
<uses-permission android:name="android.permission.CALL_PHONE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_LOGS" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-feature android:name="android.hardware.Camera"/>
<uses-feature android:name="android.hardware.camera.autofocus" />
六、在自定義View--CustomView的佈局中加入騰訊雲點播的播放控件com.tencent.rtmp.ui.TXCloudVideoView;
七、在JS端封裝原生模塊、原生UI,然後寫一個頁面進行測試。
源代碼如下:
MainActivity.java
package com.yb;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import com.facebook.react.ReactActivity;
import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;
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 "yb";
}
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
EventBus.getDefault().register(this);
}
@Subscribe(threadMode = ThreadMode.MAIN)
public void getMqttMessage(String mqttMessage) {
Log.d("123pp", mqttMessage);
startActivity(new Intent(this, SecondActivity.class));
}
@Override
protected void onDestroy()
{
EventBus.getDefault().unregister(this);
super.onDestroy();
}
}
MainApplication.java
package com.yb;
import android.app.Application;
import com.facebook.react.ReactApplication;
import com.facebook.react.ReactNativeHost;
import com.facebook.react.ReactPackage;
import com.facebook.react.shell.MainReactPackage;
import com.facebook.soloader.SoLoader;
import java.util.Arrays;
import java.util.List;
public class MainApplication extends Application implements ReactApplication
{
private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this)
{
@Override
public boolean getUseDeveloperSupport()
{
return BuildConfig.DEBUG;
}
@Override
protected List<ReactPackage> getPackages()
{
return Arrays.<ReactPackage>asList(
new MainReactPackage(),
new MyReactPackage()
);
}
};
@Override
public ReactNativeHost getReactNativeHost()
{
return mReactNativeHost;
}
@Override
public void onCreate()
{
super.onCreate();
SoLoader.init(this, /* native exopackage */ false);
}
}
GoToActivity.java
package com.yb;
import com.facebook.react.ReactActivity;
import android.widget.Toast;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import org.greenrobot.eventbus.EventBus;
import java.util.Map;
import java.util.HashMap;
public class GoToActivity extends ReactContextBaseJavaModule
{
private static final String DURATION_SHORT_KEY = "SHORT";
private static final String DURATION_LONG_KEY = "LONG";
public GoToActivity(ReactApplicationContext reactContext)
{
super(reactContext);
}
/**
* getName方法。這個函數用於返回一個字符串名字,就是js中的模塊名
*/
@Override
public String getName()
{
return "GoToActivity";
}
/**
* 返回了需要導出給JavaScript使用的常量
*/
@Override
public Map<String, Object> getConstants()
{
final Map<String, Object> constants = new HashMap<>();
constants.put(DURATION_SHORT_KEY, Toast.LENGTH_SHORT);
constants.put(DURATION_LONG_KEY, Toast.LENGTH_LONG);
return constants;
}
/**
* 導出給js使用的方法,需要使用註解@ReactMethod。方法的返回類型必須爲void
*/
@ReactMethod
public void show(String message, int duration)
{
Toast.makeText(getReactApplicationContext(), message, duration).show();
EventBus.getDefault().post(message);
}
}
CustomView.java
package com.yb;
import android.content.Context;
import android.graphics.Color;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.widget.RelativeLayout;
import android.widget.TextView;
import com.tencent.rtmp.ui.TXCloudVideoView;
/**
* Created by Auser on 2017/5/2.
*/
public class CustomView extends RelativeLayout
{
private TextView textView;
private TXCloudVideoView txCloudVideoView;
public CustomView(Context context)
{
super(context);
init(context);
}
public CustomView(Context context, AttributeSet attrs)
{
super(context, attrs);
init(context);
}
public CustomView(Context context, AttributeSet attrs, int defStyleAttr)
{
super(context, attrs, defStyleAttr);
init(context);
}
private void init(Context context) {
LayoutInflater.from(context).inflate(R.layout.customview_layout, this, true);
textView = (TextView) this.findViewById(R.id.tv01);
textView.setText("我是一個CustomView");
textView.setTextColor(Color.parseColor("#ff0000"));
txCloudVideoView = (TXCloudVideoView) this.findViewById(R.id.video_view);
}
public void setText(String txt) {
this.textView.setText(txt);
}
public String getText() {
return this.textView.getText().toString();
}
public TXCloudVideoView getTxCloudVideoView()
{
return txCloudVideoView;
}
public void setTxCloudVideoView(TXCloudVideoView txCloudVideoView)
{
this.txCloudVideoView = txCloudVideoView;
}
}
MyReactPackage.java
package com.yb;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.JavaScriptModule;
import com.facebook.react.uimanager.ViewManager;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Collections;
public class MyReactPackage implements ReactPackage
{
@Override
public List<NativeModule> createNativeModules(
ReactApplicationContext reactContext)
{
List<NativeModule> modules = new ArrayList<>();
modules.add(new GoToActivity(reactContext));
return modules;
}
@Override
public List<Class<? extends JavaScriptModule>> createJSModules()
{
// TODO Auto-generated method stub
return Collections.emptyList();
}
@Override
public List<ViewManager> createViewManagers(
ReactApplicationContext reactContext)
{
return Arrays.<ViewManager>asList(
new ReactCustomViewManager()
);
}
}
ReactCustomViewManager.java
package com.yb;
import android.support.annotation.Nullable;
import android.util.Log;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import com.facebook.react.uimanager.SimpleViewManager;
import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.uimanager.annotations.ReactProp;
import com.tencent.rtmp.TXLivePlayer;
/**
* Created by YiBing on 2017/4/28.
*/
public class ReactCustomViewManager extends SimpleViewManager<CustomView>
{
ThemedReactContext context;
public static final String REACT_CLASS = "RCTCustomView";
@Override
public String getName() {
return REACT_CLASS;
}
@Override
protected CustomView createViewInstance(ThemedReactContext reactContext) {
this.context = reactContext;
CustomView customView = new CustomView(reactContext);
return customView;
}
@ReactProp(name = "url")
public void setUrl(CustomView customView,@Nullable String url) {
Log.e("TAG", "setUrl");
customView.setText(url);
TXLivePlayer txLivePlayer = new TXLivePlayer(context);
txLivePlayer.setPlayerView(customView.getTxCloudVideoView());
txLivePlayer.startPlay(url, TXLivePlayer.PLAY_TYPE_VOD_MP4);
}
}
SecondActivity.java
package com.yb;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import com.tencent.rtmp.ui.TXCloudVideoView;
import com.tencent.rtmp.TXLivePlayer;
public class SecondActivity extends AppCompatActivity
{
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second);
}
}
activity_second.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/tv01"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Second Activity"
android:gravity="center_horizontal"/>
</RelativeLayout>
customview_layout.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/tv01"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Hello world!"
android:gravity="center_horizontal"/>
<com.tencent.rtmp.ui.TXCloudVideoView
android:id="@+id/video_view"
android:layout_below="@id/tv01"
android:layout_marginTop="8dp"
android:layout_width="match_parent"
android:layout_height="300dp"
android:layout_centerInParent="true"
android:visibility="gone"/>
</RelativeLayout>
CustomView.js
/**
* Created by YiBing on 2017/4/28.
* react-native: 0.43.3
* react-native-cli: 2.0.1
*/
import { PropTypes } from 'react';
import { requireNativeComponent, View } from 'react-native';
var iface = {
name: 'CustomView',
propTypes: {
url: PropTypes.string,
...View.propTypes // include the default view properties
},
};
module.exports = requireNativeComponent('RCTCustomView', iface);
GoToActivity.js
/**
* Created by YiBing on 2017/4/28.
*/
'use strict';
import {
NativeModules
} from 'react-native';
export default NativeModules.GoToActivity;
// 以前的ES版本的寫法。
// var {NativeModules} = require('react-native');
// module.exports = NativeModules.MyToast;
index.android.js
import React, { Component } from 'react';
import {
AppRegistry,
StyleSheet,
Text,
View,
ListView,
TouchableOpacity,
Image,
ToastAndroid,
} from 'react-native';
import GoToActivity from './GoToActivity';
import CustomView from './CustomView';
var video_url = "http://www.zxx.net.cn:8080//dmmm/vedio/201612270541078235250/201612270541078235250.mp4";
export default class yb extends Component {
render() {
return (
<View style={styles.container}>
<View style={{width:'100%', height:50, borderWidth:2, borderColor:'#f00',
justifyContent:'center', alignItems:'center',}}>
<Text style={{textAlign:'center',}}>騰訊雲點播測試</Text>
</View>
<TouchableOpacity
onPress={() => GoToActivity.show("Go To SecondActivity", ToastAndroid.SHORT)}
style={{width:'100%', flex:1, justifyContent:'center', alignItems:'center',
borderWidth:10, borderColor:'#00f'}}>
<CustomView
url={video_url}
style={{width:'100%', height:'100%'}} />
</TouchableOpacity>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#F5FCFF',
},
welcome: {
fontSize: 20,
textAlign: 'center',
margin: 10,
},
instructions: {
textAlign: 'center',
color: '#333333',
marginBottom: 5,
},
});
AppRegistry.registerComponent('yb', () => yb);
程序顯示效果圖: