Android PDF閱讀開發

最近項目裏,需要集成PDF閱讀,翻閱了很多網站,發現Android系統不支持PDF閱讀,網上現有的庫和插件,都會增大apk的體積,綜合比較了一下,解決方案有如下幾種:

1.谷歌提供了在線閱讀,在webView中調用GoogleDocs

     但是由於國內手機無法獲取Google提供支持,所以這種方案基本被否決,其使用方式如下:

public void setDocumentPath(final String path) {
    WebView webView = (WebView) findViewById(R.id.webview);
    webView.getSettings().setJavaScriptEnabled(true);
    webView.getSettings().setPluginsEnabled(true);
    webView.loadUrl("https://docs.google.com/viewer?url=http://www.asce1885.com/cms/wwwroot/ng/downLoad/011615200732.pdf");
}

2.最簡單的方式就是跳第三方的瀏覽器,下載閱讀

   下載完成後,如果瀏覽器含有PDF閱讀插件,便可以在瀏覽器中直接打開,否則還要下載PDF閱讀器方可閱讀,使用方式如下:

public Intent getPdfFileIntent(File file) {
    Intent intent = new Intent("android.intent.action.VIEW");
    intent.addCategory("android.intent.category.DEFAULT");
    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    Uri uri = Uri.fromFile(file);
    intent.setDataAndType(uri, "application/pdf");
    return Intent.createChooser(intent, "Open File");
}

3.利用騰訊X5內核瀏覽器的Tbs,下載閱讀

  下載完成後,首先加載X5插件,中間會有一段時間特別緩慢,官方建議可以再application中進行加載初始化,但這會導致APP啓動時間增長,更糟糕的是卡在啓動頁,出現的原因:手機沒有X5內核瀏覽器,需要開啓服務下載(鑑於自己的項目,僅僅是猜測),其具體的使用方法如下(我這裏自己封裝了一個下載工具,下面的幾種方式都採用這個工具):

import android.app.DownloadManager;
import android.app.DownloadManager.Query;
import android.app.DownloadManager.Request;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.database.Cursor;
import android.net.Uri;
import android.os.Environment;
import android.util.Log;


import com.mysteel.android.steelphone.bean.ebe.EBPdfMsg;

import org.greenrobot.eventbus.EventBus;

import java.io.File;

import static android.app.DownloadManager.ACTION_DOWNLOAD_COMPLETE;
import static android.app.DownloadManager.ACTION_NOTIFICATION_CLICKED;
import static android.app.DownloadManager.STATUS_FAILED;
import static android.app.DownloadManager.STATUS_PAUSED;
import static android.app.DownloadManager.STATUS_PENDING;
import static android.app.DownloadManager.STATUS_RUNNING;
import static android.app.DownloadManager.STATUS_SUCCESSFUL;

public class DownloadPdfUtils {

    //下載器
    private DownloadManager downloadManager;

    //上下文
    private Context mContext;

    //下載的ID
    private long downloadId;

    private boolean mReceiverTag = false;

    public DownloadPdfUtils(Context context) {
        if (context == null) {
            return;
        }
        this.mContext = context;

    }


    public void downloadUrl(String url, String name) {


        //創建下載任務
        Request request = new Request(Uri.parse(url));
        //移動網絡下是否允許
        request.setAllowedOverRoaming(false);

        //在通知欄中顯示,默認就是顯示的
        request.setNotificationVisibility(Request.VISIBILITY_HIDDEN);
        request.setVisibleInDownloadsUi(true);

        //設置下載路徑
        final String sdcard = Environment.getExternalStorageDirectory().getAbsolutePath();
        final String pdfPath = sdcard + "/mysteel/pdf";
        if (!(new File(pdfPath)).exists()) {
            (new File(pdfPath)).mkdirs();
        }
        File file = new File(pdfPath, name);

        request.setDestinationUri(Uri.fromFile(file));

        downloadManager = ((DownloadManager) mContext.getSystemService(Context.DOWNLOAD_SERVICE));
        removePdfFile();
        downloadId = downloadManager.enqueue(request);
        mContext.registerReceiver(receiver, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));
        mReceiverTag = true;
    }

    private BroadcastReceiver receiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            if (intent.getAction() == ACTION_DOWNLOAD_COMPLETE) {
                checkStatus();
            }
        }
    };

    private BroadcastReceiver receiver1 = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            if (intent.getAction() == DownloadManager.ACTION_NOTIFICATION_CLICKED) {
                downloadManager.remove(downloadId);
            }
        }
    };

    /**
     * 下載狀態
     */
    private void checkStatus() {
        Query query = new Query();

        //通過下載的ID查找
        Cursor cursor = downloadManager.query(query);

        if (cursor.moveToFirst()) {
            int status = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_STATUS));
            switch (status) {
                case STATUS_PAUSED:
                    break;
                case STATUS_PENDING:
                    break;
                case STATUS_RUNNING:
                    break;
                case STATUS_SUCCESSFUL:
                    EBPdfMsg event = new EBPdfMsg();
                    event.setUrl(queryUrl());
                    EventBus.getDefault().post(event);
                    break;
                case STATUS_FAILED:
                    break;
                default:
                    break;
            }
        }
        cursor.close();
        mContext.unregisterReceiver(receiver);
        mReceiverTag = false;
    }

    /**
     * 返回文件下載路徑
     *
     * @return
     */
    public String queryUrl() {
        String url = "";
        if (downloadId != -1) {
            DownloadManager.Query query = new DownloadManager.Query();
            query.setFilterById(downloadId);
            query.setFilterByStatus(DownloadManager.STATUS_SUCCESSFUL);
            Cursor cursor = downloadManager.query(query);
            if (cursor != null) {
                if (cursor.moveToFirst()) {
                    url = cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI));
                }
            }
            assert cursor != null;
            cursor.close();
        }
        return url;
    }

    /**
     * 取消下載
     */
    public void cancelDownload() {
        mContext.registerReceiver(receiver1, new IntentFilter(ACTION_NOTIFICATION_CLICKED));
    }

    /**
     * 取消廣播註冊
     */
    public void unRegister() {
        if (mReceiverTag) {
            mContext.unregisterReceiver(receiver);
        }
        mContext.unregisterReceiver(receiver1);
    }

    /**
     * 刪除PDF文件
     */
    private void removePdfFile() {
        DownloadManager.Query query = new DownloadManager.Query();
        query.setFilterByStatus(DownloadManager.STATUS_SUCCESSFUL);
        Cursor cursor = downloadManager.query(query);
        if (cursor != null) {
            while (cursor.moveToNext()) {
                if (cursor.getColumnIndex(DownloadManager.COLUMN_ID) != -1) {
                    downloadManager.remove(cursor.getLong(cursor.getColumnIndex(DownloadManager.COLUMN_ID)));
                }
            }
        }
        assert cursor != null;
        cursor.close();
    }
}

具體的集成方式可以去騰訊官網查看:https://x5.tencent.com/

首先,在Application中初始化X5瀏覽,雖說這是一個輕量級的不會造成ANR但是還是採用服務比較穩妥,具體如下:

寫一個服務類:

import android.app.IntentService;
import android.content.Intent;
import android.support.annotation.Nullable;

import com.tencent.smtt.sdk.QbSdk;

/**
 * 預加載X5內核瀏覽器
 */
public class X5CorePreLoadService extends IntentService{

    private static final String TAG = X5CorePreLoadService.class.getSimpleName();

    public X5CorePreLoadService() {
        super(TAG);
    }

    @Override
    protected void onHandleIntent(@Nullable Intent intent) {
        //在這裏添加我們要執行的代碼,Intent中可以保存我們所需的數據,
        //每一次通過Intent發送的命令將被順序執行
        initX5();
    }

    /**
     * 初始化X5內核
     */
    private void initX5() {
        if (!QbSdk.isTbsCoreInited()) {
            QbSdk.preInit(getApplicationContext(), null);// 設置X5初始化完成的回調接口
        }

        QbSdk.initX5Environment(getApplicationContext(), cb);
    }

    QbSdk.PreInitCallback cb = new QbSdk.PreInitCallback() {

        @Override
        public void onViewInitFinished(boolean arg0) {
            // TODO Auto-generated method stub
            //初始化完成回調
        }

        @Override
        public void onCoreInitFinished() {
            // TODO Auto-generated method stub
        }
    };
}

其次,在Application中初始化:

public class MyApplication extends Application {

    @Override
    public void onCreate() {
        super.onCreate();        
        preInitX5Core();
    }

    
    /**
     * 初始化X5內核
     */
    private void preInitX5Core() {
        //預加載x5內核
        Intent intent = new Intent(this, X5CorePreLoadService.class);
        startService(intent);
    }  
}

在Activity中顯示,這裏需要注意6.0權限,我用的EasyPermission,具體用法:https://github.com/googlesamples/easypermissions,TBS在Activity中使用

public class WebActivity extends AppCompatActivity implements TbsReaderView.ReaderCallback {


    private static final int PERMISSION_CODE3 = 1;
    private TbsReaderView mTbsReaderView;

    private FrameLayout mPdf;

    public static final String url = "";//PDF鏈接
    private DownloadPdfUtils downloadUtils;
    private String[] PERMISSION_ARRAYS3 = {Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE};

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_web);
        mPdf = findViewById(R.id.read_pdf);
        mTbsReaderView = new TbsReaderView(this, this);
        mPdf.addView(mTbsReaderView, new RelativeLayout.LayoutParams(-1, -1));
        downloadUtils = new DownloadPdfUtils(this);
        if (url != null) {
            checkPermission();
        }
    }

    @Subscribe(threadMode = ThreadMode.MAIN)
    public void onMessageEvent(EBPdfMsg event) {
        String urls = event.getUrl();
        displayFile(new File(urls).getName());
    }

    /**
     * 檢查是否有權限
     */
    @AfterPermissionGranted(PERMISSION_CODE3)
    private void checkPermission() {
        if (EasyPermissions.hasPermissions(this, PERMISSION_ARRAYS3)) {
            downloadUtils.downloadUrl(url, pdfName);
        } else {
            //Auto to do
        }
    }

    /**
     * PDF閱讀
     *
     * @param name
     */
    private void displayFile(String name) {
        Bundle bundle = new Bundle();
        bundle.putString("filePath", Environment.getExternalStorageDirectory().getAbsolutePath() + "/mysteel/pdf" + File.separator + name);
        bundle.putString("tempPath", Environment.getExternalStorageDirectory().getAbsolutePath() + "/mysteel/pdf");
        boolean result = mTbsReaderView.preOpen(getFileType(name), false);
        if (result) {
            mTbsReaderView.openFile(bundle);
        }
    }

    /**
     * 文件類型是否是.pdf
     *
     * @param paramString
     * @return
     */
    public static String getFileType(String paramString) {
        String str = "";

        if (TextUtils.isEmpty(paramString)) {
            return str;
        }
        int i = paramString.lastIndexOf('.');
        if (i <= -1) {
            return str;
        }
        try {
            str = paramString.substring(i + 1);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return str;
    }

    @Override
    public void onCallBackAction(Integer integer, Object o, Object o1) {
    }
}

activity的xml如下:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/read_pdf"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

4.利用PDFVIew進行加載,可能會使apk增大4M左右,其依賴地址:https://github.com/barteksc/AndroidPdfViewer

使用方式如下:

public class ReadPdfActivity extends AppCompatActivity  {


    private static final int PERMISSION_CODE3 = 1;
    private PDFView mPdf ;

    public static final String url = "";//PDF鏈接
    private DownloadPdfUtils downloadUtils;
    private String[] PERMISSION_ARRAYS3 = {Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE};

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_web);
        mPdf = findViewById(R.id.read_pdf);
        downloadUtils = new DownloadPdfUtils(this);
        if (url != null) {
            checkPermission();
        }
    }

    @Subscribe(threadMode = ThreadMode.MAIN)
    public void onMessageEvent(EBPdfMsg event) {
        String urls = event.getUrl();
        displayFile(new File(urls).getName());
    }

    /**
     * 檢查是否有權限
     */
    @AfterPermissionGranted(PERMISSION_CODE3)
    private void checkPermission() {
        if (EasyPermissions.hasPermissions(this, PERMISSION_ARRAYS3)) {
            downloadUtils.downloadUrl(url, pdfName);
        } else {
            //Auto to do
        }
    }

    /**
     * PDF閱讀
     *
     * @param name
     */
    private void displayFile(String name) {
        mReadPdf.fromFile(new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/mysteel/pdf" + File.separator + name))
                .swipeHorizontal(false)
                .enableDoubletap(true)
                .defaultPage(0)
                .onError(new OnErrorListener() {
                    @Override
                    public void onError(Throwable t) {
                        showEmpty();
                    }
                })
                .load();
    }

 
}

5.利用PDFJS,這種效果不是很好,感興趣的可以去官網自行查看:https://github.com/mozilla/pdf.js/

總結:

    這五種方式,體積最小的是騰訊的TBS,體積最大的PDFView,個人比較推崇使用PDFView,pdfjs這個實現方式有三種,服務器前後端配合這種我沒有使用,其他兩者種,在下拉滑動時,可能會出現空白頁,界面不太友好,但最終能顯示。當然刨去網絡問題還是Google docs最好,但是這種在國內無法使用。以上代碼中有誤的地方還望各位小夥伴指出。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章