静态集成腾讯TBS文件浏览能力

众所周知,腾讯的X5内核除了支持webview浏览以外,还支持文件浏览,如果以前就用过的,就知道是这么调用的:

QbSdk.openFileReader(ct, file.getAbsolutePath(), null, null);

然而最近查看官方文档,不管是腾讯浏览服务还是腾讯接入文档都没有相关的资料,再仔细查,发现在常见问题列表当中有这么一个:

点击进去,咦咦咦,怎么内容没有了

可能因为商业调整原因吧。之前我看过里面的内容,大致意思是原来的TBS文件浏览能力不稳定,为了更好地提供定制需求,TBS团队专门剥离了原来的文件浏览能力并进行优化,生成一款新产品,然后我们咨询了一下,价格不菲,于是我们产品经理让我再想想办法。。。

一般地,如果没有特殊需求,要使用文件浏览服务只需要调用本文最开始的TBS接口就行了,但是,我们的产品要求不允许调用第三方APP打开,更不允许界面右上角能点击进入分享弹窗。我找了一圈发现市场上并没有其他专门做文件浏览的产品,于是又回到了TBS。它现在还是免费,但是依据官方文档操作确实有不稳定的问题,内核下发常常很慢,这是不能容忍的。我就想,能不能做成静态加载呢?

我的思路是,把TBS内核从APP的/data/data/包名/路径下复制出来,删掉多余的东西,放在assets里面,使用时再解压放回去。

开干!

 经过测试,发现32位手机的内核放在app_tbs文件夹里,64位的放在app_tbs_64文件夹里,删除掉多余的so库(位于/app_tbs/core_share或/app_tbs_64/core_share文件夹里):

 这是最占空间的,当然如果你的应用需要X5 webview浏览功能这个就千万不能删;还有其他看着没有绝对必要的库我也一并删除了。还有就是,并不是在这部手机下载的64位内核就能用于另一部手机,经过我反复测试,终于下载到了一版能够通用的(手头上5、6部手机都能正常运行)

将整个文件夹压缩,放入assets:

接下来就是代码实现:

class FileScanActivity : BaseActivity(), CoroutineScope by MainScope() {
    private var job: Job = Job()
    private var path = ""

    companion object {

        const val APP_TBS_ZIP = "tbs.zip"
        const val APP_TBS_64_ZIP = "tbs_64.zip"

        const val APP_TBS = "tbs"
        const val APP_TBS_64 = "tbs_64"

        @JvmStatic
        fun startMe(context: Context, path: String) {
            val intent = Intent(context, FileScanActivity::class.java)
            intent.putExtra("path", path)
            context.startActivity(intent)
        }
    }

    override fun initContentView() {
        window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE or WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN) // 设置默认键盘不弹出
        setContentView(R.layout.activity_file_scan)
        path = intent.getStringExtra("path")
        val name = File(path).name
        titleBar.setTitle(name)
    }

    override fun init() {
        var support64 = false
        val cpus = Build.SUPPORTED_64_BIT_ABIS
        if (cpus.isNotEmpty()) {
            //64位架构
            support64 = true
        }
        val tbsFile = getDir(if (support64) APP_TBS_64 else APP_TBS, Context.MODE_PRIVATE)
        if (tbsFile.exists() && tbsFile.listFiles().isNotEmpty()) {
            for (file in tbsFile.listFiles()) {
                if (file.isDirectory && file.name == "core_share" && file.listFiles().isNotEmpty()) {
                    loadFragment()
                    return
                }
            }
        }
        job = launch {
            ProgressDialog.show(context)
            val res = withContext(Dispatchers.IO) {
                try {
                    // 将文件从assets目录中复制出来
                    if (support64) {
                        val is2 = assets.open(APP_TBS_64_ZIP)
                        val output2 = openFileOutput(APP_TBS_64_ZIP, Context.MODE_PRIVATE)
                        output2.use {
                            is2.copyTo(output2)
                        }
                        //解压
                        FilePathUtils.unZipResourcePackage(File(filesDir.absolutePath + File.separator + APP_TBS_64_ZIP), getDir("", Context.MODE_PRIVATE).absolutePath)
                    } else {
                        val inputStream = assets.open(APP_TBS_ZIP)
                        val output = openFileOutput(APP_TBS_ZIP, Context.MODE_PRIVATE)
                        output.use {
                            inputStream.copyTo(output)
                        }
                        //解压
                        FilePathUtils.unZipResourcePackage(File(filesDir.absolutePath + File.separator + APP_TBS_ZIP), getDir("", Context.MODE_PRIVATE).absolutePath)
                    }
                    true
                } catch (e: IOException) {
                    e.printStackTrace()
                    false
                }
            }
            ProgressDialog.closed()
            if (res) {
                loadFragment()
            }
            Log.d("FileScanActivity", "copy res: $res")
        }
    }

    private fun loadFragment() {
        val fragment = TbsReaderFragment.newFragment(path)
        addFragment(fragment, R.id.layout)
    }

    override fun onDestroy() {
        super.onDestroy()
        job.cancel()
    }
}
class TbsReaderFragment : Fragment() {
    var mReaderView: TbsReaderView? = null
    var mReaderOpened = false
    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        val context = context
        mReaderView = TbsReaderView(context) { integer, o, o1 -> Log.d("TbsReaderFragment", "1: $integer, 2: $o, 3: $o1") }
        return mReaderView
    }

    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)
        arguments?.apply {
            val path = getString(INTENT_PATH)
            if (!TextUtils.isEmpty(path)) {
                open(path)
            }
        }
    }

    private fun open(path: String) {
        lifecycleScope.launch {
            val format = parseFormat(path)
            val preOpen = mReaderView!!.preOpen(format, false) // 该状态标志x5文件能力是否成功初始化并支持打开文件的格式
            if (preOpen) { // 使用x5内核打开office文件
                val b = SharedPrefsHelper.get(PubConstant.TBS_FILE_LOAD, false)
                if (b) {
                    delay(20)
                } else {
                    delay(1000)
                    SharedPrefsHelper.put(PubConstant.TBS_FILE_LOAD, true)
                }
                val bundle = Bundle()
                bundle.putString("filePath", path)
                bundle.putString("tempPath", FilePathUtils.getInstance().downFilePath)
                mReaderView?.openFile(bundle)
            } else {
                toast(R.string.file_no_support)
            }
        }
    }

    /**
     * 解析文件格式
     * @param fileName
     * @return
     */
    private fun parseFormat(fileName: String): String {
        return fileName.substring(fileName.lastIndexOf(".") + 1)
    }

    override fun onDestroy() {
        super.onDestroy()
        mReaderView?.onStop()
    }

    companion object {
        private const val TAG = "TbsReaderFragment"
        private const val INTENT_PATH = "INTENT_PATH"
        fun newFragment(path: String): TbsReaderFragment {
            val fragment = TbsReaderFragment()
            val argument = Bundle()
            argument.putString(INTENT_PATH, path)
            fragment.arguments = argument
            return fragment
        }
    }
}

心细的可能发现到了2个问题,

1、为啥要delay,因为内核首次初始化需要比较长的时间,再次使用就比较快了

2、为啥mReaderView?.onStop()要写在onDestroy()方法里面,因为如果按照字面意思写在onStop()里面,你会发现APP退回后台再返回前台,界面的内容被销毁了。。。

然后还有一个需要注意的点,使用TbsReaderView会发生内存泄漏,可能内部有个线程绑定了UI,好在使用多次也只会发生一次,就当作没看见了。

最后,可以把TBS的内核初始化代码注释掉了,再也不用傻乎乎的等着初始化回调是true还是false了:

//        QbSdk.setDownloadWithoutWifi(true);
//        QbSdk.PreInitCallback cb = new QbSdk.PreInitCallback() {
//
//            @Override
//            public void onViewInitFinished(boolean arg0) {
//                //x5內核初始化完成的回调,为true表示x5内核加载成功,否则表示x5内核加载失败,会自动切换到系统内核。
//                Log.d("QbSdk", " onViewInitFinished is " + arg0);
//            }
//
//            @Override
//            public void onCoreInitFinished() {
//                Log.d("QbSdk", "onCoreInitFinished");
//            }
//        };
//        //x5内核初始化接口
//        QbSdk.initX5Environment(getApplicationContext(),  cb);
//        // 接入文档地址:https://x5.tencent.com/docs/access.html
//        // 在调用TBS初始化、创建WebView之前进行如下配置
//        HashMap<String, Object> map = new HashMap<>();
//        map.put(TbsCoreSettings.TBS_SETTINGS_USE_SPEEDY_CLASSLOADER, true);
//        map.put(TbsCoreSettings.TBS_SETTINGS_USE_DEXLOADER_SERVICE, true);
//        QbSdk.initTbsSettings(map);

PS:写DEMO的时候发现,从正式项目拷贝过来的内核不能用,怀疑是某些配置文件引起的,终于让我发现是这个文件的问题:

打开是这样的:

  所以如果是直接拷贝的其他项目的内核,需要对该文件做处理(注意:直接在AS上修改该文件会失败)

    private fun changeConfig() {
        val tbsRootPath = getDir(APP_TBS_64, Context.MODE_PRIVATE).absolutePath
        //tbsRootPath: /data/user/0/包名/app_tbs_64
        val content = "core_packagename=${packageName}\n" +
                "app_version=1\n" +
                "core_disabled=false\n" +
                "core_version=46011\n" +
                "core_path=${tbsRootPath + File.separator}core_share"
        val file = File("${tbsRootPath + File.separator}share" + File.separator + "core_info")
        val fileWriter = FileWriter(file)
        fileWriter.use {
            it.write(content)
        }
    }

PPS:刚又发现,上周32位的手机使用这个方案还可以正常运行,突然就失灵了,凌乱了。。。目前采用的解决办法是判断如果是32位的,还是去下载内核

        String[] cpus = Build.SUPPORTED_64_BIT_ABIS;
        if (cpus == null || cpus.length <= 0) {
            //只有32位架构
            QbSdk.setDownloadWithoutWifi(true);
            QbSdk.setTbsListener(new TbsListener() {
                @Override
                public void onDownloadFinish(int progress) {
                    Log.d("QbSdk", "onDownloadFinish -->下载X5内核完成进度:" + progress);
                }

                @Override
                public void onInstallFinish(int progress) {
                    Log.d("QbSdk", "onInstallFinish -->安装X5内核进度:" + progress);
                }

                @Override
                public void onDownloadProgress(int progress) {
                    Log.d("QbSdk", "onDownloadProgress -->下载X5内核进度:" + progress);
                }
            });
            QbSdk.PreInitCallback cb = new QbSdk.PreInitCallback() {

                @Override
                public void onViewInitFinished(boolean arg0) {
                    //x5內核初始化完成的回调,为true表示x5内核加载成功,否则表示x5内核加载失败,会自动切换到系统内核。
                    Log.d("QbSdk", " onViewInitFinished is " + arg0);
                    if (!arg0) {
                        TbsDownloader.startDownload(AppContext.this);
                    }
                }

                @Override
                public void onCoreInitFinished() {
                    Log.d("QbSdk", "onCoreInitFinished");
                }
            };
            //x5内核初始化接口
            QbSdk.initX5Environment(getApplicationContext(),  cb);
            // 接入文档地址:https://x5.tencent.com/docs/access.html
            // 在调用TBS初始化、创建WebView之前进行如下配置
            HashMap<String, Object> map = new HashMap<>();
            map.put(TbsCoreSettings.TBS_SETTINGS_USE_SPEEDY_CLASSLOADER, true);
            map.put(TbsCoreSettings.TBS_SETTINGS_USE_DEXLOADER_SERVICE, true);
            QbSdk.initTbsSettings(map);
        }

还有一个问题:没加载插件之前,断掉手机网络,会提示加载插件失败。。。意思是还没有完全摆脱掉TBS的网络影响,排查了一番,暂时没有定位到原因。这有可能是个致命问题,将来一旦收费,前面的这些操作都是无用功。费了半天劲,只换来64位手机免下载内核。。。

附上github demo地址:(待完成)

疑虑:是否合法?

但是目前TBS肯定是在做商业化准备,有需求的小伙伴建议尽早做处理 

PS:腾讯文档SDK已经商业化了,以上方案作废

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值