Android 在开发项目的过程中遇到了关于office 文件打开浏览的问题,在解决完后,在这里做个记录,也给各位未接触过的小伙伴们一点方向(会在文末附上该项目的git链接地址)。在查阅相关资料后,给出了几种方案:
方案一:用谷歌或微软的url链接格式拼接后,通过控件webView内置预览。
方案二:使用第三方手机软件WPS 打开本地文档。
方案三:使用国内腾讯 X5内核浏览控件 打开本地文档。
在自己亲自尝试后,方案一由于国内Android手机各种魔改和需要翻墙不可行,故介绍其它两种可行的方案与其缺陷。
结论先行:
方案二:通过第三方软件打开,若手机设备未安装WPS等能打开文档的相关软件则无法调用打开
方案三:能在自己APP内置打开,若手机设备未安装QQ或微信,则无法调用打开
方案二效果图如下:
wps打开录屏.gif
一般情况是由后台生成文件链接,我们下载保存到手机中再打开,由于没有服务器支持,我这里是将assets文件夹的test.docx文件保存到手机中去。再调用WPS打开文件
在这之前借鉴了大佬的博客:
https://blog.csdn.net/qq_31939617/article/details/83443440?utm_medium=distribute.pc_relevant.none-task-blog-baidujs-1
开始上代码:
由于我们要将文件写入到手机中,需要写入权限
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
读取目标文件可能会遇到7.0以上手机 FileUriExposedException ,这里动态申请权限和解决FileUri异常相信大多是博客写的很详细,我就不赘诉了
权限初始化判断授权,6.0以上动态申请
checkPermissions();
checkPermissions 方法
private void checkPermissions() {
//检查是否获得权限
if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
//没有获得授权,申请授权
if (ActivityCompat.shouldShowRequestPermissionRationale(MainActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
//弹窗解释为何需要该权限,再次请求权限
Toast.makeText(MainActivity.this, "请授权,否则无法存储test.docx 文档", Toast.LENGTH_LONG).show();
//跳转到应用设置界面
Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
Uri uri = Uri.fromParts("package", getPackageName(), null);
intent.setData(uri);
startActivity(intent);
} else {
//不需要解释为何需要授权直接请求授权
ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, MY_PERMISSIONS_REQUEST_CALL_PHONE);
}
} else {
//获得授权,将文件写入到
saveFileToPhone();
}
}
onRequestPermissionsResult 权限申请回调:
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
switch (requestCode) {
case MY_PERMISSIONS_REQUEST_CALL_PHONE: {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
//授权成功,写入文件
saveFileToPhone();
} else {
//授权失败
Toast.makeText(this, "授权失败!", Toast.LENGTH_LONG).show();
}
break;
}
}
}
动态申请权限成功之后直接调用 saveFileToPhone() 将 assets中的文件保存复制到手机中
private void saveFileToPhone() {
FileUtils.getInstance(getApplicationContext()).copyAssetsToSD("", "officeShow/office").setFileOperateCallback(new FileUtils.FileOperateCallback() {
@Override
public void onSuccess() {
Toast.makeText(MainActivity.this,"保存成功,可以打开啦",Toast.LENGTH_SHORT).show();
fileUrl= Environment.getExternalStorageDirectory()+"/officeShow/office/test.docx";
}
@Override
public void onFailed(String error) {
Toast.makeText(MainActivity.this,"保存失败:"+error,Toast.LENGTH_SHORT).show();
}
});
}
工具类代码如下:
public class FileUtils {
private static FileUtils instance;
private static final int SUCCESS = 1;
private static final int FAILED = 0;
private Context context;
private FileOperateCallback callback;
private volatile boolean isSuccess;
private String errorStr;
public static FileUtils getInstance(Context context) {
if (instance == null)
instance = new FileUtils(context);
return instance;
}
private FileUtils(Context context) {
this.context = context;
}
private Handler handler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
if (callback != null) {
if (msg.what == SUCCESS) {
callback.onSuccess();
}
if (msg.what == FAILED) {
callback.onFailed(msg.obj.toString());
}
}
}
};
public FileUtils copyAssetsToSD(final String srcPath, final String sdPath) {
new Thread(new Runnable() {
@Override
public void run() {
copyAssetsToDst(context, srcPath, sdPath);
if (isSuccess)
handler.obtainMessage(SUCCESS).sendToTarget();
else
handler.obtainMessage(FAILED, errorStr).sendToTarget();
}
}).start();
return this;
}
public void setFileOperateCallback(FileOperateCallback callback) {
this.callback = callback;
}
private void copyAssetsToDst(Context context, String srcPath, String dstPath) {
try {
String fileNames[] = context.getAssets().list(srcPath);
if (fileNames.length > 0) {
File file = new File(Environment.getExternalStorageDirectory(), dstPath);
if (!file.exists()) file.mkdirs();
for (String fileName : fileNames) {
if (!srcPath.equals("")) { // assets 文件夹下的目录
copyAssetsToDst(context, srcPath + File.separator + fileName, dstPath + File.separator + fileName);
} else { // assets 文件夹
copyAssetsToDst(context, fileName, dstPath + File.separator + fileName);
}
}
} else {
File outFile = new File(Environment.getExternalStorageDirectory(), dstPath);
InputStream is = context.getAssets().open(srcPath);
FileOutputStream fos = new FileOutputStream(outFile);
byte[] buffer = new byte[1024];
int byteCount;
while ((byteCount = is.read(buffer)) != -1) {
fos.write(buffer, 0, byteCount);
}
fos.flush();
is.close();
fos.close();
}
isSuccess = true;
} catch (Exception e) {
e.printStackTrace();
errorStr = e.getMessage();
isSuccess = false;
}
}
public interface FileOperateCallback {
void onSuccess();
void onFailed(String error);
}
}
保存成功之后,通过点击按钮启动意图打开手机中的第三方软件:WPS,打开之前先通过isInstall函数判断是否已安装
private boolean isInstall(Context context, String packageName) {
final PackageManager packageManager = context.getPackageManager();
// 获取所有已安装程序的包信息
List<PackageInfo> pinfo = packageManager.getInstalledPackages(0);
for (int i = 0; i < pinfo.size(); i++) {
if (pinfo.get(i).packageName.equalsIgnoreCase(packageName))
return true;
}
return false;
}
@Override
public void onClick(View view) {
switch (view.getId()){
case R.id.btn_openWithWPS:
if (!isInstall(this, "cn.wps.moffice_eng")) {
Toast.makeText(this,"请下载安装WPS",Toast.LENGTH_SHORT).show();
return;
}
startActivity(getWordFileIntent(fileUrl));
break;
}
}
若已经安装,则通过getWordFileIntent(fileUrl) 启动意图
private Intent getWordFileIntent(String Path) {
File file = new File(Path);
Intent intent = new Intent("android.intent.action.VIEW");
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
Uri uri;
//Uri uri = Uri.fromFile(file);//解决FileUriExposedException
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
uri = FileProvider.getUriForFile(getApplicationContext(),
getPackageName() + ".fileprovider", new File(String.valueOf(file)));
} else {
uri = Uri.fromFile(new File(String.valueOf(file)));
}
String type = getMIMEType(file);
if (type.contains("pdf") || type.contains("vnd.ms-powerpoint") || type.contains("vnd.ms-word") || type.contains("vnd.ms-excel") || type.contains("text/plain") || type.contains("text/html")) {
if (isInstall(this, "cn.wps.moffice_eng")) {
intent.setClassName("cn.wps.moffice_eng",
"cn.wps.moffice.documentmanager.PreStartActivity2");
intent.setData(uri);
} else {
intent.addCategory("android.intent.category.DEFAULT");
intent.setDataAndType(uri, type);
}
} else {
intent.addCategory("android.intent.category.DEFAULT");
intent.setDataAndType(uri, type);
}
return intent;
}
/**
* 判断文件类型
*/
private static String getMIMEType(File f) {
String type = "";
String fName = f.getName();
/* 取得扩展名 */
String end = fName.substring(fName.lastIndexOf(".") + 1, fName.length()).toLowerCase();
/* 依扩展名的类型决定MimeType */
if (end.equals("pdf")) {
type = "application/pdf";
} else if (end.equals("m4a") || end.equals("mp3") || end.equals("mid") ||
end.equals("xmf") || end.equals("ogg") || end.equals("wav")) {
type = "audio/*";
} else if (end.equals("3gp") || end.equals("mp4")) {
type = "video/*";
} else if (end.equals("jpg") || end.equals("gif") || end.equals("png") ||
end.equals("jpeg") || end.equals("bmp")) {
type = "image/*";
} else if (end.equals("apk")) {
type = "application/vnd.android.package-archive";
} else if (end.equals("pptx") || end.equals("ppt")) {
type = "application/vnd.ms-powerpoint";
} else if (end.equals("docx") || end.equals("doc")) {
type = "application/vnd.ms-word";
} else if (end.equals("xlsx") || end.equals("xls")) {
type = "application/vnd.ms-excel";
} else if (end.equals("txt")) {
type = "text/plain";
} else if (end.equals("html") || end.equals("htm")) {
type = "text/html";
} else {
//如果无法直接打开,就跳出软件列表给用户选择
type = "*/*";
}
return type;
}
至此,用手机第三方打开WPS已经实现了
接下来实现方案三的功能,参考大佬博客:https://blog.csdn.net/Andy_l1/article/details/78218078?locationNum=5&fps=1
效果图如下
腾讯浏览服务官网地址
现在引用下腾讯官方文档说明 与贴上它在Android浏览器的文件能力支持情况
目前TSB还不支持在线预览功能,只支持本地文件打开。现在将相关的库依赖到开发项目app目录下的gradle中
api 'com.tencent.tbs.tbssdk:sdk:43903'
添加网络权限和网络监视管理权限
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
初始化QBSDK
private void initQBSDK() {
QbSdk.initX5Environment(this, new QbSdk.PreInitCallback() {
@Override
public void onCoreInitFinished() {
Log.e("QbSdk", "QbSdk onCoreInitFinished");
}
@Override
public void onViewInitFinished(boolean b) {
ifInitSuccess = b;
Log.e("QbSdk", "QbSdk 初始化是否成功:" + b);
}
});
QbSdk.setDownloadWithoutWifi(true);//设置支持非Wifi下载
}
在docx文件成功写入手机和QbSdk初始化成功后,跳转到新的页面展示
case R.id.btn_openWithTbs:
if(!ifInitSuccess){
Toast.makeText(this, "初始化失败,请查看原因", Toast.LENGTH_SHORT).show();
return;
}
startActivity(new Intent(this,TbsX5ReadOfficeActivity.class)
.putExtra("fileUrl",fileUrl)
);
break;
现在来看看加载office的TbsX5ReadOfficeActivity,贴上整个类的代码:
public class TbsX5ReadOfficeActivity extends AppCompatActivity implements TbsReaderView.ReaderCallback {
RelativeLayout rootRl;
private TbsReaderView tbsReaderView;
private String fileUrl;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_tbs_x5_read_office);
fileUrl = getIntent().getStringExtra("fileUrl");
initTbs();
}
private void initTbs() {
rootRl = findViewById(R.id.rootRl);
tbsReaderView = new TbsReaderView(this, this);
rootRl.addView(tbsReaderView, new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT,
RelativeLayout.LayoutParams.MATCH_PARENT));
Bundle bundle = new Bundle();
bundle.putString("filePath", fileUrl);
//加载插件保存的路径
bundle.putString("tempPath", Environment.getExternalStorageDirectory() + File.separator + "temp");
boolean b = tbsReaderView.preOpen("docx", false);
if (b) {
tbsReaderView.openFile(bundle);
}
}
@Override
public void onCallBackAction(Integer integer, Object o, Object o1) {
}
}
大概流程就是通过上个界面传递过来的文件路径,在tbs初始化后将TbsReaderView 添加到RelativeLayout rootRl中做其子View,通过bundle把文件传给x5,打开的事情交由x5处理,tbsReaderView.preOpen("docx", false) 的第一个参数是目标文件格式,启动相应的格式插件去打开。
至此两个方案的实现均以给出,希望能给小伙伴们借鉴思路,如有讲解错误,希望能不吝惜指正。
这是我项目的github地址
网友评论