Bootstrap

ParcelFileDescriptor+PdfRenderer在Android渲染显示PDF文件

ParcelFileDescriptor 是一个非常重要的类,用于表示一个文件描述符(File Descriptor,简称 FD),它可以让文件或数据通过进程间通信(IPC)进行共享。

1. 基本概念

  • ParcelFileDescriptorandroid.os 包下的类,通常用来操作文件或文件流,尤其是需要跨进程传递文件句柄时。
  • 它封装了一个底层的文件描述符,可以表示普通文件、管道、套接字等数据源。

2. 常见用途

  1. 跨进程共享文件:通过 Binder、AIDL 等机制,将文件描述符传递给其他进程。
  2. ContentProvider 配合:在 ContentProvider 中,可以使用 ParcelFileDescriptor 返回一个文件句柄,而不是整个文件内容。
  3. 与大文件交互:通过流的方式读取文件内容,避免直接将大文件加载到内存中。

3. 创建方式

ParcelFileDescriptor 的创建方式多种多样,以下是常见的几种:

(1)通过 open 方法打开文件

File file = new File("/path/to/file");
ParcelFileDescriptor pfd = ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);

常见模式:

  • MODE_READ_ONLY:只读模式。
  • MODE_WRITE_ONLY:只写模式。
  • MODE_READ_WRITE:读写模式。
  • MODE_APPEND:追加模式。

(2)通过 createPipe 创建管道

创建一个管道,用于进程间通信:

ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe();
ParcelFileDescriptor readSide = pipe[0];
ParcelFileDescriptor writeSide = pipe[1];

(3)通过 createSocketPair 创建套接字对

创建一对本地套接字:

ParcelFileDescriptor[] socketPair = ParcelFileDescriptor.createSocketPair();
ParcelFileDescriptor socket1 = socketPair[0];
ParcelFileDescriptor socket2 = socketPair[1];

(4)通过 fromFd 使用已有文件描述符

如果已有文件描述符,可以直接创建:

int fd = ...; // 已有的文件描述符
ParcelFileDescriptor pfd = ParcelFileDescriptor.fromFd(fd);

4. 读取和写入数据

ParcelFileDescriptor 本身不提供直接的读写操作,通常通过它获取 FileInputStreamFileOutputStream 来操作数据:

读取数据

FileInputStream fis = new FileInputStream(pfd.getFileDescriptor());
byte[] buffer = new byte[1024];
int readBytes = fis.read(buffer);
fis.close();

写入数据

FileOutputStream fos = new FileOutputStream(pfd.getFileDescriptor());
fos.write("Hello, ParcelFileDescriptor!".getBytes());
fos.close();

5. 关闭资源

ParcelFileDescriptor 使用后需要关闭,以防资源泄漏:

pfd.close();

或者使用 try-with-resources 自动关闭:

try (ParcelFileDescriptor pfd = ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY)) {
    // 使用 pfd
}

6. 常见方法

方法描述
getFileDescriptor()返回底层的 FileDescriptor 对象。
detachFd()分离底层的文件描述符,用作其他用途。
close()关闭文件描述符,释放资源。
getStatSize()返回文件的大小(如果支持)。
checkError()检查是否发生错误。

7. 使用场景举例

(1)在 ContentProvider 中共享文件

@Override
public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
    File file = new File(context.getFilesDir(), "example.txt");
    return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
}

(2)AIDL 中跨进程传递文件描述符

@Override
public ParcelFileDescriptor getFileDescriptor() {
    File file = new File("/path/to/file");
    return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
}

PdfRenderer

是 Android 提供的一个类,用于解析和渲染 PDF 文件。它需要通过 ParcelFileDescriptor 来获取 PDF 文件的描述符,从而读取和渲染 PDF 文件的页面。如果需要实现类似 PDF 阅读器的分页浏览,可以将 PdfRendererParcelFileDescriptor 结合 ViewPager 或 RecyclerView。每页创建一个 Bitmap,并动态加载和回收页面内容。

    private void renderPage(File file) {

        Observable.create((ObservableOnSubscribe<Pair<Integer, Bitmap>>) emitter -> {

                    try {
                        ParcelFileDescriptor parcelFileDescriptor = ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
                        renderer = new PdfRenderer(parcelFileDescriptor);
                        int w = getResources().getDisplayMetrics().widthPixels;
                        int padding = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 100, getResources().getDisplayMetrics());
                        int h = getResources().getDisplayMetrics().heightPixels - padding;
                        ArrayList<Bitmap> pageList = new ArrayList<>();
                        // let us just render all pages
                        final int pageCount = renderer.getPageCount();
                        for (int i = 0; i < pageCount; i++) {
                            PdfRenderer.Page page = renderer.openPage(i);
                            Bitmap bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
                            Canvas canvas = new Canvas(bitmap);
                            canvas.drawColor(Color.WHITE);
                            // say we render for showing on the screen
                            page.render(bitmap, null, null, PdfRenderer.Page.RENDER_MODE_FOR_DISPLAY);
                            // do stuff with the bitmap
                            pageList.add(bitmap);
                            // mulRenderAdapter.add(i, bitmap);
                            // close the page
                            page.close();
                            emitter.onNext(new Pair<>(i, bitmap));
//                            if (i == pageCount-1){
//                                runOnUiThread(this::dismissLoading);
//                            }
                        }
                        // close the renderer
                    } catch (Exception e) {
                        e.printStackTrace();
                        runOnUiThread(this::dismissLoading);
                    } finally {
                        if (renderer != null) {
                            renderer.close();
                        }
                    }
                }).subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new BaseObserver<Pair<Integer, Bitmap>>() {
                    @Override
                    public void onSuccess(Pair<Integer, Bitmap> pair) {
                        dismissLoading();
                        mulRenderAdapter.add(pair.first, pair.second);
                    }

                    @Override
                    public void onFailure(Throwable e) {
                        dismissLoading();
                    }
                });
//        new Handler().postDelayed(this::dismissLoading, 3 * 1000);
    }
;