前言:
接着上一篇的博客,在搜索出手机内的.pdf格式文件后,实现pdf文件的预览的方式有很多,
1.Android PdfViewer项目地址:
https://github.com/barteksc/AndroidPdfViewer
功能很强大, 使用也比较广, 亲测可以使用. 唯一的缺点 :添加到项目中 会使apk增加10M左右, 这是最不能接受的, 故弃用.
2.PdfViewPager:
项目地址:
https://github.com/voghDev/PdfViewPager
可加载assets/SD卡/URL(在线预览) , 优点: 使用比较方便, 也不大
3.mozilla开源的PDF.js
项目地址:
GitHub – mozilla/pdf.js: PDF Reader in JavaScript
是将pdf.js和相关的文件全部下载下来并拷贝的工程中的assets目录,apk会增加4M左右
4.腾讯的tbs
项目地址:
腾讯浏览服务
这种方式是实现本地预览,把pdf文件下载到本地或直接加载本地的pdf文件
优点:使用方便,集成简单,包不大,可以远程依赖(ps:本文采用的是这种方式)
缺点:不能很好地支持部分64手机,Androidx兼容也不是很好.
5.实现的效果图如下:

6.跳转事件代码
//跳转到文件预览
pdfAdapter.setOnItemClickListener(new BaseQuickAdapter.OnItemClickListener() {
@Override
public void onItemClick(BaseQuickAdapter adapter, View view, int position) {
String fileUrl = Objects.requireNonNull(pdfAdapter.getItem(position)).getFilePath();
String fileName = Objects.requireNonNull(pdfAdapter.getItem(position)).getFileName();
PDFPreViewActivity.actionStart(PDFSearchActivity.this, fileUrl, fileName);
}
});
7.PDFPreViewActivity代码:
/**
* @作者: njb
* @时间: 2019/9/11 20:31
* @描述: pdf文件预览
*/
public class PDFPreViewActivity extends AppCompatActivity implements TbsReaderView.ReaderCallback {
private TbsReaderView mTbsReaderView;
private String mFileName;
private TextView tv_download;
private long mRequestId;
private DownloadObserver mDownloadObserver;
private ProgressBar progressBar_download;
private DownloadManager mDownloadManager;
private String fileName;
private String fileUrl;
private String TAG = PDFPreViewActivity.class.getSimpleName();
private RelativeLayout rootRl;
private TextView tv_title;
private ImageView iv_back;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_pdf_preview);
initIntent();
initTbsReaderView();
mTbsReaderView = new TbsReaderView(this, this);
rootRl.addView(mTbsReaderView, new RelativeLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT));
if ((fileUrl == null) || (fileUrl.length() <= 0)) {
Toast.makeText(PDFPreViewActivity.this, "获取文件url出错了", Toast.LENGTH_SHORT).show();
return;
}
mFileName = PDFUtil.parseName(fileUrl);
requestPermission();
isDown();
}
private void initIntent() {
if (getIntent() != null && getIntent().getExtras() != null) {
fileUrl = getIntent().getExtras().getString("fileUrl");
fileName = getIntent().getExtras().getString("fileName");
}
}
//初始化TbsReaderView 5-3
private void initTbsReaderView() {
rootRl = findViewById(R.id.rl_tbsView);
tv_download = findViewById(R.id.tv_download);
progressBar_download = findViewById(R.id.progressBar_download);
tv_title = findViewById(R.id.tv_title);
tv_title.setText("PDF文件预览");
iv_back = findViewById(R.id.iv_back);
iv_back.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
finish();
}
});
}
private void requestPermission() {
RxPermissions rxPermissions = new RxPermissions(this);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
rxPermissions.requestEach(Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE)
.subscribe(new Consumer<Permission>() {
@Override
public void accept(Permission permission) {
if (permission.granted) {
// 用户已经同意该权限
Log.d(TAG, permission.name + " is granted.");
} else if (permission.shouldShowRequestPermissionRationale) {
// 用户拒绝了该权限,没有选中『不再询问』(Never ask again),那么下次再次启动时,还会提示请求权限的对话框
Log.d(TAG, permission.name + " is denied. More info should be provided.");
} else {
// 用户拒绝了该权限,并且选中『不再询问』
Log.d(TAG, permission.name + " is denied.");
}
}
});
}
}
private void isDown() {
if (isLocalExist() || fileUrl.contains("storage")) {
tv_download.setText("打开文件");
tv_download.setVisibility(View.GONE);
displayFile();
} else {
if (!fileUrl.contains("http")) {
new AlertDialog.Builder(PDFPreViewActivity.this)
.setTitle("温馨提示:")
.setMessage("文件的url地址不合法哟,无法进行下载")
.setCancelable(false)
.setPositiveButton("确定", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
return;
}
}).create().show();
}
startDownload();
}
}
@TargetApi(Build.VERSION_CODES.GINGERBREAD)
private void queryDownloadStatus() {
DownloadManager.Query query = new DownloadManager.Query()
.setFilterById(mRequestId);
Cursor cursor = null;
try {
cursor = mDownloadManager.query(query);
if (cursor != null && cursor.moveToFirst()) {
// 已经下载的字节数
long currentBytes = cursor
.getLong(cursor
.getColumnIndexOrThrow(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR));
// 总需下载的字节数
long totalBytes = cursor
.getLong(cursor
.getColumnIndexOrThrow(DownloadManager.COLUMN_TOTAL_SIZE_BYTES));
// 状态所在的列索引
int status = cursor.getInt(cursor
.getColumnIndex(DownloadManager.COLUMN_STATUS));
tv_download.setText("下载中...(" + PDFUtil.FormetFileSize(currentBytes)
+ "/" + PDFUtil.FormetFileSize(totalBytes) + ")");
// 将当前下载的字节数转化为进度位置
int progress = (int) ((currentBytes * 1.0) / totalBytes * 100);
progressBar_download.setProgress(progress);
Log.i("downloadUpdate: ", currentBytes + " " + totalBytes + " "
+ status + " " + progress);
if (DownloadManager.STATUS_SUCCESSFUL == status
&& tv_download.getVisibility() == View.VISIBLE) {
tv_download.setVisibility(View.GONE);
tv_download.performClick();
if (isLocalExist()) {
tv_download.setVisibility(View.GONE);
progressBar_download.setVisibility(View.GONE);
displayFile();
}
}
}
} finally {
if (cursor != null) {
cursor.close();
}
}
}
/**
* 加载显示文件内容
*/
private void displayFile() {
Bundle bundle = new Bundle();
if (isLocalExist()) {
bundle.putString("filePath", getLocalFile().getPath());
}
if (fileUrl.contains("storage")) {
bundle.putString("filePath", fileUrl);
}
bundle.putString("tempPath", Environment.getExternalStorageDirectory().getPath());
boolean result = mTbsReaderView.preOpen(PDFUtil.getFileType(mFileName), false);
if (result) {
mTbsReaderView.openFile(bundle);
} else {
File file = new File(getLocalFile().getPath());
if (file.exists()) {
Intent openintent = new Intent();
openintent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
String type = PDFUtil.getMIMEType(file);
// 设置intent的data和Type属性。
openintent.setDataAndType(Uri.fromFile(file), type);
// 跳转
startActivity(openintent);
finish();
}
}
}
/**
* 下载文件
*/
@SuppressLint("NewApi")
private void startDownload() {
mDownloadObserver = new DownloadObserver(new Handler());
getContentResolver().registerContentObserver(
Uri.parse("content://downloads/my_downloads"), true,
mDownloadObserver);
mDownloadManager = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);
//将含有中文的url进行encode
String mFileUrl = toUtf8String(fileUrl);
try {
DownloadManager.Request request = new DownloadManager.Request(
Uri.parse(mFileUrl));
request.setDestinationInExternalPublicDir(Constants.DOWNLOAD_PDF_PATH, mFileName);
request.allowScanningByMediaScanner();
request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_HIDDEN);
mRequestId = mDownloadManager.enqueue(request);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 跳转页面
*
* @param context
* @param fileUrl 文件url
* @param fileName 文件名
*/
public static void actionStart(Context context, String fileUrl, String fileName) {
Intent intent = new Intent(context, PDFPreViewActivity.class);
intent.putExtra("fileUrl", fileUrl);
intent.putExtra("fileName", fileName);
context.startActivity(intent);
}
/**
* 是否为本地文件
*
* @return
*/
private boolean isLocalExist() {
return getLocalFile().exists();
}
/**
* 本地文件目录
*
* @return
*/
private File getLocalFile() {
return new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), mFileName);
}
@Override
public void onCallBackAction(Integer integer, Object o, Object o1) {
}
private class DownloadObserver extends ContentObserver {
private DownloadObserver(Handler handler) {
super(handler);
}
@Override
public void onChange(boolean selfChange, Uri uri) {
queryDownloadStatus();
}
}
@Override
protected void onDestroy() {
super.onDestroy();
mTbsReaderView.onStop();
if (mDownloadObserver != null) {
getContentResolver().unregisterContentObserver(mDownloadObserver);
}
}
}
8.activity_pdf_preview.xml布局代码
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#f5f5f5" android:orientation="vertical"> <include layout="@layout/toolbar" /> <RelativeLayout android:id="@+id/rl_tbsView" android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:id="@+id/tv_download" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:text="开始下载" android:textColor="@android:color/black" android:textSize="13sp" /> <ProgressBar android:id="@+id/progressBar_download" style="?android:attr/progressBarStyleHorizontal" android:layout_width="match_parent" android:layout_height="3dp" android:layout_below="@+id/tv_download" android:layout_marginLeft="12dp" android:layout_marginRight="12dp" android:layout_marginTop="6dp" android:max="100" android:progressDrawable="@drawable/progressbar_color" /> </RelativeLayout> </LinearLayout>
9.build.gradle配置
implementation fileTree(include: ['*.jar'], dir: 'libs') implementation 'androidx.appcompat:appcompat:1.1.0' implementation 'androidx.constraintlayout:constraintlayout:1.1.3' testImplementation 'junit:junit:4.12' androidTestImplementation 'androidx.test:runner:1.2.0' androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' //Rx权限 implementation 'com.tbruyelle.rxpermissions2:rxpermissions:0.9.5@aar' implementation 'com.github.CymChad:BaseRecyclerViewAdapterHelper:2.9.40' implementation 'androidx.recyclerview:recyclerview:1.0.0' implementation 'io.reactivex.rxjava2:rxandroid:2.1.0' implementation 'io.reactivex.rxjava2:rxjava:2.2.3' implementation "com.tencent.tbs.tbssdk:sdk:43697"
10.Manifest权限配置
<uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.READ_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.READ_PHONE_STATE" /> <uses-permission android:name="android.permission.READ_SETTINGS" /> <uses-permission android:name="android.permission.WRITE_SETTINGS" /> <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" /> <!-- 硬件加速对X5视频播放非常重要,建议开启 --> <uses-permission android:name="android.permission.GET_TASKS" /> <uses-permission android:name="android.permission.VIBRATE" /> <uses-permission android:name="android.permission.DOWNLOAD_WITHOUT_NOTIFICATION" /> <uses-permission android:name="android.permission.ACCESS_DOWNLOAD_MANAGER" />
11.遇到的问题:
11.1 64位手机so加载失败,由于腾讯x5浏览器官方的sdk库文件就提供了一种类型的so库,所以在APP的build.gradle目录下配置支持其他手机类型,配置如下:
ndk {
abiFilters "armeabi", "armeabi-v7a", "x86", "mips"
}
如果不指定so的目录,默认就会默认匹配main下的jniLibs目录:

11.2 x5浏览器初始化成功,但是加载失败,有些手机不兼容,解决方法如下:
使用远程依赖的方式加载 ,目前Androidx加载第一次可能会失败,多加载几次就好了.

12.最后,项目的源码下载地址为:
PDFSearch: Android实现手机内PDF文件搜索
13.csdn下载地址:没有积分的同学可以去码云下载.