https://blog.csdn.net/choose_xt/article/details/78653370
下面介绍几种可行性较高的方案
一.PDFView
这个比较早,有几年没更新了,但可以满足一般的需求,手势放大缩小,左右滑动,体积也较小,对于Android版本可以兼容到较低版本,缺点也比较明显,不能查看带有水印和超链接的PDF
1.添加依赖 compile 'com.joanzapata.pdfview:android-pdfview:1.0.4@aar'
2.适用场景:加载本地asserts中的资源;加载内存中的文件(其实在线预览也就是先下载,然后查看)
3.show code
<?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:orientation="vertical"> <TextView android:id="@+id/tv_page" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textColor="@color/colorAccent" android:textSize="18sp" /> <com.joanzapata.pdfview.PDFView android:id="@+id/pdf" android:layout_width="match_parent" android:layout_height="match_parent" /> </LinearLayout>
public class PdfReadActivity extends AppCompatActivity implements OnPageChangeListener { public static final String APP_NAME = "my/pdf"; public static final String IS_LOAD_COMPLETE = "is_load_complete"; public static final int LOAD_CANCEL = 0; public static final int LOAD_ERROR = 1; private MyHandler myHandler; private PDFView pdf; private TextView tvPage; private AsyncTask<String, Integer, String> task; private String pdfName; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_pdf_read); pdf = (PDFView) findViewById(R.id.pdf); tvPage = (TextView) findViewById(R.id.tv_page); //Asserts资源查看 // pdf.fromAsset("sample.pdf") // .defaultPage(1) // .showMinimap(false) // .enableSwipe(true) // .onPageChange(this) // .load(); // String path = getIntent().getStringExtra("path"); myHandler = new MyHandler(this); if (TextUtils.isEmpty(path)) { Toast.makeText(this, "文件不存在", Toast.LENGTH_SHORT).show(); finish(); } startOpenPdf(path); } private void startOpenPdf(String path) { getPdfName(path); boolean isLoadComplete = SharedPreferencesUtils.getBooleanParam(this, IS_LOAD_COMPLETE, false); String pdfPath = checkApkExist(); //若存在pdf文件...不存在则直接下载 if (!TextUtils.isEmpty(pdfPath)) { if (isLoadComplete) { showPdf(pdfPath); } else { File file = new File(FileUtils.getRootFilePath() + APP_NAME, pdfName); FileUtils.deleteFile(file); task = new DownPdfFileTask().execute(path); } } else { task = new DownPdfFileTask().execute(path); } } private void getPdfName(String path) { try { URL url = new URL(path); String urlPath = url.getPath(); pdfName = urlPath.substring(urlPath.lastIndexOf("/") + 1); } catch (Exception e) { e.printStackTrace(); } } private String checkApkExist() { String path = FileUtils.getRootFilePath() + APP_NAME; File file = new File(path, pdfName); return file.exists() ? path + "/" + pdfName : ""; } private class DownPdfFileTask extends AsyncTask<String, Integer, String> { private File pdfFile; String result = null; @Override protected String doInBackground(String... params) { InputStream input = null; FileOutputStream fos = null; try { URL url = new URL(params[0]); String urlPath = url.getPath(); pdfName = urlPath.substring(urlPath.lastIndexOf("/") + 1); url = new URL("http://resourcetest.com/" + URLEncoder.encode(pdfName)); HttpURLConnection connection = (HttpURLConnection) url.openConnection(); connection.setConnectTimeout(10000); connection.setRequestMethod("GET"); if (HttpURLConnection.HTTP_OK != connection.getResponseCode()) { return null; } String pdkDir = FileUtils.getRootFilePath() + APP_NAME; FileUtils.createDirectory(pdkDir); pdfFile = new File(pdkDir, pdfName); input = new BufferedInputStream(connection.getInputStream()); fos = new FileOutputStream(pdfFile); int count; byte buf[] = new byte[1024]; while ((count = input.read(buf)) != -1) { if (isCancelled()) { break; } fos.write(buf, 0, count); } result = pdkDir + "/" + pdfName; } catch (IOException e) { e.printStackTrace(); if (pdfFile != null && pdfFile.exists()) { pdfFile.delete(); result = null; } myHandler.sendEmptyMessage(LOAD_ERROR); } finally { if (null != fos) { try { fos.flush(); fos.close(); } catch (IOException e) { e.printStackTrace(); } } if (null != input) { try { input.close(); } catch (IOException e) { e.printStackTrace(); } } } return result; } @Override protected void onPostExecute(String s) { super.onPostExecute(s); if (!TextUtils.isEmpty(s)) { Message message = new Message(); Bundle bundle = new Bundle(); bundle.putString("path", s); message.setData(bundle); myHandler.sendMessage(message); } } } private void showPdf(String fileName) { if (TextUtils.isEmpty(fileName)) { Toast.makeText(this, "文件不存在", Toast.LENGTH_SHORT).show(); } else { try { pdf.fromFile(new File(fileName)) .defaultPage(1) .showMinimap(false) .enableSwipe(true) .onLoad(new OnLoadCompleteListener() { @Override public void loadComplete(int nbPages) { float pageWidth = pdf.getOptimalPageWidth(); float viewWidth = pdf.getWidth(); pdf.zoomTo(viewWidth / pageWidth); pdf.loadPages(); } }) .onPageChange(this) .load(); } catch (FileNotFoundException e) { Toast.makeText(this, "文件不存在", Toast.LENGTH_SHORT).show(); File file = new File(fileName); FileUtils.deleteFile(file); } } } @Override public void onPageChanged(int page, int pageCount) { tvPage.setText(getString(R.string.page_count, page, pageCount)); } static class MyHandler extends Handler { private WeakReference<PdfReadActivity> weakReference; MyHandler(PdfReadActivity activity) { weakReference = new WeakReference<>(activity); } @Override public void handleMessage(Message msg) { super.handleMessage(msg); PdfReadActivity activity = weakReference.get(); int what = msg.what; if (null != activity) { if (LOAD_ERROR == what) { Toast.makeText(activity, "文件读取失败,请重试", Toast.LENGTH_SHORT).show(); } else if (LOAD_CANCEL == what) { SharedPreferencesUtils.saveBooleanParam(activity, IS_LOAD_COMPLETE, false); } Bundle data = msg.getData(); if (null != data) { String path = (String) data.get("path"); SharedPreferencesUtils.saveBooleanParam(activity, IS_LOAD_COMPLETE, true); activity.showPdf(path); } } } } @Override protected void onDestroy() { if (task != null && task.getStatus() == AsyncTask.Status.RUNNING) { SharedPreferencesUtils.saveBooleanParam(this, IS_LOAD_COMPLETE, false); task.cancel(true); } super.onDestroy(); } }
//文件工具类
public class FileUtils { public static boolean hasSDCard() { return Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED); }
public static String getRootFilePath() { if (hasSDCard()) { // filePath:/sdcard/ return Environment.getExternalStorageDirectory().getAbsolutePath() + "/"; } else { // filePath: /data/data/ return Environment.getDataDirectory().getAbsolutePath() + "/data/"; } }
补充:compile 'com.lidong.pdf:android_pdf:1.0.1'
这个是也算是上者的升级版,内部封装好了Retrofit 网络下载,也做了缓存,一次只能缓存一个,主要是通过文件名是否一致来判断,所以喜欢简单的,也可以试试,毕竟一行代码解决很吸引人。
二.Android自带PdfRenderer
这个是Google的,对于apk不会增加体积为0,乍看很有诱惑,但是可惜的是最低支持5.0系统,核心是转换成图片然后显示
1.适用场景:同上
2.show code
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <ImageView android:id="@+id/iv" android:layout_width="match_parent" android:layout_height="match_parent" /> <Button android:id="@+id/btn_previous" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:text="上一页" /> <Button android:id="@+id/btn_next" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:layout_alignParentRight="true" android:text="下一页" /> </RelativeLayout>
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) public class AndroidPdfViewActivity extends AppCompatActivity implements View.OnClickListener { /** * Key string for saving the state of current page index. */ private static final String STATE_CURRENT_PAGE_INDEX = "current_page_index"; /** * The filename of the PDF. */ private static final String FILENAME = "sample.pdf"; /** * File descriptor of the PDF. */ private ParcelFileDescriptor mFileDescriptor; /** * Page that is currently shown on the screen. */ private PdfRenderer.Page mCurrentPage; /** * PDF page index */ private int mPageIndex; /** * {@link android.graphics.pdf.PdfRenderer} to render the PDF. */ private PdfRenderer mPdfRenderer; private ImageView mImageView; private Button btnPrevious; private Button btnNext; @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); if (null != mCurrentPage) { outState.putInt(STATE_CURRENT_PAGE_INDEX, mCurrentPage.getIndex()); } } @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_third_pdf_view); mImageView = (ImageView) findViewById(R.id.iv); btnPrevious = (Button) findViewById(R.id.btn_previous); btnNext = (Button) findViewById(R.id.btn_next); btnPrevious.setOnClickListener(this); btnNext.setOnClickListener(this); mPageIndex = 0; // If there is a savedInstanceState (screen orientations, etc.), we restore the page index. if (null != savedInstanceState) { mPageIndex = savedInstanceState.getInt(STATE_CURRENT_PAGE_INDEX, 0); } } @Override protected void onStart() { super.onStart(); try { openRenderer(this); showPage(mPageIndex); } catch (IOException e) { e.printStackTrace(); Toast.makeText(this, "Error! " + e.getMessage(), Toast.LENGTH_SHORT).show(); } } /** * Sets up a {@link android.graphics.pdf.PdfRenderer} and related resources. */ private void openRenderer(Context context) throws IOException { // In this sample, we read a PDF from the assets directory. File file = new File(context.getCacheDir(), FILENAME); if (!file.exists()) { // Since PdfRenderer cannot handle the compressed asset file directly, we copy it into // the cache directory. InputStream asset = getAssets().open(FILENAME); FileOutputStream output = new FileOutputStream(file); final byte[] buffer = new byte[1024]; int size; while ((size = asset.read(buffer)) != -1) { output.write(buffer, 0, size); } asset.close(); output.close(); } mFileDescriptor = ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY); // This is the PdfRenderer we use to render the PDF. if (mFileDescriptor != null) { mPdfRenderer = new PdfRenderer(mFileDescriptor); } } /** * Shows the specified page of PDF to the screen. * * @param index The page index. */ private void showPage(int index) { if (mPdfRenderer.getPageCount() <= index) { return; } // Make sure to close the current page before opening another one. if (null != mCurrentPage) { mCurrentPage.close(); } // Use `openPage` to open a specific page in PDF. mCurrentPage = mPdfRenderer.openPage(index); // Important: the destination bitmap must be ARGB (not RGB). Bitmap bitmap = Bitmap.createBitmap(mCurrentPage.getWidth(), mCurrentPage.getHeight(), Bitmap.Config.ARGB_8888); // Here, we render the page onto the Bitmap. // To render a portion of the page, use the second and third parameter. Pass nulls to get // the default result. // Pass either RENDER_MODE_FOR_DISPLAY or RENDER_MODE_FOR_PRINT for the last parameter. mCurrentPage.render(bitmap, null, null, PdfRenderer.Page.RENDER_MODE_FOR_DISPLAY); // We are ready to show the Bitmap to user. mImageView.setImageBitmap(bitmap); updateUi(); } /** * Closes the {@link android.graphics.pdf.PdfRenderer} and related resources. * * @throws java.io.IOException When the PDF file cannot be closed. */ private void closeRenderer() throws IOException { if (null != mCurrentPage) { mCurrentPage.close(); } mPdfRenderer.close(); mFileDescriptor.close(); } @Override protected void onDestroy() { try { closeRenderer(); } catch (IOException e) { e.printStackTrace(); } super.onDestroy(); } @Override protected void onPause() { try { closeRenderer(); } catch (IOException e) { e.printStackTrace(); } super.onPause(); } /** * Updates the state of 2 control buttons in response to the current page index. */ private void updateUi() { int index = mCurrentPage.getIndex(); int pageCount = mPdfRenderer.getPageCount(); btnPrevious.setEnabled(0 != index); btnNext.setEnabled(index + 1 < pageCount); // setTitle(getString(R.string.app_name_with_index, index + 1, pageCount)); } /** * Gets the number of pages in the PDF. This method is marked as public for testing. * * @return The number of pages. */ public int getPageCount() { return mPdfRenderer.getPageCount(); } @Override public void onClick(View v) { switch (v.getId()) { case R.id.btn_previous: { // Move to the previous page showPage(mCurrentPage.getIndex() - 1); break; } case R.id.btn_next: { // Move to the next page showPage(mCurrentPage.getIndex() + 1); break; } } } }
三.Android PdfViewer
它是基于PdfiumAndroid解码PDF文件
compile 'com.github.barteksc:android-pdf-viewer:2.8.1'
1.适用场景很广,使用简单,唯一缺陷就是会明显增大包体积,所以就不多介绍了