安卓加载展示PDF文件(腾讯X5内核(TbsReaderView)+PDFView)
Posted 夜尽天明89
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了安卓加载展示PDF文件(腾讯X5内核(TbsReaderView)+PDFView)相关的知识,希望对你有一定的参考价值。
最近项目要求加载、展示PDF文件,因为之前项目中已经有X5浏览器了,用X5内核加载PDF文件也简单,就很快写完。但是没想到,测试出来几个坑。这里总结整理下。
读写权限,必须要有
写在前面:引入PDFView,会让包的体积大几M;用其他的,我查了下,有各种问题:如水印加载不全、放大缩小不顺、超过3M的PDF文件就会OOM等。最后选择了PDFView
PDFView 的GitHub 地址
https://github.com/barteksc/androidPdfViewer
X5内核(TbsReaderView)+PDFView 实现PDF的加载,已经成功,并应用到项目中了
我的测试用PDF文件,找了3个:几百K、几兆、18M
全部源代码我会放在后面(关于项目包名的,我都去掉了,复制的时候,用自己需要的代码就好),我这里先说我踩的坑或因项目需求而产生的额外操作
项目要求:
1、在线加载,本地不保留PDF文件;
2、不做缓存。如:一个PDF文件有100页,当前打开看到50页,退出去,再次进来,加载完后,从第一页开始;
3、加载PDF文件正常(这点我单独写出来,是因为X5内核启动有问题,和这个“要求”冲突,后面会说)
实现项目要求办法:
1、TbsReaderView加载PDF,是把PDF先下载了,然后加载,但是项目要求本地不保留文件,我想到个折中方法:先下载展示,等退出界面的时候,删掉文件。
这里会有个问题:用户不退出呢?如果用户加载完界面,不杀死APP,直接切换到手机文件夹,就能找到PDF文件了。会有这个问题
2、我看了不少技术博客,没有提及清除PDF文件的,但是我在实现功能的过程中,看到了这个
//存放临时文件的目录。运行后,会在
//Environment.getExternalStorageDirectory().getPath() 的目录下生成.tbs 的文件
bundle.putString(
"tempPath",
Environment.getExternalStorageDirectory().getPath()
)
然后我就找啊找,在文件夹的根目录下(因为后面没有加自定义文件夹的名字),找到了这个文件夹
.TbsReaderTemp包名
如:项目的包名是 com.chen.demo
这个文件夹的名字就是
.TbsReaderTempcom.chen.demo
在 .TbsReaderTemp包名 中,真有 .tbs 文件。进过测试(1、打开PDF文件,定位到某一页;2、退出界面;3、重新打开PDF文件,查看文件定位到的页数;4、重复1、2;5、切换到这个文件夹下;6、删除 .tbs 文件;7、回到APP,重新打开PDF文件,查看文件定位到的页数;8、和步骤3中的情况做对比),删除 .tbs 后,真的可以从第一页开始展示
注意:
(1)删掉这个临时文件,重新打开PDF文件时,会慢一点。这个需要自己恒量了。
(2).TbsReaderTemp包名 这个文件夹,在一些手机上,是不可见的。如:我的 华为mate20(安卓10、EMUI 10.1.0 )手机,就找不到,但是通过文件是否存在,可以判断出来
val s: String =
"$Environment.getExternalStorageDirectory().getPath()/.TbsReaderTemp包名/"
Log.e("s:",s)
val f: File = File(s)
if (f.exists())
......
3、为了解决PDF的正确加载,我遇到了一个巨坑:X5内核,首次安装启动的时候,不一定会加载成功,如果加载失败 result 会变成false,即:tbsReaderView 无法加载PDF
val result = tbsReaderView!!.preOpen("pdf", false)
if (result)
//X5内核正常,可以直接展示PDF文件
tbsReaderView!!.openFile(bundle)
我查了资料,也通过自己大量的卸载安装,得出下面的结论:
(1)手机上有腾讯类的产品(如:QQ、微信),可能会在手机上安装X5内核,如果有了内核,其他APP会共用;
(2)APP启动时,会调用
fun initX5Core()
QbSdk.setDownloadWithoutWifi(true)
QbSdk.initX5Environment(this, object : QbSdk.PreInitCallback
override fun onCoreInitFinished()
Log.d("X5core", "x5加载结束")
override fun onViewInitFinished(p0: Boolean)
Log.d("X5core", "x5加载结束$p0")
)
如果是第一次安装启动,有可能 onViewInitFinished 的 p0 值是false,表示X5初始化、加载失败,会导致后面的 tbsReaderView 无法加载PDF文件;安装完APP后,从第二次启动APP开始,每次都是正常的
(3)和网络情况也有关系。如果第一次安装、启动,是在WIFI情况下,onViewInitFinished 小概率会 p0 = false,如果是流量情况下 大概率 p0 = false
(4)在 result = false 时,重新调用 initX5Core() 也无法解决第一次安装、启动时 X5 初始化失败的问题
解决办法就是:再引入一个其他的PDF加载控件(如:PDFView),如果X5不行了,就切换用其他的去加载。
为什么不直接用PDFView呢?
经过对比:PDFView在展示页数、界面分页等方面,没有 tbsReaderView 好(PDFView的功能方法其实很多,我还没研究透彻,也可能是我研究不透彻的原因)。tbsReaderView更符合我的要求、使用习惯。
以下是源代码
我这里假设X5相关已经集成成功了(so文件项目已经具备了)
1、
api 'com.tencent.tbs.tbssdk:sdk:43903'
2、Application 的 onCreate中
fun initX5Core()
QbSdk.setDownloadWithoutWifi(true)
QbSdk.initX5Environment(this, object : QbSdk.PreInitCallback
override fun onCoreInitFinished()
Log.d("X5core", "x5加载结束")
override fun onViewInitFinished(p0: Boolean)
Log.d("X5core", "x5加载结束$p0")
)
3、新建文件 LoadPDFAsyncTask(用于下载PDF文件)
import android.os.AsyncTask;
import android.text.TextUtils;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
public class LoadPDFAsyncTask extends AsyncTask<String, Integer, File>
private OnLoadPDFListener onLoadPDFListener;
private String filePath = "下载的PDF文件存放的位置";
private String fileName;
public LoadPDFAsyncTask(String fileName)
this.fileName = fileName;
public LoadPDFAsyncTask(String fileName, OnLoadPDFListener onLoadPDFListener)
this.fileName = fileName;
this.onLoadPDFListener = onLoadPDFListener;
public void setOnLoadPDFListener(OnLoadPDFListener onLoadPDFListener)
this.onLoadPDFListener = onLoadPDFListener;
//这里是开始线程之前执行的,是在UI线程
@Override
protected void onPreExecute()
super.onPreExecute();
//当任务被取消时回调
@Override
protected void onCancelled()
super.onCancelled();
if (onLoadPDFListener != null) onLoadPDFListener.onFailureListener();
//当任务被取消时回调
@Override
protected void onCancelled(File file)
super.onCancelled(file);
//子线程中执行
@Override
protected File doInBackground(String... strings)
String httpUrl = strings[0];
if (TextUtils.isEmpty(httpUrl))
if (onLoadPDFListener != null) onLoadPDFListener.onFailureListener();
File pathFile = new File(filePath);
if (!pathFile.exists())
pathFile.mkdirs();
File pdfFile = new File(filePath + File.separator + fileName);
if (pdfFile.exists())
return pdfFile;
try
URL url = new URL(httpUrl);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setConnectTimeout(10000);
conn.setReadTimeout(10000);
conn.setRequestMethod("GET");
conn.setDoInput(true);
conn.connect();
int count = conn.getContentLength(); //文件总大小 字节
int curCount = 0; // 累计下载大小 字节
int curRead = -1;// 每次读取大小 字节
if (conn.getResponseCode() == HttpURLConnection.HTTP_OK)
InputStream is = conn.getInputStream();
FileOutputStream fos = new FileOutputStream(pdfFile);
byte[] buf = new byte[1024];
while ((curRead = is.read(buf)) != -1)
curCount += curRead;
fos.write(buf, 0, curRead);
publishProgress(curCount, count);
fos.close();
is.close();
conn.disconnect();
catch (Exception e)
e.printStackTrace();
if (onLoadPDFListener != null) onLoadPDFListener.onFailureListener();
return null;
return pdfFile;
//更新进度
@Override
protected void onProgressUpdate(Integer... values)
super.onProgressUpdate(values);
if (onLoadPDFListener != null)
onLoadPDFListener.onProgressListener(values[0], values[1]);
//当任务执行完成是调用,在UI线程
@Override
protected void onPostExecute(File file)
super.onPostExecute(file);
if (onLoadPDFListener != null)
if (file != null && file.exists())
onLoadPDFListener.onCompleteListener(file);
else
onLoadPDFListener.onFailureListener();
//下载PDF文件时的回调接口
public interface OnLoadPDFListener
//下载完成
void onCompleteListener(File file);
//下载失败
void onFailureListener();
//下载进度 字节
void onProgressListener(Integer curPro, Integer maxPro);
到此,TbsReaderView 准备完成,还缺布局和界面中的使用,下面会有。继续看
4、引入 PDFView
//https://github.com/barteksc/AndroidPdfViewer
implementation 'com.github.barteksc:android-pdf-viewer:2.8.2'
5、加载PDF界面整体布局
activity_show_online_pdf.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/white"
android:orientation="vertical">
<!--标题-->
<RelativeLayout
android:id="@+id/show_online_pdf_title_rl"
android:layout_width="match_parent"
android:layout_height="@dimen/title_height"
android:background="@color/white">
<ImageView
android:id="@+id/show_online_pdf_back_iv"
android:layout_width="23dp"
android:layout_height="24dp"
android:layout_centerVertical="true"
android:layout_marginStart="15dp"
android:paddingStart="5dp"
android:paddingTop="5dp"
android:paddingEnd="10dp"
android:paddingBottom="5dp"
android:src="@mipmap/ic_zuo_fanhui" />
<TextView
android:id="@+id/show_online_pdf_title_tv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:layout_marginStart="60dp"
android:layout_marginEnd="60dp"
android:ellipsize="end"
android:gravity="center"
android:singleLine="true"
android:textColor="@color/color_252525"
android:textSize="16dp"
tools:text="PDF文件名字" />
</RelativeLayout>
<!--展示pdf-->
<LinearLayout
android:id="@+id/show_online_pdf_ll"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
</LinearLayout>
<com.github.barteksc.pdfviewer.PDFView
android:id="@+id/pdfView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone" />
</LinearLayout>
准备工作已经完成,开始具体使用
6、ShowOnlinePdfActivity
import android.app.Activity
import android.content.Intent
import android.os.Bundle
import android.os.Environment
import android.view.View
import android.view.ViewGroup
import android.widget.LinearLayout
import com.jess.arms.base.BaseActivity
import com.jess.arms.di.component.AppComponent
import com.jess.arms.utils.ArmsUtils
import com.jess.arms.utils.LogUtils
import com.jess.arms.utils.showToast
import com.tencent.smtt.sdk.TbsReaderView
import kotlinx.android.synthetic.main.activity_show_online_pdf.*
import java.io.File
/**
* 展示在线pdf的界面
*/
class ShowOnlinePdfActivity : BaseActivity<ShowOnlinePdfPresenter>(), ShowOnlinePdfContract.View,
TbsReaderView.ReaderCallback
override fun setupActivityComponent(appComponent: AppComponent)
DaggerShowOnlinePdfComponent //如找不到该类,请编译一下项目
.builder()
.appComponent(appComponent)
.showOnlinePdfModule(ShowOnlinePdfModule(this))
.build()
.inject(this)
companion object
private val ShowOnlinePdf_Url: String = "ShowOnlinePdf_Url"
private val ShowOnlinePdf_Name: String = "ShowOnlinePdf_Name"
fun enterShowOnlinePdf(activity: Activity, pdfUrl: String, pdfName: String)
var intent: Intent = Intent(activity, ShowOnlinePdfActivity::class.java)
intent.putExtra(ShowOnlinePdf_Url, pdfUrl)
intent.putExtra(ShowOnlinePdf_Name, pdfName)
activity.startActivity(intent)
override fun initView(savedInstanceState: Bundle?): Int
return R.layout.activity_show_online_pdf //如果你不需要框架帮你设置 setContentView(id) 需要自行设置,请返回 0
private var pdfUrl: String = ""
private var pdfName: String = ""
private var tbsReaderView: TbsReaderView? = null
override fun initData(savedInstanceState: Bundle?)
initClickListener()
show_online_pdf_ll?.visibility = View.VISIBLE
pdfView?.visibility = View.GONE
pdfUrl = intent.getStringExtra(ShowOnlinePdf_Url).orEmpty()
pdfName = intent.getStringExtra(ShowOnlinePdf_Name).orEmpty()
show_online_pdf_title_tv?.text = pdfName
try
tbsReaderView = TbsReaderView(this, this)
tbsReaderView?.apply
show_online_pdf_ll?.addView(
this, LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
)
)
showLoading()
LoadPDFAsyncTask(pdfName, object : LoadPDFAsyncTask.OnLoadPDFListener
override fun onProgressListener(curPro: Int?, maxPro: Int?)
override fun onFailureListener()
hideLoading()
showToast("文件加载失败")
override fun onCompleteListener(file: File?)
hideLoading()
try
val bundle = Bundle()
bundle.putString("filePath", file!!.path)
//存放临时文件的目录。运行后,会在 Environment.getExternalStorageDirectory().getPath() 的目录下生成.tbs...的文件
bundle.putString(
"tempPath",
Environment.getExternalStorageDirectory().getPath()
)
val result = tbsReaderView!!.preOpen("pdf", false)
if (result)
//X5内核正常,可以直接展示PDF文件
tbsReaderView!!.openFile(bundle)
else
//这是备选方案,兼容X5内核加载失败,无法展示PDF文件的情况
show_online_pdf_ll?.visibility = View.GONE
pdfView?.visibility = View.VISIBLE
pdfView.fromFile(file)
.defaultPage(0)
.enableAnnotationRendering(true)
.spacing(0) // in dp
.load()
catch (e: Exception)
e.printStackTrace()
hideLoading()
showToast("文件加载失败")
LogUtils.errorInfo("加载在线PDF e = $e")
).execute(pdfUrl)
catch (e: Exception)
e.printStackTrace()
hideLoading()
private fun initClickListener()
show_online_pdf_back_iv?.click
killMyself()
override fun onCallBackAction(p0: Int?, p1: Any?, p2: Any?)
override fun onDestroy()
try
//项目要求是不保留PDF文件,所以,在界面销毁的时候删掉。如果项目不做要求,可以不删除。
FileUtil.deleteDirectory(存放PDF文件的文件夹)
/**
* 以下是删除缓存的。(是否需要删除,根据项目要求来)
*
* 如:打开一个PDF文件,停到第10页,退出去进来,还是第10页。
*
* 加上下面的删除临时文件代码,下次进入,就从0开始
*/
val s: String =
"$Environment.getExternalStorageDirectory().getPath()/.TbsReaderTemp包名/"
LogUtils.errorInfo("s:$s")
val f: File = File(s)
if (f.exists())
FileUtil.deleteDirectory(s)
catch (e: Exception)
e.printStackTrace()
super.onDestroy()
//很重要
if (tbsReaderView != null)
tbsReaderView?.onStop()
tbsReaderView = null
override fun showLoading()
DialogManager.showLoadingDialog(this)
override fun hideLoading()
DialogManager.dismissAllLoadingDialog()
override fun showMessage(message: String)
ArmsUtils.snackbarText(message)
override fun launchActivity(intent: Intent)
ArmsUtils.startActivity(intent)
override fun killMyself()
finish()
7、deleteDirectory 工具
/**
* 删除文件夹以及目录下的文件
* @param filePath 被删除目录的文件路径
* @return 目录删除成功返回true,否则返回false
*/
fun deleteDirectory(filePath: String): Boolean
var filePath = filePath
var flag = false
//如果filePath不以文件分隔符结尾,自动添加文件分隔符
if (!filePath.endsWith(File.separator))
filePath += File.separator
val dirFile = File(filePath)
if (!dirFile.exists() || !dirFile.isDirectory)
return false
flag = true
val files = dirFile.listFiles()
//遍历删除文件夹下的所有文件(包括子目录)
for (i in files.indices)
if (files[i].isFile)
//删除子文件
flag = deleteFile(files[i].absolutePath)
if (!flag) break
else
//删除子目录
flag = deleteDirectory(files[i].absolutePath)
if (!flag) break
return if (!flag) false else dirFile.delete()
以上是关于安卓加载展示PDF文件(腾讯X5内核(TbsReaderView)+PDFView)的主要内容,如果未能解决你的问题,请参考以下文章