Android问题笔记九:关于腾讯文档TBS离线的研究

专栏分享

👉关于作者

众所周知,人生是一个漫长的流程,不断克服困难,不断反思前进的过程。在这个过程中会产生很多对于人生的质疑和思考,于是我决定将自己的思考,经验和故事全部分享出来,以此寻找共鸣 !!!
专注于Android/Unity和各种游戏开发技巧,以及各种资源分享(网站、工具、素材、源码、游戏等)
有什么需要欢迎私我,交流群让学习不再孤单

在这里插入图片描述

👉实践过程

从2023年4月开始腾讯的TBS文档进入付费和在线时代,原来的免费形式已经成为过去式,付费无可厚非,但是很容易遇到不愿意掏钱的公司,而你又在这个公司工作。所以离线文档浏览的业务还是有小众场景的。
而且即使是免费在线的他也有流控,就是当你应用有一定用户量之后,会有一定概率从中抽取必然有小部分用户让他无法下载内核,刺激付费的手段而已。除此之外,一定时间内失败次数多了后续一定时间必然失败,就好比你手机开锁忘记密码类似,还有官网维护时间,你也不能下载,等等原因。
这篇文章主要讲正常情况下离线模式的使用,搞清楚原因,下一篇文章我们将如何彻底的离线,可以以后任何项目随便用。
从2022年不知道哪个TBS版本更新,就已经开始内置付费功能以及去除了一些本地方法。所以我们使用的前提是尽量靠前点版本,比如2021年-2022年5月份的插件和功能。

最下方有完整代码。

😜准备内容

  1. 我们都知道在线模式集成需要下载tbs文件,大概40mb左右,所以我们就需要准备好这个,光这个我相信就难道了大部分人,因为很少有人去专门的备份这个,只能去网上搜看看谁保存过。也可以找我,我备份了7个版本。拿到这个后将后缀改为apk。
  2. 接着就需要准备jar包了。这个很好准备,相信很多人都有,例
    tbs_sdk_thirdapp_v4.3.0.253_44153_sharewithdownloadwithfile_withoutGame_obfs_20220117_105333.jar

😜开始集成

先将jar放到libs下进行依赖,然后在Manifest中添加权限。
CSDN-芝麻粒儿

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
    tools:ignore="ProtectedPermissions" />
<!--SDCard中创建与删除文件权限 -->
<uses-permission
    android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"
tools:ignore="ProtectedPermissions" />

之后打开我们的Activity,添加代码将我们改过tbs后缀的apk用代码赋值到指定目录,记得是指定目录,刚开始我也以随意目录都可以,但是测试了多款机型发现并非如此。只有在这个目录jar包的静态安装代码才能保证100%正确识别到。规则是:
/storage/emulated/0/Android/data/你的应用包名/下。
注:记得动态权限申请,我写Demo嫌麻烦直接从手机设置里自己打开的。

拷贝完成后要进行installLocalTbsCore代码安装,

FileUtils.copyAssets(getApplicationContext(), "046007_x5.tbs.apk", FileUtils.getTBSFileDir(getApplicationContext()).getPath() + "/046007_x5.tbs.apk");
if (!QbSdk.canLoadX5(getApplicationContext())) {
    QbSdk.reset(getApplicationContext());
    QbSdk.installLocalTbsCore(getApplicationContext(), 46007, FileUtils.getTBSFileDir(getApplicationContext()).getPath() + "/046007_x5.tbs.apk");
}
HashMap<String, Object> map = new HashMap<>(2);
map.put(TbsCoreSettings.TBS_SETTINGS_USE_SPEEDY_CLASSLOADER, true);
map.put(TbsCoreSettings.TBS_SETTINGS_USE_DEXLOADER_SERVICE, true);
QbSdk.initTbsSettings(map);
QbSdk.setTbsListener(new TbsListener() {
    @Override
    public void onDownloadFinish(int i) {
        Log.e("TAG", "进行了tbs:onDownloadFinish " + i);
    }

    @Override
    public void onInstallFinish(int i) {
        Log.e("TAG", "进行了tbs:onInstallFinish " + i);
    }

    @Override
    public void onDownloadProgress(int i) {
        Log.e("TAG", "进行了tbs:onDownloadProgress " + i);
    }
});

记得调用下reset,删除所有相关的tbs内容,确保必然成功。
这样基本就可以安装完毕,记得调用安装代码延迟一点时间,因为拷贝资源需要一两秒时间。
安装完毕后你可以在设备大data/data/包名目录下看到增加了很多tbs的内容,利用Android Studio的Device File Explorer可以看到。
大家以为安装完这个打开文件就行了?
并不是,又被坑了一把,原来在线集成都是随意传递个路径就可以,但是这种离线的想要打开文档还得放到指定目录下,这点要格外注意。

打开文件需要你将文件拷贝的指定目录
FileUtils.copyAssets(getApplicationContext(), “config”, FileUtils.getTBSFileDir(getApplicationContext()).getPath());

之后打开文件的代码也需要是这个目录才可以。详情看附录全部代码。
问题又来了,你仔细观察打开的瞬间,会发现竟然还在下载东西,没错那是对应文档类型的真正解析渲染插件。那这也算不上离线啊,不要急我们下篇文章见分晓。彻底意义上的离线。

public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    private Button idDebugBtnFileDocx, idDebugBtnFileDoc, idDebugBtnFileXlsx, idDebugBtnFileXls, idDebugBtnFilePdf, idDebugBtnFilePptx,
            idDebugBtnFilePpt, idDebugBtnFileTxt, idDebugBtnFileEpub, idDebugBtnFilePng;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        idDebugBtnFileDocx = findViewById(R.id.idDebugBtnFileDocx);
        idDebugBtnFileDocx.setOnClickListener(this);
        idDebugBtnFileDoc = findViewById(R.id.idDebugBtnFileDoc);
        idDebugBtnFileDoc.setOnClickListener(this);
        idDebugBtnFileXlsx = findViewById(R.id.idDebugBtnFileXlsx);
        idDebugBtnFileXlsx.setOnClickListener(this);
        idDebugBtnFileXls = findViewById(R.id.idDebugBtnFileXls);
        idDebugBtnFileXls.setOnClickListener(this);
        idDebugBtnFilePdf = findViewById(R.id.idDebugBtnFilePdf);
        idDebugBtnFilePdf.setOnClickListener(this);
        idDebugBtnFilePptx = findViewById(R.id.idDebugBtnFilePptx);
        idDebugBtnFilePptx.setOnClickListener(this);
        idDebugBtnFilePpt = findViewById(R.id.idDebugBtnFilePpt);
        idDebugBtnFilePpt.setOnClickListener(this);
        idDebugBtnFileTxt = findViewById(R.id.idDebugBtnFileTxt);
        idDebugBtnFileTxt.setOnClickListener(this);
        idDebugBtnFileEpub = findViewById(R.id.idDebugBtnFileEpub);
        idDebugBtnFileEpub.setOnClickListener(this);
        idDebugBtnFilePng = findViewById(R.id.idDebugBtnFilePng);
        idDebugBtnFilePng.setOnClickListener(this);
        FileUtils.copyAssets(getApplicationContext(), "046007_x5.tbs.apk", FileUtils.getTBSFileDir(getApplicationContext()).getPath() + "/046007_x5.tbs.apk");
        FileUtils.copyAssets(getApplicationContext(), "config", FileUtils.getTBSFileDir(getApplicationContext()).getPath());
    }

    @Override
    protected void onResume() {
        super.onResume();
        Log.e("TAG", "安装流程: " + QbSdk.canLoadX5(getApplicationContext()) + "==" + getFileIsExists("046007_x5.tbs.apk"));
        if (!QbSdk.canLoadX5(getApplicationContext())) {
            QbSdk.reset(getApplicationContext());
            QbSdk.installLocalTbsCore(getApplicationContext(), 46007, FileUtils.getTBSFileDir(getApplicationContext()).getPath() + "/046007_x5.tbs.apk");
        }
        HashMap<String, Object> map = new HashMap<>(2);
        map.put(TbsCoreSettings.TBS_SETTINGS_USE_SPEEDY_CLASSLOADER, true);
        map.put(TbsCoreSettings.TBS_SETTINGS_USE_DEXLOADER_SERVICE, true);
        QbSdk.initTbsSettings(map);
        QbSdk.setTbsListener(new TbsListener() {
            @Override
            public void onDownloadFinish(int i) {
                Log.e("TAG", "进行了tbs:onDownloadFinish " + i);
            }

            @Override
            public void onInstallFinish(int i) {
                Log.e("TAG", "进行了tbs:onInstallFinish " + i);
            }

            @Override
            public void onDownloadProgress(int i) {
                Log.e("TAG", "进行了tbs:onDownloadProgress " + i);
            }
        });
    }

    public static boolean getFileIsExists(String fileName) {
        File file = new File(Environment.getExternalStorageDirectory(), fileName);
        // 如果文件不存在
        if (file.exists()) {
            return true;
        }
        return false;
    }

    @Override
    public void onClick(View view) {
        switch (view.getId()) {
            case R.id.idDebugBtnFileDocx:
                FileLookActivity.path = FileUtils.getTBSFileDir(getApplicationContext()).getPath() + "/doc插件.docx";
                startActivity(new Intent(this, FileLookActivity.class));
                break;
            case R.id.idDebugBtnFileDoc:
                FileLookActivity.path = FileUtils.getTBSFileDir(getApplicationContext()).getPath() + "/doc插件 - 副本.doc";
                startActivity(new Intent(this, FileLookActivity.class));
                break;
            case R.id.idDebugBtnFileXlsx:
                FileLookActivity.path = FileUtils.getTBSFileDir(getApplicationContext()).getPath() + "/xls插件.xlsx";
                startActivity(new Intent(this, FileLookActivity.class));
                break;
            case R.id.idDebugBtnFileXls:
                FileLookActivity.path = FileUtils.getTBSFileDir(getApplicationContext()).getPath() + "/xls插件 - 副本.xls";
                startActivity(new Intent(this, FileLookActivity.class));
                break;
            case R.id.idDebugBtnFilePdf:
                FileLookActivity.path = FileUtils.getTBSFileDir(getApplicationContext()).getPath() + "/pdf插件.pdf";
                startActivity(new Intent(this, FileLookActivity.class));
                break;
            case R.id.idDebugBtnFilePptx:
                FileLookActivity.path = FileUtils.getTBSFileDir(getApplicationContext()).getPath() + "/ppt插件.pptx";
                startActivity(new Intent(this, FileLookActivity.class));
                break;
            case R.id.idDebugBtnFilePpt:
                FileLookActivity.path = FileUtils.getTBSFileDir(getApplicationContext()).getPath() + "/ppt插件 - 副本.ppt";
                startActivity(new Intent(this, FileLookActivity.class));
                break;
            case R.id.idDebugBtnFileTxt:
                FileLookActivity.path = FileUtils.getTBSFileDir(getApplicationContext()).getPath() + "/txt插件.txt";
                startActivity(new Intent(this, FileLookActivity.class));
                break;
            case R.id.idDebugBtnFileEpub:
                FileLookActivity.path = FileUtils.getTBSFileDir(getApplicationContext()).getPath() + "/猫哥聊.epub";
                startActivity(new Intent(this, FileLookActivity.class));
                break;
            case R.id.idDebugBtnFilePng:
                FileLookActivity.path = FileUtils.getTBSFileDir(getApplicationContext()).getPath() + "/微信-空名先生_2023-03-27_16-07-53.png";
                startActivity(new Intent(this, FileLookActivity.class));
                break;
            default:
                break;
        }
    }
}


public class FileUtils {

    /**
     * 保存文件预览的目录
     *
     * @param context 上下文对象
     */
    public static File getTBSFileDir(Context context) {
        String dirName = "TBSFile";
        return context.getExternalFilesDir(dirName);
    }

    /**
     * 把asset的文件转化为本地文件
     *
     * @param context 上下文对象
     * @param oldPath 旧的文件路径
     * @param newPath 新的文件路径
     */
    public static boolean copyAssets(Context context, String oldPath, String newPath) {
        try {
            String fileNames[] = context.getAssets().list(oldPath);// 获取assets目录下的所有文件及目录名
            if (fileNames.length > 0) {// 如果是目录
                File file = new File(newPath);
                file.mkdirs();// 如果文件夹不存在,则递归
                for (String fileName : fileNames) {
                    Log.e("TAG", "正在进行拷贝: "+fileName);
                    copyAssets(context, oldPath + "/" + fileName, newPath + "/" + fileName);
                }
            } else {// 如果是文件
                InputStream is = context.getAssets().open(oldPath);
                FileOutputStream fos = new FileOutputStream(new File(newPath));
                byte[] buffer = new byte[1024];
                int byteCount;
                while ((byteCount = is.read(buffer)) != -1) {// 循环从输入流读取
                    // buffer字节
                    fos.write(buffer, 0, byteCount);// 将读取的输入流写入到输出流
                }
                fos.flush();// 刷新缓冲区
                is.close();
                fos.close();
            }
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }

    /**
     * 获取url文件后缀
     *
     * @param url 文件链接
     */
    public static String getSuffix(String url) {
        if ((url != null) && (url.length() > 0)) {
            int dot = url.lastIndexOf('.');
            if ((dot > -1) && (dot < (url.length() - 1))) {
                return url.substring(dot + 1);
            }
        }
        return "";
    }
}


public class FileLookActivity extends FragmentActivity {
    public static String path = "";
    private TbsReaderView mTbsReaderView;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.aty_filelook);
        RelativeLayout mFlRoot = findViewById(R.id.fl_container);
        Log.e("TAG", ":文件地址"+path+MainActivity.getFileIsExists("猫哥聊.epub"));
        String bsReaderTemp = FileUtils.getTBSFileDir(FileLookActivity.this) + "/TbsReaderTemp";
        File bsReaderTempFile = new File(bsReaderTemp);
        if (!bsReaderTempFile.exists()) {
            Log.e("TAG", "准备创建/storage/emulated/0/TbsReaderTemp!!");
            boolean mkdir = bsReaderTempFile.mkdir();
            if (!mkdir) {
                Log.e("TAG", "创建/storage/emulated/0/TbsReaderTemp失败!!!!!");
            }
        }
        TbsReaderView.ReaderCallback readerCallback = new TbsReaderView.ReaderCallback() {
            @Override
            public void onCallBackAction(Integer integer, Object o, Object o1) {

            }
        };
        //回调结果参考 TbsReaderView.ReaderCallback
        //2、创建TbsReaderView
        mTbsReaderView = new TbsReaderView(this, readerCallback);
        //3、将TbsReaderView 添加到RootLayout中(可添加到自定义标题栏的下方)
        mFlRoot.addView(
                mTbsReaderView,
                new RelativeLayout.LayoutParams(
                        RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT
                )
        );
        //4、传入指定参数
        Bundle bundle =new Bundle();
        bundle.putString(TbsReaderView.KEY_FILE_PATH, path);
        bundle.putString(TbsReaderView.KEY_TEMP_PATH, FileUtils.getTBSFileDir(this) + "/" + "TbsReaderTemp");
        boolean result = mTbsReaderView.preOpen(getFileTypeDot(path), false);
        if (result) {
            //6、调用openFile打开文件
            mTbsReaderView.openFile(bundle);
        } else {
            Log.e("TAG", ":加载失败,联网重启应用进行下载 ");
        }
    }

    public static String getFileTypeDot(String filename) {
        if ((filename != null) && (filename.length() > 0)) {
            int dot = filename.lastIndexOf('.');
            if ((dot > -1) && (dot < (filename.length() - 1))) {
                return filename.substring(dot + 1);
            }
        }
        return null;
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (mTbsReaderView != null) {
            mTbsReaderView.onStop();
        }
    }
}

👉其他

📢作者:小空和小芝中的小空
📢转载说明-务必注明来源:https://zhima.blog.csdn.net/
📢这位道友请留步☁️,我观你气度不凡,谈吐间隐隐有王者霸气💚,日后定有一番大作为📝!!!旁边有点赞👍收藏🌟今日传你,点了吧,未来你成功☀️,我分文不取,若不成功⚡️,也好回来找我。

温馨提示点击下方卡片获取更多意想不到的资源。
空名先生

  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 8
    评论
Android 中,我们可以使用 `TranslateAnimation` 类来实现 View 的平移动画效果。以下是从左向右平移的示例代码: ```java TranslateAnimation animation = new TranslateAnimation(0, 200, 0, 0); animation.setDuration(1000); // 动画持续时间,单位为毫秒 animation.setFillAfter(true); // 动画结束后保持状态 view.startAnimation(animation); // 开始动画 ``` 上述代码中,`TranslateAnimation` 的两个参数分别表示 View 在 X 轴和 Y 轴上的起点和终点位置。这里我们将 X 轴的起点设置为 0,终点设置为 200,表示 View 从左向右平移 200 个像素。 如果想要实现循环播放的效果,可以使用 `AnimationSet` 类来组合多个动画,示例代码如下: ```java TranslateAnimation animation1 = new TranslateAnimation(0, 200, 0, 0); animation1.setDuration(1000); animation1.setInterpolator(new LinearInterpolator()); // 线性插值器,使动画匀速播放 TranslateAnimation animation2 = new TranslateAnimation(200, 0, 0, 0); animation2.setDuration(1000); animation2.setStartOffset(1000); // 设置动画延迟开始的时间,单位为毫秒 animation2.setInterpolator(new LinearInterpolator()); AnimationSet animationSet = new AnimationSet(true); animationSet.addAnimation(animation1); animationSet.addAnimation(animation2); animationSet.setRepeatCount(Animation.INFINITE); // 设置动画重复播放的次数 view.startAnimation(animationSet); ``` 上述代码中,我们创建了两个 `TranslateAnimation`,分别表示 View 从左向右平移和从右向左平移。然后使用 `AnimationSet` 将两个动画组合在一起,并设置为循环播放,效果如下: ![translate_animation.gif](https://img-blog.csdn.net/20180420202704371?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvbGFuZ3VhZ2VfYmFpZHU=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/q/75|watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvbGFuZ3VhZ2VfYmFpZHU=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/q/75) 如果想要实现手势控制平移的效果,可以使用 `ViewPropertyAnimator` 类来实现,示例代码如下: ```java view.animate().translationX(200); // 平移 View 到 X 轴 200 像素的位置 ``` 上述代码中,我们使用 `animate()` 方法获取 `ViewPropertyAnimator` 对象,然后调用 `translationX()` 方法实现 View 的平移动画。注意,这种方式只能实现单次平移动画,不能循环播放。 除了平移动画,Android 中还有旋转和缩放动画,可以使用 `RotateAnimation` 和 `ScaleAnimation` 类来实现。具体用法与 `TranslateAnimation` 类似。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

芝麻粒儿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值