美文网首页
Android 打开 office 文档的几种方式

Android 打开 office 文档的几种方式

作者: 隐姓埋名的猫大叔 | 来源:发表于2020-07-02 22:19 被阅读0次

    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
    效果图如下

    tbs内置浏览office.gif

    腾讯浏览服务官网地址
    现在引用下腾讯官方文档说明 与贴上它在Android浏览器的文件能力支持情况

    x5相关说明.png office_支持文档.png

    目前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地址

    相关文章

      网友评论

          本文标题:Android 打开 office 文档的几种方式

          本文链接:https://www.haomeiwen.com/subject/bxvpxktx.html