有奖:语音产品征文挑战赛火热进行中> HOT

SDK 介绍

腾讯浏览服务已支持以下基础文档格式的本地打开,接入 SDK 可支持打开文档格式:doc、docx、rtf、ppt、pptx、xls、xlsx、xlsm、csv、pdf、txt、epub、dwg、chm。
其他兼容了 office 格式、遵循 office 标准的文件,可以尝试把文件后缀名转成以上格式,再调用 SDK 打开。

SDK 接入说明

隐私政策

请开发者及终端用户认真阅读上述规则。如您是开发者,请您确认充分了解并同意本规则后再集成 SDK 产品,如果您不同意上述规则及按照本规则履行对应的用户个人信息保护义务,应立即停止接入及使用 SDK 产品。
为了保证腾讯浏览服务 SDK 的正常使用,SDK 需要在 AndroidManifest.xml 声明了如下权限:
权限类型
权限使用说明
android.permission.INTERNET
文档能力使用前的授权鉴权功能
(离线版本忽略此项)
android.permission.ACCESS_NETWORK_STATE
在 SDK 的功能使用期间,SDK 需要获取以下信息数据以支持 SDK 基础功能的正常运行。
信息类型
目的
时机
处理方式
获取设备信息(操作系统版本、CPU 类型、屏幕宽高、屏幕方向、屏幕像素)
加载文档引擎必要参数
SDK 初始化获取
内存内使用,退出程序即销毁
应用信息(宿主应用包名,版本号)
加载文档引擎必要参数
SDK 初始化获取
通过标识化、加密传输和处理的安全处理方式
为实现 SDK 的基础功能,SDK 打开文档可能涉及使用到以下权限,但 SDK 不会主动申请权限,也不会判断应用是否有权限,在需要使用功能的同时,宿主根据业务的判断自己决定是否授予,权限的控制由宿主自主决定。下表列举了各个权限使用涉及的功能及影响。
操作系统
权限名称
使用目的及功能场景(申请时机)
是否必选
Android
网络权限
通过 SDK 打开文档时,需要网络保持连接,支持授权鉴权能力。开发者在调用需要该权限的 SDK 功能时进行调用。
必选
存储权限
若接入方将文档存储在非 App 私有目录下(例如 SD 卡公共目录),则需要申请存储权限,SDK 才能访问并打开文档;若接入方将文档存储在 App 私有目录下,则无需申请存储权限。开发者在调用需要该权限的 SDK 功能时进行调用。
可选
剪切板权限
用户主动操作文档内容复制粘贴,需要访问剪切板。开发者在调用需要该权限的 SDK 功能时进行调用。
可选
注意:
请务必确保用户同意隐私政策后,再调用 SDK iniEngine 接口进行初始化。

SDK 接入流程

1. 下载官网 SDK(按照需要选择一种),集成到项目中。例如:在工程根目录下创建 libs 目录,将 aar 文件放到 libs 目录下。
2. 在 App 模块的 build.gradle 中引入 SDK,例如:
implementation fileTree(dir: 'libs', include: ['TbsFileSdk_xxxx.aar'])
3. 配置混淆,为了功能的正常使用,需要在 proguard-rules.pro 文件中添加如下配置:
-dontwarn com.tencent.tbs.reader.**
-keep class com.tencent.tbs.reader.** {
*;
}
4. 参考下文 接入示例-自定义 layout 方式 实现文档打开代码。

接口介绍

腾讯浏览服务 SDK 不使用 TbsReaderView,统一使用 TbsFileInterfaceImpl 下的接口:

LicenseKey 初始化接口

功能介绍:设置客户端绑定的 LicenseKey,LicenseKey 获取请参见 购买方式
场景描述:用户同意隐私政策后,首先调用该接口设置 LicenseKey,才能初始化 SDK。
public static void setLicenseKey(String licenseStr)

SDK 初始化接口

功能介绍:初始化文件 SDK。
场景描述:该方法每次 App 启动后仅需调用一次,只有成功初始化 SDK 才能调用打开文档的其他接口。
/**
* 同步初始化SDK
*/
public static int initEngine(Context appContext)

/**
* 异步初始化SDK
*/
public static void initEngineAsync(Context appContext, final ITbsReaderCallback callBackListener)
initEngineAsync 相关回调值:
actionType
args
result
描述
OPEN_FILEREADER_ASYNC_LOAD_READER_ENTRY_CALLBACK
0:加载 SDK 成功
非0:加载 SDK 失败
/
异步加载 SDK

格式判断接口

功能介绍:判断该文件的格式是否是腾讯浏览服务 SDK 支持打开的文件格式。
场景描述:在打开文件前调用。
/**
* @param fileExt 文件后缀名,如文件名为test.pdf,则只需要传入"pdf"
* @return boolean
*

/public static boolean canOpenFileExt(String fileExt)

打开文档接口

功能介绍:打开本地文档。
public int openFileReader(Context cx, Bundle param, ITbsReaderCallback callback, FrameLayout layout)
参数配置:
layout:
传入 null 则使用默认的 dialog 方式,使用默认标题栏。
传入一个自定义的 layout,则没有标题栏,layout 内只显示文档内容,宿主可以自己实现标题栏及其功能(和 TbsReaderView 方式相同)。
param:
必要参数
参数名称
参数类型
参数描述
filePath
String
本地文件路径,SDK 只支持打开一个本地文档,服务器文档需要先下载到本地再调 SDK 打开
fileExt

String
文件后缀名,例如文件名为 test.pdf,则只需要传入"pdf"
tempPath
String
文件临时目录路径(文件打开过程中缓存目录,包括记录上次文档打开位置等,打开文档结束后不会自动删除,如有需要可自行删除,建议指定在沙盒目录下)
可选参数
参数名称
参数类型
参数描述
file_reader_enable_long_press_menu
Boolean
开启长按菜单复制功能,支持 PDF、DOCX、XLSX 和 TXT
file_reader_is_ppt_page_mode_default
Boolean
设置 PPT 打开为翻页模式
file_reader_stream_mode
Boolean
设置为文件流打开模式
file_reader_goto_last_pos
Boolean
打开文件自动跳转到上次浏览结束位置
file_reader_content_bg_color
String
设置文档视图的背景颜色,支持 PDF、DOCX、PPTX,如"#ffffff"
默认 dialog 模式特定参数
默认 dialog 模式提供一些自定义标题栏的参数:
参数名称
参数类型
参数描述
file_reader_top_bar_bg_color
String
设置顶 bar 的颜色,如"#ffffff"
file_reader_top_bar_hight
Int
设置顶 bar 的高度
自定义 layout 方式特定参数(老 SDK 的 TbsReaderView 方式)
如果宿主实现了固定高度的标题栏,需要传入额外的参数设置文档内容显示区域的高度(例如屏幕高度-标题栏高度):
参数名称
参数类型
描述
set_content_view_height
Int
设置文档内容的高度(默认为整个屏幕高度)
openFileReader 相关回调值:
actionType
args
result
描述
OPEN_FILEREADER_STATUS_UI_CALLBACK
打开文件:
Bundle[{typeId=0, typeDes=fileReaderOpened}]
关闭文件:
Bundle[{typeId=1, typeDes=fileReaderClosed}]
/
文件打开关闭事件
NOTIFY_CANDISPLAY
/
/
文档引擎渲染成功,即将显示文档
READER_EVENT_CLICK
/
/
单击事件
READER_EVENT_SCROLL_BEGIN
/
/
滑动开始事件
READER_EVENT_SCROLL_END
/
/
滑动结束事件
READER_EVENT_SCALE_BEGIN
/
/
缩放开始事件
READER_EVENT_SCALE_END
/
/
缩放结束事件
READER_PAGE_TOAST
Bundle[{cur_page=当前页码, page_count=总页码}]
/
获取当前页码和总页码

关闭文档接口

功能介绍:关闭文档,释放相关资源。
public void closeFileReader()

页面跳转接口

功能介绍:支持 PDF、DOCX、PPTX 跳转到指定页面。
public void gotoPosition(Bundle b)
使用示例:
int curPageIndex = -1;
ITbsReaderCallback callback = new ITbsReaderCallback() {
@Override
public void onCallBackAction(Integer actionType, Object args, Object result) {
if (ITbsReader.READER_PAGE_TOAST == actionType) {
// cur_page获取到的是当前真实的页码,需要-1才能得到页面对应的数组下标
curPageIndex = ((Bundle) args).getInt("cur_page") - 1;
}
}
};

// 上一页
Button pre = view.findViewById(R.id.iv_document_previous_page);
pre.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Bundle b = new Bundle();
b.putInt("progress", curPageIndex - 1);
TbsFileInterfaceImpl.getInstance().gotoPosition(b); // 传入页面对应的数组下标
}
});

// 下一页
Button next = view.findViewById(R.id.iv_document_next_page);
next.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Bundle b = new Bundle();
b.putInt("progress", curPageIndex + 1);
TbsFileInterfaceImpl.getInstance().gotoPosition(b);
}
});

其他功能

横屏

AndroidManifest.xml 设置打开文件的 activity 属性:
android:configChanges="orientation|keyboardHidden|navigation|screenSize"
自定义 layout 方式需要主动调用接口适配横屏,默认 dialog 模式无需调用该接口:
/**
* @param width layout宽度
* @param height layout高度
*/
public void onSizeChanged(int width, int height)

长按菜单

腾讯浏览服务文档引擎(PDF、DOCX、XLSX、TXT)支持长按复制文本功能 ,见打开文档接口可选参数。

PPT 翻页模式

参考打开文档接口可选参数,设置 PPT 打开为翻页模式(默认是滑动模式);参考页面跳转接口设置 PPT 翻页模式页面跳转。

接入示例

腾讯浏览服务 SDK 不使用 TbsReaderView,统一使用 TbsFileInterfaceImpl 下的接口,提供以下两种接入方式:

默认 Dialog 方式

openFileReader 接口 layout 传入 null。
//设置回调
ITbsReaderCallback callback = new ITbsReaderCallback() {
@Override
public void onCallBackAction(Integer actionType, Object args, Object result) {
Log.i(TAG, "actionType=" + actionType + ",args=" + args + ",result=" + result);
if (ITbsReader.OPEN_FILEREADER_STATUS_UI_CALLBACK == actionType) {
if (args instanceof Bundle) {
int id = ((Bundle) args).getInt("typeId");
if (ITbsReader.TBS_READER_TYPE_STATUS_UI_SHUTDOWN == id) {
TbsFileInterfaceImpl.getInstance().closeFileReader(); //关闭fileReader
}
}
}
}
};

// 初始化Engine,见下文
int ret = initEngine();
if(ret != 0) {
Log.i(TAG, "initEngine失败, 不要调用其他接口,ret = " + ret);
return;
}

//点击打开文档
findViewById(R.id.btn_dialog).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
ret = openFileReader(fileName);
}
});

private int openFileReader(String fileName) {
//设置参数
Bundle param = new Bundle();
param.putString("filePath", filePath); //文件路径
param.putString("fileExt", extName); // 文件后缀名,如文件名为test.pdf,则只需要传入"pdf"
param.putString("tempPath", getExternalFilesDir("temp").getAbsolutePath());

//调用openFileReader打开文档
if (TbsFileInterfaceImpl.canOpenFileExt(extName)) { //tbs支持的文档类型
int ret = TbsFileInterfaceImpl.getInstance().openFileReader(this, param, callback, null);
if (ret != 0) {
Log.i(TAG, "openFileReader失败, ret = " + ret);
}
} else {
//tbs不支持的文档类型
//...
}
}

自定义 Layout 方式

openFileReader 接口传入一个 layout。
MainActivity.java
PreviewActivity.java
activity_preview.xml
activity_main.xml

public class MainActivity extends AppCompatActivity {
private final String TAG = "FileSdkDemo";
private static boolean isInit = false;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initUI();

// 初始化Engine,见下文
int ret = initEngine();
if(ret != 0) {
Toast.makeText(getApplicationContext(), "初始化Engine失败 ret = " + ret, Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(getApplicationContext(), "初始化Engine成功", Toast.LENGTH_SHORT).show();
isInit = true;
}
}

private void initUI() {
findViewById(R.id.btn_open_file).setOnClickListener(
view -> openExternalFilesDirDocument(filePath,extName));
}

private void openExternalFilesDirDocument(String filePath, String extName) {
if(isInit) {
if(TbsFileInterfaceImpl.canOpenFileExt(fileExt)) {
PreviewActivity.start(this, filePath, extName);
} else {
// tbs不支持打开的类型
}
} else {
Toast.makeText(getApplicationContext(), "Engine未初始化成功,无法打开文件", Toast.LENGTH_SHORT).show();
}
}

public class PreviewActivity extends AppCompatActivity {
private FrameLayout mFlRoot; //内容显示区域
private RelativeLayout mRl; //自定义标题栏
private boolean isDestroyed = false;

public static void start(Context context, String filePath,String fileExt) {
context = context.getApplicationContext();
Intent starter = new Intent(context, PreviewActivity.class);
starter.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
starter.putExtra("filePath", filePath);
starter.putExtra("fileExt", fileExt);
context.startActivity(starter);
}

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_preview);
String filePath = getIntent().getStringExtra("filePath");
String fileExt = getIntent().getStringExtra("fileExt");
initView();
openFile(filePath, fileExt);
}

private void initView() {
mFlRoot = findViewById(R.id.content);
mRl = findViewById(R.id.reader_top);
}

private void openFile(String filePath, String fileExt) {
//设置回调
ITbsReaderCallback callback = new ITbsReaderCallback() {
@Override
public void onCallBackAction(Integer actionType, Object args, Object result) {
Log.i(TAG, "actionType=" + actionType + ",args=" + args + ",result=" + result);
if (ITbsReader.OPEN_FILEREADER_STATUS_UI_CALLBACK == actionType) {
if (args instanceof Bundle) {
int id = ((Bundle) args).getInt("typeId");
if (ITbsReader.TBS_READER_TYPE_STATUS_UI_SHUTDOWN == id) {
finish(); // 加密文档弹框取消需关闭activity
}
}
}
}
};
//设置参数
Bundle param = new Bundle();
param.putString("filePath", filePath);
param.putString("fileExt", fileExt); // 文件后缀名,如文件名为test.pdf,则只需要传入"pdf"
param.putString("tempPath", getExternalFilesDir("temp").getAbsolutePath());
//调用openFileReader打开文件
mFlRoot.post(new Runnable() {
@Override
public void run() {
int height = mFlRoot.getHeight();
// 自定义layout模式必须设置这个值,否则可能导致文档内容显示不全
param.putInt("set_content_view_height", height);
int ret = TbsFileInterfaceImpl.getInstance().openFileReader(
PreviewActivity.this, param, callback, mFlRoot);
}
});
}
// 销毁资源使用onpause + ondestroy的方式。避免onDestroy延迟回调
private void destroy() {
if (isDestroyed) {
return;
}
// 回收资源
mFlRoot.removeAllViews(); //移除内容显示区域layout
TbsFileInterfaceImpl.getInstance().closeFileReader(); //关闭fileReader
isDestroyed = true;
}

@Override
protected void onPause() {
super.onPause();
if (isFinishing()) {
destroy();
}
}

@Override
public void onDestroy() {
super.onDestroy();
destroy();
}
//横竖屏切换
@Override
public void onConfigurationChanged(@NonNull Configuration newConfig) {
super.onConfigurationChanged(newConfig);
mFlRoot.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { mFlRoot.getViewTreeObserver().removeOnGlobalLayoutListener(this); int w = mFlRoot.getWidth(); int h = mFlRoot.getHeight(); TbsFileInterfaceImpl.getInstance().onSizeChanged(w, h); } });
}
}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
// 自定义标题栏
<RelativeLayout
android:id="@+id/reader_top"
android:layout_width="match_parent"
android:layout_height="50dp"
android:background="#576B95">
</RelativeLayout>
// 内容显示区域
<FrameLayout
android:id="@+id/content"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="@+id/btn_open_file"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="打开文件"
android:layout_centerInParent="true"/>
</RelativeLayout>

同步初始化 Engine

public int initEngine() {
//设置licenseKey
TbsFileInterfaceImpl.setLicenseKey("your licenseKey");
int ret = -1;
//初始化Engine
if (!TbsFileInterfaceImpl.isEngineLoaded()) {
ret = TbsFileInterfaceImpl.initEngine(this);
}
return ret;
}

异步初始化 Engine

public void initEngineAsync() {
//设置licenseKey
TbsFileInterfaceImpl.setLicenseKey("your licenseKey");
ITbsReaderCallback callback = new ITbsReaderCallback() {
@Override
public void onCallBackAction(Integer actionType, Object args, Object result) {
Log.i(TAG, "actionType=" + actionType + ",args=" + args + ",result=" + result);
// ITbsReader.OPEN_FILEREADER_ASYNC_LOAD_READER_ENTRY_CALLBACK 的值为 7002,不是错误码
if (ITbsReader.OPEN_FILEREADER_ASYNC_LOAD_READER_ENTRY_CALLBACK == actionType) {
int ret = (int)args; // 错误码为actionType == 7002时 args的值
if (ret == 0) {
// 初始化成功
} else {
// 初始化失败
}
}
}
};
if (!TbsFileInterfaceImpl.isEngineLoaded()) {
TbsFileInterfaceImpl.initEngineAsync(this, callback);
}
}

打开文件流

ITbsReaderCallback callback = new ITbsReaderCallback() {
@Override
public void onCallBackAction(Integer actionType, Object args, Object result) {
// 使用文件流模式会收到该actionType回调
if (ITbsReader.OPEN_FILEREADER_STREAM_MODE_CALLBACK == actionType) {
if (args instanceof ITbsReaderStream) {
ITbsReaderStream stream = (ITbsReaderStream) args;
FileInputStream fin = null;
try {
// 实际需要打开的文件流
File realFile = new File(this.getExternalFilesDir("docs") + "/RealTbsTestFile.docx");
fin = new FileInputStream(realFile);
// 文件流打开真正的错误码
int ret = stream.write(fin);
fin.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
};

Bundle param = new Bundle();
param.putBoolean("file_reader_stream_mode", true); // 传入该参数表示使用文件流模式打开
param.putString("filePath", filePath); // 该参数必须设置,模拟文件路径。例如:"sdcard/Andoird/data/package_name/files/docs/MockTbsTestFile.docx"

// 本地必须创建一个模拟文件,可以为空文件
File file = new File(filePath);
File fileParent = file.getParentFile();
if(!fileParent.exists()){
fileParent.mkdirs();
}
file.createNewFile();

param.putString("fileExt", fileExt); // 该参数必须设置,后缀名必须和传入后续要打开的文件流类型一致。如:打开docx文件流,就要传入"docx"
param.putString("tempPath", ""); // 该参数无需设置

if (TbsFileInterfaceImpl.canOpenFileExt(fileExt)) {
// 使用文件流模式,ret会返回-8。
int ret = TbsFileInterfaceImpl.getInstance().openFileReader(this, param, callback, null);
}