As we all know, Android App is far less convenient than iOS system to open Office documents such as Word, Excel and PDF locally or on a remote server within the application in the domestic environment. I wrote an article before listing the pros and cons of existing solutions. Until recently, a new solution was found on the Internet: Tencent Browsing service (TBS).

TBS brief introduction


As stated on the website, TBS is committed to providing a complete solution to optimize the mobile browsing experience based on the powerful capabilities of the X5 kernel. Although the core of TBS is to provide a set of SDK to solve many problems in the use of traditional WebView. However, with enhanced browsing capabilities, we can also use this SDK to implement in-app file browsing and video playback functions. For more detailed functions, please refer to the official website:

X5.tencent.com/tbs/product…

However, the fly in the ointment is that when it comes to browsing files, the authorities do not provide complete usage documentation and Demo cases. After some twists and turns, and borrowing from this article, I finally realized the function of opening Office documents in the app, so I organized it here.

Local File browsing


Note: TBS can only open and browse local files. For remote files located on the server, online preview cannot be implemented. You can only browse by downloading and opening them first. Let’s first look at how to open local files using TBS.

The first step is to add SDK dependencies. Download jar packages and SO files provided by TBS and add them to liBS and jniLibs directories in the project. As shown in figure:

Then in the app/build.gradle file the dependency on the JAR file in the libs directory can look like this:

dependencies {
    compile fileTree(dir: 'libs'.include: ['*.jar'])}Copy the code

Note that TBS currently only provides so libraries for ARmeABi-type CPU architectures. You can also place the so file in the libs directory, but you need to modify the dependency configuration of the so file in app/build.gradle:

android {
    sourceSets {
        main {
            jniLibs.srcDirs = ['libs']}}}Copy the code

After the configuration is complete, take a look at how to use it. The main use here is the TbsReaderView class. Implement the ReaderCallback interface in the host Activity, and dynamically create TbsReaderView objects through Java code and add them to the Content View. Such as:

mTbsReaderView = new TbsReaderView(this.this);
RelativeLayout rootRl = (RelativeLayout) findViewById(R.id.rl_root);
rootRl.addView(mTbsReaderView, new RelativeLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT))Copy the code

The method provided by the ReaderCallback interface can be left unhandled (its purpose is unknown, but it must be implemented) :

@Override
public void onCallBackAction(Integer integer, Object o, Object o1) {}Copy the code

You may be wondering why you don’t put TbsReaderView in a Layout file instead of adding it manually in your code. When tested, this will result in an error indicating that the class cannot be found. Then we look at the TbsReaderView source and see that there is only one constructor:

public TbsReaderView(Context var1, TbsReaderView.ReaderCallback var2) {
  super(var1.getApplicationContext());
  if(! (var1instanceof Activity)) {
    throw new RuntimeException("error: unexpect context(none Activity)");
  } else {
    this.d = var2;
    this.a = var1;
    this.e = new au(this); }}Copy the code

There is no constructor with an AttributeSet parameter, which means that this version of TBS only allows you to create TbsReaderView instances using new.

After that, let’s look at how to open a local file. The code is also simple:

private void displayFile(a) {
  Bundle bundle = new Bundle();
  bundle.putString("filePath", getLocalFile().getPath());
  bundle.putString("tempPath", Environment.getExternalStorageDirectory().getPath());
  boolean result = mTbsReaderView.preOpen(parseFormat(mFileName), false);
  if(result) { mTbsReaderView.openFile(bundle); }}Copy the code

As you can see, the file address and another temporary directory address are passed to the TbsReaderView object as a Bundle type parameter. These two data are indispensable.

How do we know what data the Bundle parameter passes without documentation? There are two member variables in this class: TbsReaderView

public static final String KEY_FILE_PATH = "filePath";
public static final String KEY_TEMP_PATH = "tempPath";Copy the code

The first parameter is easy to understand, the second parameter does not know what the meaning, according to the name of the need should be a temporary directory address, but it is ok to upload. Here are two tips that you may need when you read the source of the Jar package:

1. How to search for a specified class file in a JAR package?

Using the jar-tf command, you can list the contents of the jar package. If combined with the grep command, you can search for the specified class file, for example:

yifeng:desktop yifeng $ jar -tf tbs_sdk_thirdapp.jar | grep -i TbsReaderView
com/tencent/smtt/sdk/TbsReaderView$ReaderCallback.class
com/tencent/smtt/sdk/TbsReaderView.classCopy the code

2. How to search for a specified string in a JAR package?

Using the zipgrep command, if the search keyword contains Spaces, use quotation marks:

yifeng:desktop yifeng $ zipgrep filePath tbs_sdk_thirdapp.jar
com/tencent/smtt/sdk/TbsReaderView.class:Binary file (standard input) matchesCopy the code

Returning to TBS usage, don’t forget to add the following to the onDestroy() lifecycle function:

@Override
protected void onDestroy(a) {
  super.onDestroy();
  mTbsReaderView.onStop();
}Copy the code

With that in mind, the entire process of opening preview local Office files in the app using TBS is over. Let’s take a look at the renderings:

As you can see, you need to download the PDF browsing plug-in provided by Tencent for the first time, and then open it without loading it again.

More often than not, the files we preview are on a remote server. Therefore, we need to download the file in advance, and then use TBS to open the local file. If you’re interested, keep reading.

Remote file download


There are many ways to download files for Android App. Here we use the simplest way to do so. We use the DownloadManager class provided by the system to realize the download function and monitor the download progress.

Let’s start by looking at how to download files using DownloadManager. According to the usage scenario, I shield the default notification message and set the local storage location. The relevant code is as follows:

mDownloadManager = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);
DownloadManager.Request request = new DownloadManager.Request(Uri.parse(mFileUrl));
request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, mFileName);
request.allowScanningByMediaScanner();
request.setNotificationVisibility(Request.VISIBILITY_HIDDEN);
mRequestId = mDownloadManager.enqueue(request);Copy the code

Then you listen for the download progress, using the ContentObserver class. Customize a listener class that inherits from ContentObserver:

private class DownloadObserver extends ContentObserver {

  private DownloadObserver(Handler handler) {
    super(handler);
  }

  @Override
  public void onChange(boolean selfChange, Uri uri) { queryDownloadStatus(); }}Copy the code

Query the download progress in real time in the onChange() callback method. When the download is complete, use TBS to open the file downloaded to the corresponding local directory:

  private void queryDownloadStatus(a) {
    DownloadManager.Query query = new DownloadManager.Query().setFilterById(mRequestId);
    Cursor cursor = null;
    try {
      cursor = mDownloadManager.query(query);
      if(cursor ! =null && cursor.moveToFirst()) {
        // Number of bytes downloaded
        int currentBytes = cursor.getInt(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR));
        // Total number of bytes to download
        int totalBytes = cursor.getInt(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_TOTAL_SIZE_BYTES));
        // Index of the column where the status is
        int status = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_STATUS));
        Log.i("downloadUpdate: ", currentBytes + "" + totalBytes + "" + status);
        mDownloadBtn.setText("Downloading:" + currentBytes + "/" + totalBytes);
        if(DownloadManager.STATUS_SUCCESSFUL == status && mDownloadBtn.getVisibility() == View.VISIBLE) { mDownloadBtn.setVisibility(View.GONE); mDownloadBtn.performClick(); }}}finally {
      if(cursor ! =null) { cursor.close(); }}}Copy the code

With these things in place, register to listen when downloading files:

mDownloadObserver = new DownloadObserver(new Handler());
getContentResolver().registerContentObserver(Uri.parse("content://downloads/my_downloads"), true, mDownloadObserver);Copy the code

And unlisten the onDestroy() method:

@Override
protected void onDestroy(a) {
  super.onDestroy();
  if(mDownloadObserver ! =null) { getContentResolver().unregisterContentObserver(mDownloadObserver); }}Copy the code

Finally, don’t forget to add permissions to the Manifest file:

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.DOWNLOAD_WITHOUT_NOTIFICATION"/>
<uses-permission android:name="android.permission.ACCESS_DOWNLOAD_MANAGER"/>
<uses-permission android:name="android.permission.WRITE_SETTINGS"/>
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>Copy the code

Overall, the file browsing function provided by TBS is very powerful. According to the website, it currently supports 42 different file formats. In addition, there are several other uses of TBS that are well worth exploring. Only, expect the official can provide more documentation to help us use it more conveniently.

Remark:

The relevant code of this article has been uploaded to GitHub website, friends who need to visit the address:

Github.com/Mike-bel/Tb…

About me: Yifeng, blog address: Yifeng. Studio /, Sina Weibo: IT Yifeng

Scan the QR code on wechat, welcome to follow my personal public account: Android Notexia

Not only share my original technical articles, but also the programmer’s workplace reverie