基于TBS编写Android原生视图提供React Navtive使用的总结
TBS的集成流程
1、下载SDK将jar包放在app/libs/目录下(以Project方式展示项目)
2、将Demo工程中的liblbs.so拷贝到main/jniLibs/armeabi/目录下
3、添加build.gradle中添加NDK支持
ndk {
abiFilters "armeabi-v7a", "x86", "armeabi"
}
4、添加所需要的权限
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
TBS安卓原生插件的编写
- 以原生module的方式提供React Native使用
如何编写RN的原生插件这里就不演示了,具体可以参考官方网站。
实际上就是RN端跳转原生的Activity加载TBS视图。
项目中使用了TbsReaderView加载PDF、Word等文件,利用WebView加载视频和利用SDK提供的Activity播放视频。
1、Tbs的初始化
在MainApplication的onCreate()方法中初始化
private void initTBS() {
//初始化X5内核
QbSdk.initX5Environment(this, new QbSdk.PreInitCallback() {
@Override
public void onCoreInitFinished() {
//x5内核初始化完成回调接口,此接口回调并表示已经加载起来了x5,有可能特殊情况下x5内核加载失败,切换到系统内核。
Log.e("QbSdk","onCoreInitFinished:");
}
@Override
public void onViewInitFinished(boolean result) {
//x5內核初始化完成的回调,为true表示x5内核加载成功,否则表示x5内核加载失败,会自动切换到系统内核。
Log.e("QbSdk","加载内核是否成功:"+ result);
isLoadX5 = result;
}
});
// 监听X5内核的下载情况
QbSdk.setTbsListener(new TbsListener() {
@Override
public void onDownloadFinish(int i) {
Log.e("QbSdk","onDownloadFinish:"+ i);
}
@Override
public void onInstallFinish(int i) {
Log.e("QbSdk","onInstallFinish:"+ i);
}
@Override
public void onDownloadProgress(int i) {
Log.e("QbSdk","onDownloadProgress:"+ i);
}
});
}
2、在activity中利用TbsReaderView加载PDF、World等文件
public class TbsActivity extends AppCompatActivity {
private TbsReaderView mTbsReaderView;
private RelativeLayout mRelativeLayout;
private TextView mTextView;
private Button backButton;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_tbs);
mTbsReaderView = new TbsReaderView(this, new TbsReaderView.ReaderCallback() {
@Override
public void onCallBackAction(Integer integer, Object o, Object o1) {
Log.i("onCallBackAction", "o = " + o + " o1 = " + o1);
}
});
backButton = findViewById(R.id.tbs_back_button);
backButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
finish();
}
});
mRelativeLayout = findViewById(R.id.tbsRelativeView);
mRelativeLayout.addView(mTbsReaderView,new RelativeLayout.LayoutParams(-1,-1));
mTextView = findViewById(R.id.tbs_title);
String localPath = getIntent().getStringExtra("localPath");
displayFile(localPath);
mTextView.setText(FileUtils.fileName(localPath));
}
private void displayFile(String localPath) {
if (!MainApplication.getInstance().getLoadX5()) {
Toast.makeText(TbsActivity.this, "加载X5内核失败", Toast.LENGTH_SHORT).show();
return;
}
if (localPath.length() == 0 || localPath == null) {
Toast.makeText(TbsActivity.this, "获取文件名失败", Toast.LENGTH_SHORT).show();
return;
}
Bundle bundle = new Bundle();
String tempPath = Environment.getExternalStorageDirectory()
.getPath();
String filePath = localPath;
int index = localPath.indexOf("file:///");
if (index != -1) {
filePath = localPath.substring(localPath.indexOf("/") + 2);
}
Log.e("displayFile - filePath", filePath);
Boolean isExists = FileUtils.fileIsExists(filePath);
if (!isExists) {
Toast.makeText(TbsActivity.this, "本地文件不存在", Toast.LENGTH_SHORT).show();
return;
}
bundle.putString("filePath", filePath);
bundle.putString("tempPath",tempPath);
boolean result = mTbsReaderView.preOpen(parseFormat(localPath), false);
if (result) {
mTbsReaderView.openFile(bundle);
} else {
Toast.makeText(TbsActivity.this, "预览文件失败", Toast.LENGTH_SHORT).show();
}
}
private String parseFormat(String fileName) {
return fileName.substring(fileName.lastIndexOf(".") + 1);
}
@Override
protected void onDestroy() {
super.onDestroy();
mTbsReaderView.onStop();
}
}
3、利用WebView播放视频,但是这种方式不会自动播放
public class TbsVideoActivity extends AppCompatActivity {
private X5WebView webView;
private Button backButton;
private TextView mTextView;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_tbs_video);
webView = findViewById(R.id.video_webView);
mTextView = findViewById(R.id.tbs_title);
backButton = findViewById(R.id.tbs_back_button);
backButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
finish();
}
});
initDisplayVideoView();
}
private void initDisplayVideoView() {
if (!MainApplication.getInstance().getLoadX5()) {
Toast.makeText(TbsVideoActivity.this, "加载X5内核失败", Toast.LENGTH_SHORT).show();
return;
}
String localPath = getIntent().getStringExtra("localPath");
mTextView.setText(FileUtils.fileName(localPath));
Log.i("TbsVideoActivity", localPath);
if (localPath.length() == 0 || localPath == null) {
Toast.makeText(TbsVideoActivity.this, "获取文件名失败", Toast.LENGTH_SHORT).show();
return;
}
Boolean isExists = FileUtils.fileIsExists(localPath);
if (!isExists) {
Toast.makeText(TbsVideoActivity.this, "本地文件不存在", Toast.LENGTH_SHORT).show();
return;
}
if (localPath.indexOf("file://") == -1) {
localPath = "file://" + localPath;
}
webView.loadUrl(localPath);
getWindow().setFormat(PixelFormat.TRANSLUCENT);
webView.getView().setOverScrollMode(View.OVER_SCROLL_ALWAYS);
webView.setWebChromeClient(new com.tencent.smtt.sdk.WebChromeClient());
}
@Override
protected void onDestroy() {
super.onDestroy();
if (webView != null) {
webView.onPause();
}
}
}
4、利用SDK自带Activity自动播放视频
activyty配置
<activity
android:name="com.tencent.smtt.sdk.VideoActivity"
android:alwaysRetainTaskState="true"
android:configChanges="orientation|screenSize|keyboardHidden"
android:exported="false"
android:launchMode="singleTask">
<intent-filter>
<action android:name="com.tencent.smtt.tbs.video.PLAY" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
// 播放视频
public void autoPreviewVideoByTBSForRN(String localPath) {
Log.i("autoPlayVideo", localPath);
if (localPath.length() == 0) {
Toast.makeText(mReactContext, "获取文件名失败", Toast.LENGTH_SHORT).show();
return;
}
Boolean isExists = FileUtils.fileIsExists(localPath);
if (!isExists) {
Toast.makeText(mReactContext, "本地文件不存在", Toast.LENGTH_SHORT).show();
return;
}
if (localPath.indexOf("file://") == -1) {
localPath = "file://" + localPath;
}
if (TbsVideo.canUseTbsPlayer(mReactContext)){
//播放视频
TbsVideo.openVideo(mReactContext, localPath);
} else {
Toast.makeText(mReactContext, "播放失败", Toast.LENGTH_SHORT).show();
}
}
- 提供原生视图View的方式预览PDF、Word等文件
遇到的问题:RN中无法刷新原生View,发现ReactRootView中重写的onLayout是空实现
public class TbsPreviewView extends RelativeLayout {
private TbsReaderView mTbsReaderView;
private RelativeLayout mRelativeLayout;
private Context mContext;
public String localPath;
public TbsPreviewView(Context context) {
super(context);
LayoutInflater.from(context).inflate(R.layout.tbs_preview, this);
mContext = context;
mRelativeLayout = findViewById(R.id.tbs_preview_relativeView);
mTbsReaderView = new TbsReaderView(MainApplication.getInstance().getMainActivity(), new TbsReaderView.ReaderCallback() {
@Override
public void onCallBackAction(Integer integer, Object o, Object o1) {
Log.i("onCallBackAction", "o = " + o + " o1 = " + o1);
}
});
mRelativeLayout.addView(mTbsReaderView,new RelativeLayout.LayoutParams(-1,-1));
}
public void setLocalPath(String localPath) {
this.localPath = localPath;
if (localPath.length() != 0) {
Log.i("TbsReaderView", "displayFile setLocalPath = " + localPath);
displayFile(localPath);
}
}
private void displayFile(String localPath) {
if (!MainApplication.getInstance().getLoadX5()) {
Toast.makeText(mContext, "加载X5内核失败", Toast.LENGTH_SHORT).show();
return;
}
if (localPath.length() == 0) {
Toast.makeText(mContext, "获取文件名失败", Toast.LENGTH_SHORT).show();
return;
}
Bundle bundle = new Bundle();
String tempPath = Environment.getExternalStorageDirectory()
.getPath();
String filePath = localPath;
int index = localPath.indexOf("file:///");
if (index != -1) {
filePath = localPath.substring(localPath.indexOf("/") + 2);
}
Log.e("displayFile - filePath", filePath);
Boolean isExists = FileUtils.fileIsExists(filePath);
if (!isExists) {
Toast.makeText(mContext, "本地文件不存在", Toast.LENGTH_SHORT).show();
return;
}
bundle.putString("filePath", filePath);
bundle.putString("tempPath",tempPath);
boolean result = mTbsReaderView.preOpen(parseFormat(localPath), false);
if (result) {
mTbsReaderView.openFile(bundle);
} else {
Toast.makeText(mContext, "预览文件失败", Toast.LENGTH_SHORT).show();
}
}
private String parseFormat(String fileName) {
return fileName.substring(fileName.lastIndexOf(".") + 1);
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
Log.i("TbsPreviewView", "onDetachedFromWindow");
if (mTbsReaderView != null) {
mTbsReaderView.onStop();
}
}
/**
*
* 在ReactRootView中是空实现
* protected void onLayout(boolean changed, int left, int top, int right, int bottom) {}
*
*/
@Override
public void requestLayout() {
super.requestLayout();
if (getWidth() > 0 && getHeight() > 0) {
int w = MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.EXACTLY);
int h = MeasureSpec.makeMeasureSpec(getHeight(), MeasureSpec.EXACTLY);
measure(w, h);
layout(getPaddingLeft() + getLeft(), getPaddingTop() + getTop(), getWidth() + getPaddingLeft() + getLeft(), getHeight() + getPaddingTop() + getTop());
}
}
}
// RN端使用
render() {
return (
<View style={styles.container}>
<TbsPreviewView style={styles.previewViewStyle}
localPath={this.state.localPath}/>
</View>
)
}
这种方式存在的问题:预览相关文件会有失败异常的问题,不稳定,建议还是使用RN跳转原生Activity的方式比较好。
完整代码地址