前言:
接着上一篇的博客,在搜索出手机内的.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下载地址:没有积分的同学可以去码云下载.