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指出錯誤哈~