1. 程式人生 > >Android文件閱讀之Office文件閱讀的方案實現

Android文件閱讀之Office文件閱讀的方案實現

Android文件閱讀(1)Office文件閱讀的方案實現

本文開始做一些檢視網路或者本地文件的功能,對於一些用到簡單開啟office文件應用,本文可能會適合,方案中會用到NoHTTP的下載功能,沒了解過的小夥伴們可以移步到NoHTTP的GitHub上了解一下:

其中我們會用到幾個下載監聽和下載佇列的類CallServer、HttpListener、HttpResponseListener

其中CallServer.java的程式碼為:

import android.app.Activity;

import com.yanzhenjie.nohttp.NoHttp;
import com.yanzhenjie.nohttp.download.DownloadListener;
import com.yanzhenjie.nohttp.download.DownloadQueue;
import com.yanzhenjie.nohttp.download.DownloadRequest;
import com.yanzhenjie.nohttp.rest.OnResponseListener;
import com.yanzhenjie.nohttp.rest.Request;
import com.yanzhenjie.nohttp.rest.RequestQueue;

/**
 * @Description:
 * @Encode: UTF-8
 * Created by zzj on 2018/4/4.
 */

public class CallServer {
    private final int MAX_DOWNLOAD_QUEUE = 10;
    private final int MAX_REQUEST_QUEUE = 10;

    private static CallServer sInstance;

    public static CallServer getInstance() {
        if (sInstance == null)
            synchronized (CallServer.class) {
                if (sInstance == null)
                    sInstance = new CallServer();
            }
        return sInstance;
    }

    private RequestQueue mRequestQueue;
    private DownloadQueue mDownloadQueue;


    private CallServer() {
        mRequestQueue = NoHttp.newRequestQueue(MAX_REQUEST_QUEUE);
        mDownloadQueue = NoHttp.newDownloadQueue(MAX_DOWNLOAD_QUEUE);
    }

    public <T> void request(int what, Request<T> request, OnResponseListener<T> listener) {
        mRequestQueue.add(what, request, listener);
    }

    public <T> void request(Activity activity, int what, Request<T> request, HttpListener<T> callback, boolean canCancel, boolean isLoading) {
        mRequestQueue.add(what, request, new HttpResponseListener<>(activity, request, callback, canCancel, isLoading));
    }

    public void download(int what, DownloadRequest request, DownloadListener listener) {
        mDownloadQueue.add(what, request, listener);
    }

    public void stop(DownloadRequest request){
        mDownloadQueue.stop();
    }

}

HttpListener.java的程式碼如下:

import com.yanzhenjie.nohttp.rest.Response;

/**
 * @Description:
 * @Encode: UTF-8
 * Created by zzj on 2018/4/4.
 */

public interface HttpListener<T> {

    void onSucceed(int what, Response<T> response);

    void onFailed(int what, Response<T> response);

}

HttpResponseListener.java程式碼如下:

import android.app.Activity;

import com.yanzhenjie.nohttp.rest.OnResponseListener;
import com.yanzhenjie.nohttp.rest.Request;
import com.yanzhenjie.nohttp.rest.Response;

/**
 * @Description:
 * @Encode: UTF-8
 * Created by zzj on 2018/4/4.
 */

public class HttpResponseListener<T> implements OnResponseListener<T> {

    private Activity mActivity;
    /**
     * Dialog.
     */
    /**
     * Request.
     */
    private Request<?> mRequest;
    /**
     * 結果回撥.
     */
    private HttpListener<T> callback;

    /**
     * @param activity     context用來例項化dialog.
     * @param request      請求物件.
     * @param httpCallback 回撥物件.
     * @param canCancel    是否允許使用者取消請求.
     * @param isLoading    是否顯示dialog.
     */
    public HttpResponseListener(Activity activity, Request<?> request, HttpListener<T> httpCallback, boolean canCancel, boolean isLoading) {
        this.mActivity = activity;
        this.mRequest = request;
        this.callback = httpCallback;
    }

    /**
     * 開始請求, 這裡顯示一個dialog.
     */
    @Override
    public void onStart(int what) {
    }

    /**
     * 結束請求, 這裡關閉dialog.
     */
    @Override
    public void onFinish(int what) {
    }

    /**
     * 成功回撥.
     */
    @Override
    public void onSucceed(int what, Response<T> response) {
        if (callback != null) {
            // 這裡判斷一下http響應碼,這個響應碼問下你們的服務端你們的狀態有幾種,一般是200成功。
            // w3c標準http響應碼:http://www.w3school.com.cn/tags/html_ref_httpmessages.asp

            callback.onSucceed(what, response);
        }
    }

    /**
     * 失敗回撥.
     */
    @Override
    public void onFailed(int what, Response<T> response) {
        if (callback != null)
            callback.onFailed(what, response);
    }

}

注意提醒一下,使用NoHTTP記得初始化一下,並且加上以下許可權:


    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

Office文件的閱讀

做Office文字的開啟,網上方案可能會有一大堆,有些需要自己手動去解析文件的各個節點,對於只是需要簡單的閱讀,可能不需要把工作量加到這麼大,我們可以變通一下。Android自動的WebView本身不支援解析Office文件,不像IOS提供的WebView,可以做到功能很強大,可以直接開啟網路文件,所以作為一名Android開發者,有時候會比較頭疼。本文采用的方案是首先採取調起本地閱讀器,如果檢測不到本地有閱讀器,則需要用到一個備選方案,採用微軟伺服器地址拼接網路文件地址來解析返回,然後直接丟進給WebView去展示。

先看下載指示layout_data_download.xml佈局檔案:

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

    <ImageView
        android:id="@+id/iv_data_cover"
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="100dp" />

    <TextView
        android:id="@+id/tv_file_name"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@+id/iv_data_cover"
        android:layout_marginTop="18dp"
        android:ellipsize="end"
        android:gravity="center"
        android:maxLines="2"
        android:paddingLeft="20dp"
        android:paddingRight="20dp"
        android:text="測試檔案.txt"
        android:textColor="#24272B"
        android:textSize="16sp" />

    <Button
        android:id="@+id/btn_download"
        android:layout_width="130dp"
        android:layout_height="34dp"
        android:layout_below="@+id/tv_file_name"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="50dp"
        android:text="下載"
        android:textSize="14sp"
        android:visibility="visible" />

    <TextView
        android:id="@+id/tv_download_progress"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@+id/tv_file_name"
        android:layout_marginTop="40dp"
        android:gravity="center"
        android:paddingLeft="20dp"
        android:paddingRight="20dp"
        android:text="下載中(6.1MB/13.7MB)"
        android:textColor="#B1B1B1"
        android:textSize="14sp"
        android:visibility="gone" />

    <ProgressBar
        android:id="@+id/pb_download"
        style="@style/progressBarDownload"
        android:layout_width="match_parent"
        android:layout_height="4dp"
        android:layout_below="@+id/tv_download_progress"
        android:layout_marginLeft="64dp"
        android:layout_marginRight="64dp"
        android:layout_marginTop="12dp"
        android:max="100"
        android:visibility="gone" />

</RelativeLayout>

style.xml progressBarDownload的程式碼如下:

    <style name="progressBarDownload" parent="android:Widget.ProgressBar.Horizontal">
        <item name="android:indeterminateOnly">false</item>
        <item name="android:progressDrawable">@drawable/progress_download</item>
        <item name="android:minHeight">4dp</item>
        <item name="android:maxHeight">4dp</item>
    </style>

其中drawable樣式progress_download如下:

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item
        android:id="@android:id/background"
        android:drawable="@drawable/shape_pb_download_bg" />
    <item android:id="@android:id/secondaryProgress">
        <scale
            android:drawable="@drawable/shape_pb_download_second"
            android:scaleWidth="100%" />
    </item>
    <item android:id="@android:id/progress">
        <scale
            android:drawable="@drawable/shape_pb_download_second"
            android:scaleWidth="100%" />
    </item>
</layer-list>

progress的背景shape_pb_download_bg.xml如下:

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
    <size
        android:width="250dp"
        android:height="4dp" />
    <solid android:color="#E8E8E8" />
    <corners android:radius="4dp" />
</shape>

progress的進度shape_pb_download_second.xml如下:

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
    <size
        android:width="250dp"
        android:height="4dp" />
    <solid android:color="#57E727" />
    <corners android:radius="4dp" />
</shape>

上面一大堆xml程式碼都是下載進度顯示,對於不需要提示,只要能下載下來的話,可能直接省略這些,然後在程式碼和佈局中直接不處理就好,小編只是讓下載進度更直觀,所以加上這些。

activity的佈局act_reader_office.xml如下,直接一個WebView和一個下載進度顯示的layout:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <WebView
        android:id="@+id/webView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:visibility="gone" />

    <include
        layout="@layout/layout_data_download"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_below="@+id/title"
        android:visibility="gone" />

</RelativeLayout>

好了,前期工作準備完了,我們看下activity的具體實現:


import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.text.format.Formatter;
import android.util.Log;
import android.view.View;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.RelativeLayout;
import android.widget.TextView;

import com.yanzhenjie.nohttp.Headers;
import com.yanzhenjie.nohttp.NoHttp;
import com.yanzhenjie.nohttp.download.DownloadListener;
import com.yanzhenjie.nohttp.download.DownloadRequest;

import java.io.File;

/**
 * @Description: office 文件閱讀頁面
 * @Author: zzj
 * @Date: 2018/9/6 16:15
 * @Version: 1.0.0
 */
public class OfficeReaderActivity extends Activity {
    //測試資料,微軟文件解析伺服器的測試url
    //DOC: http://view.officeapps.live.com/op/view.aspx?src=newteach.pbworks.com%2Ff%2Fele%2Bnewsletter.docx
    //EXCEL: http://view.officeapps.live.com/op/view.aspx?src=http%3A%2F%2Flearn.bankofamerica.com%2Fcontent%2Fexcel%2FWedding_Budget_Planner_Spreadsheet.xlsx
    //PPT: http://view.officeapps.live.com/op/view.aspx?src=http%3a%2f%2fvideo.ch9.ms%2fbuild%2f2011%2fslides%2fTOOL-532T_Sutter.pptx

    //微軟解析伺服器地址
    private static final String MICROSOFT_SERVER = "http://view.officeapps.live.com/op/view.aspx?src=";
    //下載地址
    private static final String ROOT_PATH = "/mnt/sdcard/office";
    private String url = "";
    private String title = "";
    private String fileSize;

    private DownloadRequest downloadRequest;

    private RelativeLayout rlDownloadContainer;
    private TextView tvDlFileName;
    private TextView tvDlProgress;
    private ProgressBar pbDownload;
    private Button btnDownload;
    private ImageView ivCover;


    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.act_reader_office);

        //url可以自己去構造,我這邊直接用測試URL
        url = "http://view.officeapps.live.com/op/view.aspx?src=newteach.pbworks.com%2Ff%2Fele%2Bnewsletter.docx";
        fileSize = Formatter.formatFileSize(this, getIntent().getIntExtra("fileSize", 0));
  
        downloadOffice(url, title);
    }


    /**
    * 當檢測不到本地有閱讀器時,初始化WebView,通過URL來解析顯示
    */
    private void initWebView() {
        WebView urlWebView = findViewById(R.id.webView);
        urlWebView.setVisibility(View.VISIBLE);
        urlWebView.setWebViewClient(new AppWebViewClients());
        urlWebView.getSettings().setJavaScriptEnabled(true);
        urlWebView.getSettings().setUseWideViewPort(true);
        urlWebView.loadUrl(MICROSOFT_SERVER + url);
    }

    private void downloadOffice(String url, String fileName) {
        File file = new File(ROOT_PATH + "/" + fileName);
        //先檢測本地是否已經有這個檔案,有的話直接開啟,沒的話就下載回來
        if (file.exists()) {
            setData(file.getPath());
            return;
        }
        
        showDownloadView(fileName);
    }

    private void showDownloadView(String fileName) {
        rlDownloadContainer = findViewById(R.id.rl_download_container);
        tvDlFileName = findViewById(R.id.tv_file_name);
        tvDlProgress = findViewById(R.id.tv_download_progress);
        btnDownload = findViewById(R.id.btn_download);
        pbDownload = findViewById(R.id.pb_download);
        ivCover = findViewById(R.id.iv_data_cover);
        tvDlFileName.setText(fileName);
        rlDownloadContainer.setVisibility(View.VISIBLE);
        //R.string.download為字元“下載”
        btnDownload.setText(String.format("%s(%s)", getString(R.string.download), fileSize));
        //點選下載按鈕,開啟下載請求,並加入到下載隊列當中
        btnDownload.setOnClickListener(v -> {
            if (downloadRequest == null) {
                downloadRequest = NoHttp.createDownloadRequest(url, ROOT_PATH , fileName, true, false);
                CallServer.getInstance().download(0, downloadRequest, downloadListener);
            }
        });
    }

    /**
    * WPS適配比較麻煩,需要按照官方需要的資訊傳,所以我們這邊也適配一下WPS,傳入相應的資料
    */
    private void setData(String path) {
        Intent intent = new Intent();
        Bundle bundle = new Bundle();
        bundle.putString(WpsModel.OPEN_MODE, WpsModel.OpenMode.READ_ONLY);
        //開啟模式
        bundle.putBoolean(WpsModel.SEND_SAVE_BROAD, true);
        //關閉時是否傳送廣播
        bundle.putString(WpsModel.THIRD_PACKAGE, getApplication().getPackageName());
        //第三方應用的包名,用於對改應用合法性的驗證
        bundle.putBoolean(WpsModel.CLEAR_TRACE, true);
        //清除開啟記錄
        //bundle.putBoolean(CLEAR_FILE, true);
        //關閉後刪除開啟檔案
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        intent.setAction(android.content.Intent.ACTION_VIEW);
        File file = new File(path);
        Uri uri = Uri.fromFile(file);
        intent.setDataAndType(uri, mimeType);
        intent.putExtras(bundle);
        //7.0以上需要許可權
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
        }
        try {
            startActivity(intent);
        } catch (ActivityNotFoundException e) {
            //找不到開啟的應用就用webView開啟
            initWebView();
        }
    }

    /**
    * 下載進度監聽
    */
    private final DownloadListener downloadListener = new DownloadListener() {
        @Override
        public void onDownloadError(int what, Exception exception) {
            if (rlDownloadContainer != null) {
                rlDownloadContainer.setVisibility(View.GONE);
            }
        }

        @Override
        public void onStart(int what, boolean isResume, long rangeSize, Headers responseHeaders, long allCount) {
        	//下載開始,隱藏相應的View和顯示進度條
            if (btnDownload != null) {
                btnDownload.setVisibility(View.GONE);
                tvDlProgress.setVisibility(View.VISIBLE);
                pbDownload.setVisibility(View.VISIBLE);
            }
        }

        @Override
        public void onProgress(int what, int progress, long fileCount, long speed) {
        	//更新下載進度
            pbDownload.setProgress(progress);
            tvDlProgress.setText(String.format("%s(%s/%s)", getResources().getString(R.string.downloading),
                    Formatter.formatFileSize(OfficeReaderActivity.this, fileCount), fileSize));
        }

        @Override
        public void onFinish(int what, String filePath) {
        	//下載完成
            setData(filePath);
            rlDownloadContainer.setVisibility(View.GONE);
        }

        @Override
        public void onCancel(int what) {
        }
    };

    @Override
    protected void onDestroy() {
        super.onDestroy();
        //退出時如果下載還沒完成,需要手動取消,避免記憶體洩漏和報錯
        if (downloadRequest != null) {
            downloadRequest.cancel();
        }
    }


    class AppWebViewClients extends WebViewClient {

        @Override
        public boolean shouldOverrideUrlLoading(WebView view, String url) {
            // TODO Auto-generated method stub
            view.loadUrl(url);
            return true;
        }

        @Override
        public void onPageFinished(WebView view, String url) {
            // TODO Auto-generated method stub
            super.onPageFinished(view, url);
            //編寫 javaScript方法
            Log.d("zzj", "onPageFinished() url = " + url);
        }
    }

    //適配WPS Android客戶端調起
    public class WpsModel {
        public static final String OPEN_MODE = "OpenMode";// 開啟檔案的模式。
        public static final String SEND_SAVE_BROAD = "SendSaveBroad";// 檔案儲存時是否傳送廣播。
        public static final String SEND_CLOSE_BROAD = "SendCloseBroad";// 檔案關閉時是否傳送廣播
        public static final String THIRD_PACKAGE = "ThirdPackage";// 第三方的包名,關閉的廣播會包含該項。
        public static final String CLEAR_BUFFER = "ClearBuffer";// 關閉檔案時是否請空臨時檔案。
        public static final String CLEAR_TRACE = "ClearTrace";// 關閉檔案時是否刪除使用記錄。
        public static final String CLEAR_FILE = "ClearFile";// 關閉檔案時是否刪除開啟的檔案。
        public static final String VIEW_PROGRESS = "ViewProgress";// 檔案上次檢視的進度。
        public static final String AUTO_JUMP = "AutoJump";// 是否自動跳轉到上次檢視的進度。
        public static final String SAVE_PATH = "SavePath";// 檔案儲存路徑。
        public static final String VIEW_SCALE = "ViewScale";// 檔案上次檢視的檢視的縮放。
        public static final String VIEW_SCALE_X = "ViewScrollX";// 檔案上次檢視的檢視的X座標。
        public static final String VIEW_SCALE_Y = "ViewScrollY";// 檔案上次檢視的檢視的Y座標。
        public static final String USER_NAME = "UserName";// 批註的作者。
        public static final String HOMEKEY_DOWN = "HomeKeyDown";// 監聽home鍵併發廣播
        public static final String BACKKEY_DOWN = "BackKeyDown";// 監聽back鍵併發廣播
        public static final String ENTER_REVISE_MODE = "EnterReviseMode";// 以修訂模式開啟文件
        public static final String CACHE_FILE_INVISIBLE = "CacheFileInvisible";// Wps生成的快取檔案外部是否可見

        public class OpenMode {
            public static final String NORMAL = "Normal";// 只讀模式
            public static final String READ_ONLY = "ReadOnly";// 正常模式
            public static final String READ_MODE = "ReadMode";// 開啟直接進入閱讀器模式
            // 僅Word、TXT文件支援
            public static final String SAVE_ONLY = "SaveOnly";// 儲存模式(開啟檔案,另存,關閉)
            // 僅Word、TXT文件支援
        }

        public class ClassName {
            public static final String NORMAL = "cn.wps.moffice.documentmanager.PreStartActivity2";
            // 普通版
            public static final String ENGLISH = "cn.wps.moffice.documentmanager.PreStartActivity2";
            // 英文版
            public static final String ENTERPRISE = "cn.wps.moffice.documentmanager.PreStartActivity2";
            // 企業版
        }

        public class PackageName {
            public static final String NORMAL = "cn.wps.moffice_eng";// 普通版
            public static final String ENGLISH = "cn.wps.moffice_eng";// 英文版
        }

        public class Reciver {
            public static final String ACTION_BACK = "com.kingsoft.writer.back.key.down";// 返回鍵廣播
            public static final String ACTION_HOME = "com.kingsoft.writer.home.key.down";// Home鍵廣播
            public static final String ACTION_SAVE = "cn.wps.moffice.file.save";// 儲存廣播
            public static final String ACTION_CLOSE = "cn.wps.moffice.file.close";// 關閉檔案廣播
        }
    }

}

整個流程大概可以總結為:

結語

本文篇幅可能會有點長,主要是先介紹到NoHTTP下載這一塊,這一塊在下載功能中非常簡單易用,剩下的就是office文件的開啟閱讀,寫的可能不太好,歡迎拍磚zhic指出錯誤哈~