引言: 项目需要预览pdf、word等文件,但是用户不一定安装了wps这样的软件,因此需要在项目里支持查看这些文件。笔者本身是不想使用webview的,因此尝试了很多原生的,但是对于doc文件基本没有什么好的方式,到最后还是用了腾讯的tbs文件游览服务。接入tbs坑较多,主要是x5内核的下载和各种配置问题,花费了两天才整理好,因此分享出来防止大家踩坑。
一、首先梳理一下选择tbs的理由
对于doc,docx文件,除tbs外似乎只有一种方式,利用poi解析doc,然后自己渲染成html。笔者尝试了这种方式,发现其原理是读取doc文件后,再把相关元素加上html的标签,用本地webview加载,html标签与doc文件毕竟有很大差距,因此展示效果很差,只能选择tbs。
然后是关于pdf的查看,这种方式略多,下面大致介绍下:
- pdf.js 这是火狐推出的可以在安卓上使用,并且文件并不大,使用难度相对不大
- 安卓原生的pdfRender,笔者使用感觉还可以,因为使用安卓自带的pdfRender,不需要额外导入文件,加载速度也还好,如果只需要显示pdf,推荐
- github上开源的pdfviewer,功能较为强大,但是因为会导致包至少增大无法被接受,项目地址https://github.com/barteksc/AndroidPdfViewer
- 采用外部链接在线预览,但是目前基本是国外链接,会被墙,不考虑
最后能够良好展示word文件的只有tbs,因此只能用它了。
二、tbs接入及踩坑
首先说一下坑的问题,实际上在本地利用tbs打开文件腾讯的文档是没有写的,因为腾讯想让我们优先使用他们的QbSdk,这样会优先打开QQ浏览器,现在的本地打开的方法是各路大神从源码找出来的,可能有各种疏忽,所以坑多。(不过腾讯的文档也怪扯淡的)
1.初始配置
先上官方文档: 腾讯浏览器tbs接入文档
下面进行大致梳理
1. 导入依赖
在app的build.gradle文件中增加依赖
api 'com.tencent.tbs.tbssdk:sdk:43939'
2.增加权限混淆,首次启动初始化冷启动优化(一定要加混淆,不然release版本不能用的
这里不做详细介绍,直接看官方文档,照着来就可以了。另外官方文档的异常上报措施可忽略。官方文档还有一个替换webview的,这种是指如果你项目本来使用了webview,但是现在你想换成使用腾讯的x5内核,那么就需要按照腾讯的做法替换,如果不想替换,那可以不用变,因为接入腾讯x5内核后,安卓原生的webview仍然是可以用的。
3、使用
从这里开始文档就相当坑爹了,腾讯文档被没有讲如何初始化x5内核,甚至x5内核到底初始化成功了都没有介绍。
首先介绍一下x5内核,x5内核就是QQ浏览器的内核,对安卓的webview做了一层封装,在你的手机各个app可以共享,比如qq、QQ游览器,微信之间一个初始化了,其他的就可以使用。(但是笔者实践,只有你安装了QQ浏览器才可以共享,QQ微信无效)。
然后是x5内核的初始化代码:(建议放在Application中)
QbSdk.initX5Environment(this, new QbSdk.PreInitCallback() {
@Override
public void onCoreInitFinished() {
}
@Override
public void onViewInitFinished(boolean b) {
//这里被回调,并且b=true说明内核初始化并可以使用
//如果b=false,内核会尝试安装,你可以通过下面监听接口获知
//另外第一次安装app不会被调用
}
});
QbSdk.setTbsListener(new TbsListener() {
@Override
public void onDownloadFinish(int i) {
//tbs内核下载完成回调
//但是只有i等于100才算完成,否则失败
//此时大概率可能由于网络问题
//如果失败可增加网络监听器
}
@Override
public void onInstallFinish(int i) {
//内核安装完成回调,通常到这里也算安装完成,但是在
//极个别情况也会出现加载失败,比如笔者在公司内网下偶现,可以忽略
}
@Override
public void onDownloadProgress(int i) {
//下载进度监听
}
});
上面的代码会自动对x5内核进行初始化,如果没有x5内核会自动下载。
之后就是使用sdk打开文件了,首先是腾讯文档写的打开文件方式:(坑较少,同时不是本文的目的,不做过多介绍)
//优先打开QQ浏览器,如果没有则利用X5内核软件内打开,
//如果X5内核也没有就弹窗让别的软件打开
QbSdk.openFileReader(getContext(),filepath,null,null);
然后就是使用TbsReaderView在软件内打开:
Bundle bundle = new Bundle();
//指定文件路径
bundle.putString("filePath", filePath);
//指定腾讯文件缓存路径
bundle.putString("tempPath", Environment.getExternalStorageDirectory()
.getPath()+"/dsadsa");
//预加载,判断格式是否正确,其中的parseFile方法是获取文件后缀
boolean result = mTbsView.preOpen(parseFileType(filePath), false);
if (result) {
mTbsView.openFile(bundle);
} else {
Log.e(TAG, "Type is not support");
}
这里再附上获取文件后缀方法,parseFileType
private String parseFileType(String path) {
if (TextUtils.isEmpty(path)) {
return "";
} else {
return path.substring(path.lastIndexOf(".")+1);
}
}
上面的基本就是按照文档的说法了,但是离真正打开还有十万八千里,下面开始踩坑。
三、踩坑
1.provider
当你运行尝试打开文件后,会报错:找不到com.tencent.smtt.utils.FileProvider或者找不到provider,官方文档竟然一点没提,气愤呀。
在AndroidManifest.xml文件中增加:
<provider
android:name="com.tencent.smtt.utils.FileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/x5webview_file_paths" />
</provider>
然后在res的xml文件夹下新建文件夹x5webview_file_paths.xml (文件名自己定,只要和上面的meta_data的resource对应上即可),内容如下:
<?xml version="1.0" encoding="utf-8"?>
<paths>
<external-path name="sdcard" path="."/>
</paths>
2.X5内存下载
在使用过程中,经常会报各种类找不到,然后在使用tbs打开文件时,明明是支持的文件,但是却说不支持,都是由于X5内核没有下载导致的。(因为内部很多方法使用了反射,X5的代码没有下载,因此会出现各种类找不到)
下面说一下X5初始化出现的各种问题:
- 网络要求,由于X5内核30M,通常在wifi下下载 (根据项目需要决定,后来我们最终决定在流量下也可以下载)
- 继续下载问题,在上一次下载失败后,下一次由于有缓存,会导致即不能用,也不能重新下载
- 初始化是否完成,官方没有提供相关的方法
也就是说,只要一次性没下载完,以后下载就没戏了,除非卸载重装,而且网络不符合要求也会中止下载
然后是我的解决总结,再详细阐述解释:
reset重置+布尔值记录初始结果+网路监听器+进度监听
上面2.1.3介绍了X5下载问题,sdk会自动判断是否初始化并下载,但是用户网络不一定符合要求,而且用户也可能打开接着关闭导致存在缓存无法再次初始化,甚至在初始化过程中调用tbsReadview的相关代码也会导致缓存问题,真的是恶心呀。
通过各种搜索百度谷歌,发现下面几个方法:
//重置化sdk,这样就清除缓存继续下载了
QbSdk.reset(context);
//手动开始下载,此时需要先判定网络是否符合要求
TbsDownloader.startDownload(context);
//是否需要下载内核,作用比较奇葩
//该方法会在完全没下载的时候返回true,在
//加载完成和存在缓存无法继续下载时返回flase
//这个方法可以用来判断是否存在缓存需要重置
boolean need =TbsDownloader.needDownload(context, false)
接下来就结合上面的方法和具体x5下载的流程,详细分析。
首先是每次启动前调用sdk初始化加速流程:
private static void BeforeSdk(Context context) {
// 在调用TBS初始化、创建WebView之前进行如下配置
HashMap<String, Object> map = new HashMap<>();
map.put(TbsCoreSettings.TBS_SETTINGS_USE_SPEEDY_CLASSLOADER, true);
map.put(TbsCoreSettings.TBS_SETTINGS_USE_DEXLOADER_SERVICE, true);
QbSdk.initTbsSettings(map);
QbSdk.setDownloadWithoutWifi(!mOnlyWifi);
QbSdk.disableAutoCreateX5Webview();
//强制使用系统内核,看你需求
//QbSdk.forceSysWebView();
}
然后就是下载过程,在这里我们设置了几个变量记录是否初始化成功
private static boolean mOnlyWifi = false;
//记录是否加载完成
private static boolean mInit = false;
//网络接收器是用来处理网络中断导致的加载问题
private static ConnectReceiver mConnectionReceiver;
//这个key是sp存储的,用来记录上一次是否加载成功
//因为有的时候需要立刻用到tbs展示文件,但是tbs可能还没加载好
//因此我们在sp里面做记录,看是否需要等待加载
//如果上次加载好了,那这次肯定也很快,可以等待
//如果上次没加载好,那还是提示用户过一会再用吧
private static String TBS_INIT_KEY = "tbs_init_key";
//该方法是用来判断tbs是否加载完成的,供外部调用
public static boolean initFinish() {
//如果上次加载成功,那么便认为当前的未加载成功由于还未加载完等
//不做额外处理
if(SPUtils.getBoolean(TBS_INIT_KEY,false)){
return mInit;
}
//上次没加载完,那就根据状态判断是否重置
if (!mInit && !TbsDownloader.isDownloading()) {
QbSdk.reset(mContext);
resetSdk(mContext);
if (!mOnlyWifi || NetUtils.isWifiConnection(mContext))
TbsDownloader.startDownload(mContext);
}
return mInit;
}
接着就是具体的下载设置:
resetSdk(context);
QbSdk.setTbsListener(new TbsListener() {
@Override
public void onDownloadFinish(int i) {
//成功时i为100
if (i != 100) {
//此处存在一种情况,第一次启动app,init不会自动回调,
// 此处额外加一层,判断网络监听器是否为空并作出处理
if (mConnectionReceiver == null)
initNetWorkCallBack();
else {
initFinish();
}
}
Log.d(TAG, "load" + i);
//tbs内核下载完成回调
}
@Override
public void onInstallFinish(int i) {
//只要运行到这里,我们就认为加载完成了
//但是也发现一些异常,在公司内网运行到这里也加载失败了,先忽略
mInit = true;
if (mConnectionReceiver != null) {
mContext.unregisterReceiver(mConnectionReceiver);
mConnectionReceiver = null;
}
Log.d(TAG, "finish" + i);
//内核安装完成回调,
}
@Override
public void onDownloadProgress(int i) {
//下载进度监听
Log.d(TAG, "progress" + i);
}
});
QbSdk.initX5Environment(context, new QbSdk.PreInitCallback() {
@Override
public void onCoreInitFinished() {
//x5内核初始化完成回调接口,此接口回调并表示已经加载起来了x5,有可能特殊情况下x5内核加载失败,切换到系统内核。
}
@Override
public void onViewInitFinished(boolean b) {
//x5內核初始化完成的回调,为true表示x5内核加载成功,否则表示x5内核加载失败,会自动切换到系统内核。
//该方法在第一次安装app打开不会回调
mInit = b;
Log.e(TAG, "加载内核是否成功:" + b);
if (!mInit) {
initNetWorkCallBack();
}
if (!mInit && TbsDownloader.needDownload(context, false) && !TbsDownloader.isDownloading()) {
initFinish();
}
SPUtils.putBoolean(TBS_INIT_KEY, mInit).apply();
}
});
至此tbs的初始化基本完成,代码示例中的SPUtils就是SharedPreferences,自己实现即可
3.使用
tbsview你在同一时间只能加载一处,因此请做好回收工作,可以设置成静态的,也可以将加载的activity设置成instance的,具体看你。
另外展示的时候会有最近文件字样,以及可能底色有问题,都可以通过逐层分析子view更改,具体百度即可。
项目地址 https://github.com/qieting/TbsUserDemo
这个项目地址和上面博客讲的略有出入,因为博客是后期补充完善的,但是项目没改