如何在 Android 中渲染 PDF 文件
Posted
技术标签:
【中文标题】如何在 Android 中渲染 PDF 文件【英文标题】:How to render a PDF file in Android 【发布时间】:2011-01-28 05:16:02 【问题描述】:android 在其库中不支持 PDF。有没有办法在 Android 应用程序中呈现 PDF 文件?
【问题讨论】:
这是显示 pdf 文件的好例子。需要参考下面链接中的 Readme.txt 文件才能使用它。 github.com/jblough/Android-Pdf-Viewer-Library 这里是使用该库的示例:***.com/a/16294833/2027232 你看***.com/questions/22498937/… 【参考方案1】:由于 API Level 21 (Lollipop) Android 提供了PdfRenderer class:
// create a new renderer
PdfRenderer renderer = new PdfRenderer(getSeekableFileDescriptor());
// let us just render all pages
final int pageCount = renderer.getPageCount();
for (int i = 0; i < pageCount; i++)
Page page = renderer.openPage(i);
// say we render for showing on the screen
page.render(mBitmap, null, null, Page.RENDER_MODE_FOR_DISPLAY);
// do stuff with the bitmap
// close the page
page.close();
// close the renderer
renderer.close();
有关详细信息,请参阅sample app。
对于较旧的 API,我推荐 Android PdfViewer library,它非常快速且易于使用,根据 Apache License 2.0 获得许可:
pdfView.fromAsset(String)
.pages(0, 2, 1, 3, 3, 3) // all pages are displayed by default
.enableSwipe(true)
.swipeHorizontal(false)
.enableDoubletap(true)
.defaultPage(0)
.onDraw(onDrawListener)
.onLoad(onLoadCompleteListener)
.onPageChange(onPageChangeListener)
.onPageScroll(onPageScrollListener)
.onError(onErrorListener)
.enableAnnotationRendering(false)
.password(null)
.scrollHandle(null)
.load();
【讨论】:
PDFView 库很棒,但请注意,它属于 GNU General Public,而不是 Lesser GPL。这可能难以包含在商业软件中。 是的,@Sam 是对的。就我个人而言,由于旧版 Android 版本的许可问题,我不得不使用 PDF.js,但渲染速度极慢,并且由于 Java-javascript 通信而难以使其工作。 对于旧版本,有什么方法可以在不下载文件并将文件存储在应用程序的某个位置的情况下显示它们? 你能用PDFView Library
做一个更详细的例子吗?我们应该在pdfName
中输入什么?函数onDraw
、onLoad
和onPageChange
监听器是什么样的?感谢您的帮助:)
在答案中添加了一个在 Apache License 2.0 下获得许可的新 PDF 库。【参考方案2】:
public class MyPdfViewActivity extends Activity
@Override
protected void onCreate(Bundle savedInstanceState)
super.onCreate(savedInstanceState);
WebView mWebView=new WebView(MyPdfViewActivity.this);
mWebView.getSettings().setJavaScriptEnabled(true);
mWebView.getSettings().setPluginsEnabled(true);
mWebView.loadUrl("https://docs.google.com/gview?embedded=true&url="+LinkTo);
setContentView(mWebView);
【讨论】:
这真的不是很干净。一方面,如果 Google 决定更改他们的 Google Docs URL,您的应用程序将会崩溃,您必须推送更新,以便用户能够再次查看文档。 这只是让我查看第一页,底部的“下载”链接无效...有什么想法吗?此外,似乎 setPluginsEnabled 不再是 WebView 类的一部分了。 如果没有 Internet 连接,这将不起作用,并且需要 INTERNET 权限 它曾经工作过。但是最近,它一直显示“哎呀!预览此文档时出现问题”,我猜谷歌已经更改了他们身边的一些东西导致了这个问题。 对我来说,加载和滚动文档真的很慢。【参考方案3】:我根据对这篇文章和其他类似帖子的一些答案做出了一种混合方法:
此解决方案检查是否安装了 PDF 阅读器应用程序并执行以下操作: - 如果安装了阅读器,请将 PDF 文件下载到设备并启动 PDF 阅读器应用程序 - 如果没有安装阅读器,询问用户是否想通过 Google Drive 在线查看 PDF 文件
注意!此解决方案使用 API9(Android 2.3 或 Gingerbread)中引入的 Android DownloadManager
类。这意味着它不适用于 Android 2.2 或更早版本。
我写了一篇关于它的博客文章here,但为了完整起见,我在下面提供了完整的代码:
public class PDFTools
private static final String GOOGLE_DRIVE_PDF_READER_PREFIX = "http://drive.google.com/viewer?url=";
private static final String PDF_MIME_TYPE = "application/pdf";
private static final String html_MIME_TYPE = "text/html";
/**
* If a PDF reader is installed, download the PDF file and open it in a reader.
* Otherwise ask the user if he/she wants to view it in the Google Drive online PDF reader.<br />
* <br />
* <b>BEWARE:</b> This method
* @param context
* @param pdfUrl
* @return
*/
public static void showPDFUrl( final Context context, final String pdfUrl )
if ( isPDFSupported( context ) )
downloadAndOpenPDF(context, pdfUrl);
else
askToOpenPDFThroughGoogleDrive( context, pdfUrl );
/**
* Downloads a PDF with the Android DownloadManager and opens it with an installed PDF reader app.
* @param context
* @param pdfUrl
*/
@TargetApi(Build.VERSION_CODES.GINGERBREAD)
public static void downloadAndOpenPDF(final Context context, final String pdfUrl)
// Get filename
final String filename = pdfUrl.substring( pdfUrl.lastIndexOf( "/" ) + 1 );
// The place where the downloaded PDF file will be put
final File tempFile = new File( context.getExternalFilesDir( Environment.DIRECTORY_DOWNLOADS ), filename );
if ( tempFile.exists() )
// If we have downloaded the file before, just go ahead and show it.
openPDF( context, Uri.fromFile( tempFile ) );
return;
// Show progress dialog while downloading
final ProgressDialog progress = ProgressDialog.show( context, context.getString( R.string.pdf_show_local_progress_title ), context.getString( R.string.pdf_show_local_progress_content ), true );
// Create the download request
DownloadManager.Request r = new DownloadManager.Request( Uri.parse( pdfUrl ) );
r.setDestinationInExternalFilesDir( context, Environment.DIRECTORY_DOWNLOADS, filename );
final DownloadManager dm = (DownloadManager) context.getSystemService( Context.DOWNLOAD_SERVICE );
BroadcastReceiver onComplete = new BroadcastReceiver()
@Override
public void onReceive(Context context, Intent intent)
if ( !progress.isShowing() )
return;
context.unregisterReceiver( this );
progress.dismiss();
long downloadId = intent.getLongExtra( DownloadManager.EXTRA_DOWNLOAD_ID, -1 );
Cursor c = dm.query( new DownloadManager.Query().setFilterById( downloadId ) );
if ( c.moveToFirst() )
int status = c.getInt( c.getColumnIndex( DownloadManager.COLUMN_STATUS ) );
if ( status == DownloadManager.STATUS_SUCCESSFUL )
openPDF( context, Uri.fromFile( tempFile ) );
c.close();
;
context.registerReceiver( onComplete, new IntentFilter( DownloadManager.ACTION_DOWNLOAD_COMPLETE ) );
// Enqueue the request
dm.enqueue( r );
/**
* Show a dialog asking the user if he wants to open the PDF through Google Drive
* @param context
* @param pdfUrl
*/
public static void askToOpenPDFThroughGoogleDrive( final Context context, final String pdfUrl )
new AlertDialog.Builder( context )
.setTitle( R.string.pdf_show_online_dialog_title )
.setMessage( R.string.pdf_show_online_dialog_question )
.setNegativeButton( R.string.pdf_show_online_dialog_button_no, null )
.setPositiveButton( R.string.pdf_show_online_dialog_button_yes, new OnClickListener()
@Override
public void onClick(DialogInterface dialog, int which)
openPDFThroughGoogleDrive(context, pdfUrl);
)
.show();
/**
* Launches a browser to view the PDF through Google Drive
* @param context
* @param pdfUrl
*/
public static void openPDFThroughGoogleDrive(final Context context, final String pdfUrl)
Intent i = new Intent( Intent.ACTION_VIEW );
i.setDataAndType(Uri.parse(GOOGLE_DRIVE_PDF_READER_PREFIX + pdfUrl ), HTML_MIME_TYPE );
context.startActivity( i );
/**
* Open a local PDF file with an installed reader
* @param context
* @param localUri
*/
public static final void openPDF(Context context, Uri localUri )
Intent i = new Intent( Intent.ACTION_VIEW );
i.setDataAndType( localUri, PDF_MIME_TYPE );
context.startActivity( i );
/**
* Checks if any apps are installed that supports reading of PDF files.
* @param context
* @return
*/
public static boolean isPDFSupported( Context context )
Intent i = new Intent( Intent.ACTION_VIEW );
final File tempFile = new File( context.getExternalFilesDir( Environment.DIRECTORY_DOWNLOADS ), "test.pdf" );
i.setDataAndType( Uri.fromFile( tempFile ), PDF_MIME_TYPE );
return context.getPackageManager().queryIntentActivities( i, PackageManager.MATCH_DEFAULT_ONLY ).size() > 0;
【讨论】:
【参考方案4】:你可以通过 import 使用一个简单的方法
implementation 'com.github.barteksc:android-pdf-viewer:2.8.2'
XML 代码是
<com.github.barteksc.pdfviewer.PDFView
android:id="@+id/pdfv"
android:layout_
android:layout_>
</com.github.barteksc.pdfviewer.PDFView>
只需声明一个文件并将其添加到资产文件夹并指定名称
PDFView pdfView=findViewById(R.id.pdfv);
pdfView.fromAsset("agl.pdf").load();
【讨论】:
如何在 Kotlin 中声明 PDFView? @NicholasFarmer 检查此链接***.com/questions/56613766/… 请注意,这会在您的应用顶部增加 16~18MB。这个库使用PdfiumAndroid
,它本身已经是 18.4MB
提示:agl.pdf
文件应该存在于assets
文件夹中
这个库会将您的应用程序大小增加到 16 Mb。所以使用时要小心【参考方案5】:
在此处下载源代码 (Display PDF file inside my android application)
在您的成绩中添加此依赖项:
编译com.github.barteksc:android-pdf-viewer:2.0.3
activity_main.xml
<RelativeLayout android:layout_
android:layout_
android:background="#ffffff"
xmlns:android="http://schemas.android.com/apk/res/android" >
<TextView
android:layout_
android:layout_
android:background="@color/colorPrimaryDark"
android:text="View PDF"
android:textColor="#ffffff"
android:id="@+id/tv_header"
android:textSize="18dp"
android:gravity="center"></TextView>
<com.github.barteksc.pdfviewer.PDFView
android:id="@+id/pdfView"
android:layout_below="@+id/tv_header"
android:layout_
android:layout_/>
</RelativeLayout>
MainActivity.java
import android.app.Activity;
import android.database.Cursor;
import android.net.Uri;
import android.provider.OpenableColumns;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import com.github.barteksc.pdfviewer.PDFView;
import com.github.barteksc.pdfviewer.listener.OnLoadCompleteListener;
import com.github.barteksc.pdfviewer.listener.OnPageChangeListener;
import com.github.barteksc.pdfviewer.scroll.DefaultScrollHandle;
import com.shockwave.pdfium.PdfDocument;
import java.util.List;
public class MainActivity extends Activity implements OnPageChangeListener,OnLoadCompleteListener
private static final String TAG = MainActivity.class.getSimpleName();
public static final String SAMPLE_FILE = "android_tutorial.pdf";
PDFView pdfView;
Integer pageNumber = 0;
String pdfFileName;
@Override
protected void onCreate(Bundle savedInstanceState)
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
pdfView= (PDFView)findViewById(R.id.pdfView);
displayFromAsset(SAMPLE_FILE);
private void displayFromAsset(String assetFileName)
pdfFileName = assetFileName;
pdfView.fromAsset(SAMPLE_FILE)
.defaultPage(pageNumber)
.enableSwipe(true)
.swipeHorizontal(false)
.onPageChange(this)
.enableAnnotationRendering(true)
.onLoad(this)
.scrollHandle(new DefaultScrollHandle(this))
.load();
@Override
public void onPageChanged(int page, int pageCount)
pageNumber = page;
setTitle(String.format("%s %s / %s", pdfFileName, page + 1, pageCount));
@Override
public void loadComplete(int nbPages)
PdfDocument.Meta meta = pdfView.getDocumentMeta();
printBookmarksTree(pdfView.getTableOfContents(), "-");
public void printBookmarksTree(List<PdfDocument.Bookmark> tree, String sep)
for (PdfDocument.Bookmark b : tree)
Log.e(TAG, String.format("%s %s, p %d", sep, b.getTitle(), b.getPageIdx()));
if (b.hasChildren())
printBookmarksTree(b.getChildren(), sep + "-");
【讨论】:
提示:assetFileName
pdf 文件应该存在于assets
文件夹中【参考方案6】:
我终于能够修改butelo's code 以使用pdf.js
打开Android 文件系统中的任何PDF 文件。代码可以找到on my GitHub
我所做的是将pdffile.js
修改为读取HTML 参数file
,如下所示:
var url = getURLParameter('file');
function getURLParameter(name)
return decodeURIComponent((new RegExp('[?|&]' + name + '=' + '([^&;]+?)(&|#|;|$)').exec(location.search)||[,""])[1].replace(/\+/g, '%20'))||null
所以你需要做的只是在index.html
之后附加文件路径,如下所示:
Uri path = Uri.parse(Environment.getExternalStorageDirectory().toString() + "/data/test.pdf");
webView.loadUrl("file:///android_asset/pdfviewer/index.html?file=" + path);
更新 path
变量以指向 Adroid 文件系统中的有效 PDF。
【讨论】:
嗨,Paul,我也使用过这个例子,但是它在 API 级别 16 的 WebView 上显示空白屏幕,你有什么关于这个问题的吗?? pdf文件存储在哪里?您无法从资产文件夹加载 pdf。您可以从 SD 卡或应用的受保护内部存储中加载。还要检查你的 logcat 是否有任何与 webview 相关的错误。 Uri 路径 = Uri.parse(Environment.getExternalStorageDirectory().toString() + "/example4.pdf"); webView.loadUrl("file:///android_asset/pdfviewer/index.html?file="+path); @Paul 我正在使用上面的代码,它在 API 19 上运行良好,但在下面它不起作用。为什么?? 请参阅此内容以了解与旧版本 Android 的兼容性:github.com/pauldmps/Android-pdf.js/issues/1github.com/pauldmps/Android-pdf.js/issues/2【参考方案7】:为了增加一点亮点,我将不得不使用 Mozilla 的 pdf.js 解决方案。这是一个已经写得很好的实现的链接:https://bitbucket.org/butelo/pdfviewer/。
以下是我在 Android Activity 中添加的编辑:
private String getInternalPDFURL(String interalPDFName)
return "file:///android_asset/pdfviewer/index.html?pdf=" + interalPDFName + ".pdf";
这是我在pdffile.js
中所做的修改:
var url = '../' + getPDFURL();
function getPDFURL()
var query = window.location.search.substring(1);
var vars = query.split("=");
var pdfPage = vars[1];
return pdfPage;
【讨论】:
【参考方案8】:我使用以下代码通过 Wi-Fi 打开和打印 PDF。我正在发送我的整个代码,希望对您有所帮助。
public class MainActivity extends Activity
int Result_code = 1;
@Override
protected void onCreate(Bundle savedInstanceState)
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button mButton = (Button)findViewById(R.id.button1);
mButton.setOnClickListener(new View.OnClickListener()
@Override
public void onClick(View v)
// TODO Auto-generated method stub
PrintManager printManager = (PrintManager)getSystemService(Context.PRINT_SERVICE);
String jobName = " Document";
printManager.print(jobName, pda, null);
);
public void openDocument(String name)
Intent intent = new Intent(android.content.Intent.ACTION_VIEW);
File file = new File(name);
String extension = android.webkit.MimeTypeMap.getFileExtensionFromUrl(Uri.fromFile(file).toString());
String mimetype = android.webkit.MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
if (extension.equalsIgnoreCase("") || mimetype == null)
// if there is no extension or there is no definite mimetype, still try to open the file
intent.setDataAndType(Uri.fromFile(file), "text/*");
else
intent.setDataAndType(Uri.fromFile(file), mimetype);
// custom message for the intent
startActivityForResult((Intent.createChooser(intent, "Choose an Application:")), Result_code);
//startActivityForResult(intent, Result_code);
//Toast.makeText(getApplicationContext(),"There are no email clients installed.", Toast.LENGTH_SHORT).show();
@SuppressLint("NewApi")
PrintDocumentAdapter pda = new PrintDocumentAdapter()
@Override
public void onWrite(PageRange[] pages, ParcelFileDescriptor destination, CancellationSignal cancellationSignal, WriteResultCallback callback)
InputStream input = null;
OutputStream output = null;
try
String filename = Environment.getExternalStorageDirectory() + "/" + "Holiday.pdf";
File file = new File(filename);
input = new FileInputStream(file);
output = new FileOutputStream(destination.getFileDescriptor());
byte[] buf = new byte[1024];
int bytesRead;
while ((bytesRead = input.read(buf)) > 0)
output.write(buf, 0, bytesRead);
callback.onWriteFinished(new PageRange[]PageRange.ALL_PAGES);
catch (FileNotFoundException ee)
//Catch exception
catch (Exception e)
//Catch exception
finally
try
input.close();
output.close();
catch (IOException e)
e.printStackTrace();
@Override
public void onLayout(PrintAttributes oldAttributes, PrintAttributes newAttributes, CancellationSignal cancellationSignal, LayoutResultCallback callback, Bundle extras)
if (cancellationSignal.isCanceled())
callback.onLayoutCancelled();
return;
// int pages = computePageCount(newAttributes);
PrintDocumentInfo pdi = new PrintDocumentInfo.Builder("Name of file").setContentType(PrintDocumentInfo.CONTENT_TYPE_DOCUMENT).build();
callback.onLayoutFinished(pdi, true);
;
【讨论】:
【参考方案9】:Android webview 中无论如何都无法预览 pdf 文档。如果你想预览 base64 pdf。需要第三方库。
构建.Gradle
compile 'com.github.barteksc:android-pdf-viewer:2.7.0'
dialog_pdf_viewer
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_
android:layout_
android:gravity="center_horizontal"
android:orientation="vertical">
<ImageView
android:id="@+id/dialog_pdf_viewer_close"
style="@style/ExitButtonImageViewStyle"
android:src="@drawable/popup_exit" />
<LinearLayout
android:layout_
android:layout_
android:layout_weight="1"
android:background="@color/white"
android:orientation="vertical">
<com.github.barteksc.pdfviewer.PDFView
android:id="@+id/pdfView"
android:layout_
android:layout_ />
</LinearLayout>
<View style="@style/HorizontalLine" />
<com.pozitron.commons.customviews.ButtonFont
android:id="@+id/dialog_pdf_viewer_button"
style="@style/ButtonPrimary2"
android:layout_
android:layout_
android:padding="15dp"
android:text="@string/agreed" />
</LinearLayout>
DailogPDFViewer.java
public class DialogPdfViewer extends Dialog
PDFView pdfView;
byte[] decodedString;
public interface OnDialogPdfViewerListener
void onAgreeClick(DialogPdfViewer dialogFullEula);
void onCloseClick(DialogPdfViewer dialogFullEula);
public DialogPdfViewer(Context context, String base64, final DialogPdfViewer.OnDialogPdfViewerListener onDialogPdfViewerListener)
super(context);
setContentView(R.layout.dialog_pdf_viewer);
findViewById(R.id.dialog_pdf_viewer_close).setOnClickListener(new View.OnClickListener()
@Override
public void onClick(View v)
onDialogPdfViewerListener.onCloseClick(DialogPdfViewer.this);
);
findViewById(R.id.dialog_pdf_viewer_button).setOnClickListener(new View.OnClickListener()
@Override
public void onClick(View v)
onDialogPdfViewerListener.onAgreeClick(DialogPdfViewer.this);
);
decodedString = Base64.decode(base64.toString(), Base64.DEFAULT);
pdfView = ((PDFView) findViewById(R.id.pdfView));
pdfView.fromBytes(decodedString).load();
setOnKeyListener(new OnKeyListener()
@Override
public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event)
if (keyCode == KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_DOWN)
onDialogPdfViewerListener.onCloseClick(DialogPdfViewer.this);
return true;
);
【讨论】:
【参考方案10】:最好的方法是使用 android lollipop 及更高版本提供的 PDF Render 类 它会比其他用原生 SDK 编写的库更轻量级
https://github.com/SaurabhDhage/Lightweight-pdf-viewer
【讨论】:
【参考方案11】:以下是如何在不使用任何第三方库的情况下从 PDF 文件中获取 Bitmap
的缩略图。
private fun loadThumbnailFromPdfFile(file:File, thumbnailImageView: ImageView)
Log.d(TAG, "loadThumbnailFromPdfFile: ")
val mFileDescriptor = ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY)
// This is the PdfRenderer we use to render the PDF.
val mPdfRenderer = PdfRenderer(mFileDescriptor)
//check if pdf have no pages then we can't show pdf thumbnail
if (mPdfRenderer.pageCount <= 0)
//No pages in pdf, can't show thumbnail
Log.d(TAG, "loadThumbnailFromPdfFile: No pages in pdf")
else
//There are page(s) in pdf, can show pdf thumbnail
//Use `openPage` to open a specific page in PDF.
val mCurrentPage = mPdfRenderer.openPage(0)
// Important: the destination bitmap must be ARGB (not RGB).
val bitmap = Bitmap.createBitmap(mCurrentPage.width, mCurrentPage.height, 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.
thumbnailImageView.setImageBitmap(bitmap)
【讨论】:
【参考方案12】:我在使用 GoogleDrive 选项时遇到了问题,有时它会无法加载文档和“com.github.barteksc:android-pdf-viewer:2.7.0”,尽管它的文档使加载文档看起来很容易从一个链接,我没有让它工作。它总是抛出 FileNotFoundException,所以经过进一步研究,我了解到它会下载 pdf,所以你必须让它指向那个文档,我觉得这有点乏味。
您可以在此处尝试使用此库。它非常易于使用并且至今仍在维护,我不知道几年后他们是否会停止更新它。它会缓存文档,您甚至不需要跟踪它的路径。
https://levelup.gitconnected.com/open-pdf-files-in-android-without-webviews-or-intents-3cc960752cca
【讨论】:
以上是关于如何在 Android 中渲染 PDF 文件的主要内容,如果未能解决你的问题,请参考以下文章