在安卓項目中,加載PDF文件,是一個比較常見的需求披摄。又分為兩大類壳贪,
1.加載網絡PDF
2.加載一個本地靜態(tài)PDF。
查閱資料饮睬,縱觀網上在安卓中打開PDF的各種方式,大致可以分為以下幾類,
1.直接使用第三方軟件打開(包括瀏覽器打開和第三方軟件打開)
如果是在app內部加載PDF文件量九,雖然安卓原生API對于PDF的支持又不是太好,但是各種第三方專門的閱讀或者辦公軟件支持的是很不錯的颂碧,可以通過Intent配置data和type實現(xiàn)荠列,:
其中,在實際需求中又會分為加載本地PDF和網絡PDF的情況载城。
使用瀏覽器打開PDF:(APP外部打開肌似,適用于加載網絡PDF)
public static void openPDFInBrowser(Context context, String url) {
Uri uri = Uri.parse(url);
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
intent.putExtra(Browser.EXTRA_APPLICATION_ID, context.getPackageName());
try {
context.startActivity(intent);
} catch (ActivityNotFoundException e) {
Log.w("error", "Activity was not found for intent, " + intent.toString());
}
}
使用手機上已安裝的可以打開PDF的第三方軟件來打開PDF:(APP外部打開,適用于加載本地PDF)
(使用這種方式缺點是:手機上如果一個可以打開PDF的軟件诉瓦,那么就尷尬了~)
public static void openPDFInNative(Context context, String FILE_NAME) {
File file = new File(context.getExternalCacheDir(),FILE_NAME);
Intent intent = new Intent(Intent.ACTION_VIEW);
Uri uri = Uri.fromFile(file);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setDataAndType(uri, "application/pdf");
try {
context.startActivity(intent);
} catch (ActivityNotFoundException e) {
Log.w("URLSpan", "Activity was not found for intent, " + intent.toString());
}
}
個人認為川队,這是加載PDF最好的方式。
2.連接Google服務器解析
安卓的WebView不支持PDF解析睬澡,因此通過連接Google的一個服務器轉換成功后返回給WebView顯示固额。但是,但是煞聪,但是呢斗躏,大家都懂的,天朝和Google之間有一道高高的墻昔脯。方法還是貼出來啄糙,作為國際化APP的一種方案馋艺。
mWebView.loadUrl("http://docs.google.com/gview?embedded=true&url="+ pdfUrl);
3.用第三方庫加載
Github上有一個Java開源項目 https://github.com/barteksc/AndroidPdfViewer ,
這個庫的大致原理,是內置了一個PDF解析器迈套,以流的方式將網絡PDF從網上Down下來捐祠,然后再以流的方式將其還原成PDF展示出來(具體細節(jié)沒關注)。親測中桑李,這個庫每次進入webview頁面都會解析加載一遍PDF踱蛀,如果PDF過大,費時無緩存不說贵白,最致命的問題乃是率拒,
APK包體積會瞬間增大15M左右,
具體原因不明禁荒,估計應該是內置PDF解析器的問題猬膨。于是,此方法被我拋棄了呛伴。
4.使用Moliza開源的Pdf.js
這個JS類庫也是很強大的,配合原生的WebView使用,支持預覽,縮放,翻頁的功能,實現(xiàn)效果和WKWebView沒差勃痴。同樣也有體積問題,全部放到本地apk的話包大小差不多會增加5M左右。但相比上一種方式還是輕量一些:
http://www.jb51.net/article/136364.htm
5.使用安卓自帶的PdfRenderer類加載
如果要求支持的功能不是很多,用安卓提供的PdfRender就可以滿足需求了热康。PdfRender加載多頁的話可以配合ViewPager或者RecyclerView使用.需要注意的是使用PdfRender需要先將PDF文件下載到本地沛申,是線程不安全的,并且API>=21才能使用姐军。因為這種方式是將PDF下載到本地铁材,于是就產生了新問題:占內存。如果是靜態(tài)的PDF文件不大還好奕锌,但是如果是頻繁加載網絡PDF的需求著觉,那就頭疼了,這種方式需要做好定時清理刪除PDF的工作惊暴,否則饼丘,GG。
這里提供的示例是阿里巴巴Android開發(fā)手冊,放到assets目錄下.
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public class MainActivity extends AppCompatActivity {
private ViewPager vpPdf;
private LayoutInflater mInflater;
private ParcelFileDescriptor mDescriptor;
private PdfRenderer mRenderer;
public static final String FILE_NAME = "alibaba.pdf";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mInflater = LayoutInflater.from(this);
vpPdf =findViewById(R.id.vp_pdf);
try {
openRender();
} catch (IOException e) {
e.printStackTrace();
}
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
private void openRender() throws IOException {
File file = new File(getExternalCacheDir(), FILE_NAME);
if (!file.exists()) {
//復制文件到本地存儲
InputStream asset = getAssets().open(FILE_NAME);
FileOutputStream fos = new FileOutputStream(file);
byte[] buffer = new byte[1024];
int size;
while ((size = asset.read(buffer)) != -1) {
fos.write(buffer, 0, size);
}
asset.close();
fos.close();
}
//初始化PdfRender
mDescriptor = ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
if (mDescriptor != null) {
mRenderer = new PdfRenderer(mDescriptor);
}
//初始化ViewPager的適配器并綁定
MyAdapter adapter = new MyAdapter();
vpPdf.setAdapter(adapter);
}
@Override
protected void onDestroy() {
//銷毀頁面的時候釋放資源,習慣
try {
closeRenderer();
} catch (IOException e) {
e.printStackTrace();
}
super.onDestroy();
}
private void closeRenderer() throws IOException{
mRenderer.close();
mDescriptor.close();
}
class MyAdapter extends PagerAdapter{
@Override
public int getCount() {
return mRenderer.getPageCount();
}
@Override
public boolean isViewFromObject(View view, Object object) {
return view==object;
}
@Override
public Object instantiateItem(ViewGroup container, int position) {
View view = mInflater.inflate(R.layout.item_pdf, null);
PhotoView pvPdf = view.findViewById(R.id.iv_pdf);
pvPdf.enable();
if (getCount() <= position) {
return view;
}
PdfRenderer.Page currentPage = mRenderer.openPage(position);
Bitmap bitmap = Bitmap.createBitmap(1080, 1760, Bitmap.Config
.ARGB_8888);
currentPage.render(bitmap, null, null, PdfRenderer.Page.RENDER_MODE_FOR_DISPLAY);
pvPdf.setImageBitmap(bitmap);
//關閉當前Page對象
currentPage.close();
container.addView(view);
return view;
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
//銷毀需要銷毀的視圖
container.removeView((View) object);
}
}
}
PdfRender的大致調用方式是:
- 初始化PdfRender,將PDF文件轉換為ParcelFileDescriptor作為入參
- 通過getPageCount()獲得Pdf的總頁數(shù)
- 循環(huán)遍歷,通過openPage()獲得一個封裝了當前頁Pdf參數(shù)的內部類Page
- 創(chuàng)建一個Bitmap對象,,用上步獲取的Page加載,相當于把當前Pdf頁的內容渲染到了Bitmap上
- 對Bitmap進行你想要的操作,每次循環(huán)結束后關閉當前Page.
大部分都是常規(guī)操作,但要注意兩點:
1,每次循環(huán)創(chuàng)建的Page對象在使用完畢后必須調用close().否則其內部的openPage()判斷會拋異常;
2.Bitmap占用內存較大,如果用ViewPager加載一定要重寫Adapter中的destroyItem(),及時的將需要銷毀的視圖從父容器中移除,否則快速滑動幾頁后會OOM