Android文档阅读(1)Office文档阅读的方案实现
本文开始做一些查看网络或者本地文档的功能,对于一些用到简单打开office文档应用,本文可能会适合,方案中会用到NoHTTP的下载功能,没了解过的小伙伴们可以移步到NoHTTP的GitHub上了解一下:
NoHTTP文档:https://github.com/yanzhenjie/NoHttp
其中我们会用到几个下载监听和下载队列的类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文档的打开阅读,写的可能不太好,欢迎拍砖指出错误哈~