Skip to content

构建一个功能完整的 PDF 阅读器(UI Extensions)

本文介绍如何基于 UI Extensions Component 快速构建一个功能完整的 PDF 阅读器(包含内置工具栏与常用功能模块 UI)。

说明 如需仅展示 PDF(不含内置 UI),请参考:构建一个功能基础的 PDF 阅读器(PDFViewCtrl)

前置条件

  1. 完成 集成福昕 Android SDK(含工程创建、SDK 集成与授权初始化)。
  2. 确认已启用 UI Extensions 依赖(参考:启用 UI Extensions)。

步骤 1:接入 UI Extensions,构建“完整阅读器”

核心思路是:

  1. 创建 PDFViewCtrl 作为渲染与交互容器。
  2. 创建 UIExtensionsManager 并绑定到 PDFViewCtrl
  3. 通过 UIExtensionsManager.openDocument() 打开文档,并将其 ContentView 作为界面内容。

1.1 主题与全屏配置(必需)

UI Extensions 会接管界面布局。为避免系统 ActionBar / 标题栏影响内置 UI 布局,建议:

  • 主题使用 NoActionBar
    • 例如 在 YourProjectName/app/src/main/res/values/themes.xml 中将主题样式设置为 Theme.MaterialComponents.Light.NoActionBar
  • Activity 设为全屏

示例(在 Activity#onCreate()super.onCreate() 之后、初始化 UI 之前调用):

java
import android.view.Window;
import android.view.WindowManager;

// Turn off the title at the top of the screen.
this.requestWindowFeature(Window.FEATURE_NO_TITLE);

// Set the window to Fullscreen.
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);

1.2 初始化 UIExtensionsManager

java
import com.foxit.uiextensions.UIExtensionsManager;

private UIExtensionsManager uiExtensionsManager = null;

uiExtensionsManager = new UIExtensionsManager(this.getApplicationContext(), pdfViewCtrl);
uiExtensionsManager.setAttachedActivity(this);
uiExtensionsManager.onCreate(this, pdfViewCtrl, savedInstanceState);
pdfViewCtrl.setUIExtensionsManager(uiExtensionsManager);

1.3 打开文档

使用 UIExtensionsManager.openDocument() 打开文档(而不是 PDFViewCtrl.openDoc()):

java
import android.os.Environment;

String path = Environment.getExternalStorageDirectory().getPath() + "/FoxitSDK/Sample.pdf";
uiExtensionsManager.openDocument(path, null);
setContentView(uiExtensionsManager.getContentView());

1.4 转发生命周期(重要)

为确保部分功能正常工作,需要在 Activity 中转发生命周期与事件回调。示例代码如下:

java
@Override
public void onStart() {
    if (uiExtensionsManager != null) {
        uiExtensionsManager.onStart(this);
    }
    super.onStart();
}

@Override
public void onStop() {
    if (uiExtensionsManager != null) {
        uiExtensionsManager.onStop(this);
    }
    super.onStop();
}

@Override
public void onPause() {
    if (uiExtensionsManager != null) {
        uiExtensionsManager.onPause(this);
    }
    super.onPause();
}

@Override
public void onResume() {
    if (uiExtensionsManager != null) {
        uiExtensionsManager.onResume(this);
    }
    super.onResume();
}

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

1.5 更新 AndroidManifest.xml(建议)

为保证部分内置功能可用且行为正确,建议同步更新 AndroidManifest.xml

  1. 权限(按功能启用)
  • 摄像头:用于扫描等需要调用相机的场景。
  • 录音:用于音视频相关能力;缺失时相关功能可能不可用。

示例:

xml
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
  1. Activity 的 configChanges(重要)

为避免旋转屏幕等配置变化触发 Activity 重建,从而影响部分功能,建议在 MainActivity 增加:

xml
<activity
    android:name=".MainActivity"
    android:configChanges="keyboardHidden|orientation|locale|layoutDirection|screenSize" />

参考实现:MainActivity.java

以下是可直接对照的 MainActivity.java 完整示例(包含 UI Extensions 初始化、运行时权限处理、文档打开与生命周期转发):

[MainActivity.java]
java
package com.foxit.pdfreader;

import android.Manifest;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.provider.Settings;
import android.view.KeyEvent;
import android.view.Window;
import android.view.WindowManager;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;

import com.foxit.sdk.PDFViewCtrl;
import com.foxit.sdk.common.Constants;
import com.foxit.sdk.common.Library;
import com.foxit.uiextensions.UIExtensionsManager;

public class MainActivity extends AppCompatActivity {

    private PDFViewCtrl pdfViewCtrl = null;
    private UIExtensionsManager uiExtensionsManager = null;
    private static final int REQUEST_EXTERNAL_STORAGE = 1;
    private static final int REQUEST_ALL_FILES_ACCESS_PERMISSION = 222;
    private static final String[] PERMISSIONS_STORAGE = {
            Manifest.permission.READ_EXTERNAL_STORAGE,
            Manifest.permission.WRITE_EXTERNAL_STORAGE
    };

    // The value of "sn" can be found in the "rdk_sn.txt".
    // The value of "key" can be found in the "rdk_key.txt".
    private static String sn = " ";
    private static String key = " ";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // initialize the library.
        int errorCode = Library.initialize(sn, key);
        if (errorCode != Constants.e_ErrSuccess)
            return;

        // Turn off the title at the top of the screen.
        this.requestWindowFeature(Window.FEATURE_NO_TITLE);
        // Set the window to Fullscreen.
        getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);

        // Instantiate a PDFViewCtrl object.
        pdfViewCtrl = new PDFViewCtrl(this);

        // Set the associated activity for RMS UI operations.
        pdfViewCtrl.setAttachedActivity(this);

        // Initialize a UIExtensionManager object and set it to PDFViewCtrl.
        uiExtensionsManager = new UIExtensionsManager(this.getApplicationContext(), pdfViewCtrl);
        uiExtensionsManager.setAttachedActivity(this);
        uiExtensionsManager.onCreate(this, pdfViewCtrl, savedInstanceState);
        pdfViewCtrl.setUIExtensionsManager(uiExtensionsManager);
        setContentView(uiExtensionsManager.getContentView());

        // Require the authorization of runtime permissions.
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
            if (!Environment.isExternalStorageManager()) {
                Intent intent = new Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION);
                intent.setData(Uri.parse("package:" + getApplicationContext().getPackageName()));
                startActivityForResult(intent, REQUEST_ALL_FILES_ACCESS_PERMISSION);
                return;
            }
        } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            int permission = ContextCompat.checkSelfPermission(this.getApplicationContext(), Manifest.permission.WRITE_EXTERNAL_STORAGE);
            if (permission != PackageManager.PERMISSION_GRANTED) {
                ActivityCompat.requestPermissions(this, PERMISSIONS_STORAGE, REQUEST_EXTERNAL_STORAGE);
                return;
            }
        }

        // Open and Render a PDF document.
        String path = Environment.getExternalStorageDirectory().getPath() + "/FoxitSDK/Sample.pdf";
        uiExtensionsManager.openDocument(path, null);
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        if (requestCode == REQUEST_EXTERNAL_STORAGE && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
            // Open and Render a PDF document.
            String path = Environment.getExternalStorageDirectory().getPath() + "/FoxitSDK/Sample.pdf";
            uiExtensionsManager.openDocument(path, null);
        } else {
            super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        }
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (requestCode == REQUEST_ALL_FILES_ACCESS_PERMISSION) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
                if (Environment.isExternalStorageManager()) {
                    String path = Environment.getExternalStorageDirectory().getPath() + "/FoxitSDK/Sample.pdf";
                    uiExtensionsManager.openDocument(path, null);
                }
            }
        } else {
            if (pdfViewCtrl != null) {
                pdfViewCtrl.handleActivityResult(requestCode, resultCode, data);
            }
        }
    }

    @Override
    public void onStart() {
        if (uiExtensionsManager != null) {
            uiExtensionsManager.onStart(this);
        }
        super.onStart();
    }

    @Override
    public void onStop() {
        if (uiExtensionsManager != null) {
            uiExtensionsManager.onStop(this);
        }
        super.onStop();
    }

    @Override
    public void onPause() {
        if (uiExtensionsManager != null) {
            uiExtensionsManager.onPause(this);
        }
        super.onPause();
    }

    @Override
    public void onResume() {
        if (uiExtensionsManager != null) {
            uiExtensionsManager.onResume(this);
        }
        super.onResume();
    }

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

    @Override
    public void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        if (uiExtensionsManager != null) {
            uiExtensionsManager.onConfigurationChanged(this, newConfig);
        }
    }

    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        if (uiExtensionsManager != null && uiExtensionsManager.onKeyDown(this, keyCode, event))
            return true;
        return super.onKeyDown(keyCode, event);
    }
}

步骤 2:运行与验证

  1. 将测试文件(例如 Sample.pdf)放到设备或模拟器的 FoxitSDK 目录(示例路径:/sdcard/FoxitSDK/Sample.pdf)。
  2. 运行应用后,按系统提示授予文件访问权限。

Android 10/11+ 存储权限说明(示例代码相关)

  • Android 10(API 29):如使用旧外部存储访问方式,可能需要在 AndroidManifest.xml 中加入 android:requestLegacyExternalStorage="true"
  • Android 11+(API 30+):若仍访问共享存储路径,可能需要引导用户授予“所有文件访问权限”(示例工程通常这样处理)。