Android Java通过Retrofit获取响应体PDF并实现直接预览
Android端Retrofit获取PDF并直接预览实现方案
问题场景
- 技术栈:Java语言开发Android应用,使用Retrofit发起网络请求
- 需求:调用POST接口获取
application/pdf格式的文件流,直接在应用内预览,不需要将文件下载到用户可见的公共存储 - 已验证:Postman发起带空数组
[]请求体的POST请求可正常返回PDF流;Android端Retrofit已可成功请求接口,仅缺少从ResponseBody解析PDF并渲染的逻辑
原有代码问题
首先修正原有代码的两处错误,否则后续解析必然失败:
- 接口定义错误:原有接口未传入Postman测试时携带的
[]空请求体,且错误将响应头的Content-Type: application/pdf写在了请求头位置 - Retrofit配置错误:添加了
GsonConverterFactory,该转换器会尝试将二进制PDF流解析为JSON文本,直接导致流数据损坏
修正后的完整实现
1. 修正Retrofit接口定义
public interface WebApiEndPoint { @POST("MyEndpoint") Call<ResponseBody> getPdfStream( @Header("Authorization") String authToken, // 对应Postman中传递的[]空请求体 @Body RequestBody emptyJsonBody ); }
2. 修正Retrofit请求逻辑
// 构造空JSON数组请求体 RequestBody emptyBody = RequestBody.create(MediaType.parse("application/json"), "[]"); new Retrofit.Builder() .baseUrl("http://localhost:5000/api/downlodeFile/") // 移除GsonConverterFactory,避免二进制流被破坏 .build() .create(WebApiEndPoint.class) .getPdfStream(authToken, emptyBody) .enqueue(new Callback<ResponseBody>() { @Override public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) { if (!response.isSuccessful() || response.body() == null) { Toast.makeText(getActivity(), "接口请求异常", Toast.LENGTH_SHORT).show(); return; } Toast.makeText(getActivity(), response.message(), Toast.LENGTH_SHORT).show(); Log.i("PDF", "开始解析PDF流"); // 传入响应体解析并渲染PDF parseAndRenderPdf(response.body()); } @Override public void onFailure(Call<ResponseBody> call, Throwable t) { Toast.makeText(getActivity(), t.getMessage(), Toast.LENGTH_SHORT).show(); Log.e("PDF", "PDF请求失败", t); } });
3. 实现PDF解析与渲染逻辑
使用Android原生自带的PdfRenderer实现预览,不需要引入第三方依赖,流写入应用私有缓存目录,用户无感知:
private void parseAndRenderPdf(ResponseBody responseBody) { // 流读写必须放在子线程,避免主线程阻塞ANR new Thread(() -> { ParcelFileDescriptor pfd = null; PdfRenderer pdfRenderer = null; try { // 创建应用私有缓存目录下的临时文件,用户不可见,系统自动清理 File tempPdfFile = new File(requireContext().getCacheDir(), "temp_pdf_preview.pdf"); if (tempPdfFile.exists()) tempPdfFile.delete(); // 将响应流写入临时文件 InputStream is = responseBody.byteStream(); FileOutputStream fos = new FileOutputStream(tempPdfFile); byte[] buffer = new byte[4096]; int readLen; while ((readLen = is.read(buffer)) != -1) { fos.write(buffer, 0, readLen); } fos.flush(); is.close(); fos.close(); // 打开PDF文件渲染 pfd = ParcelFileDescriptor.open(tempPdfFile, ParcelFileDescriptor.MODE_READ_ONLY); pdfRenderer = new PdfRenderer(pfd); // 示例:渲染PDF第一页,多页场景可循环openPage渲染所有页面 PdfRenderer.Page firstPage = pdfRenderer.openPage(0); Bitmap pageBitmap = Bitmap.createBitmap( firstPage.getWidth(), firstPage.getHeight(), Bitmap.Config.ARGB_8888 ); firstPage.render(pageBitmap, null, null, PdfRenderer.Page.RENDER_MODE_FOR_DISPLAY); firstPage.close(); // 切回主线程更新UI requireActivity().runOnUiThread(() -> { // 将渲染好的Bitmap设置到布局中的ImageView即可展示 // 多页场景可结合ViewPager2实现翻页,也可支持缩放等交互 pdfPreviewIv.setImageBitmap(pageBitmap); Toast.makeText(getActivity(), "PDF加载完成", Toast.LENGTH_SHORT).show(); }); } catch (Exception e) { e.printStackTrace(); requireActivity().runOnUiThread(() -> Toast.makeText(getActivity(), "PDF解析失败:" + e.getMessage(), Toast.LENGTH_SHORT).show() ); } finally { // 关闭资源避免泄漏 try { if (pdfRenderer != null) pdfRenderer.close(); if (pfd != null) pfd.close(); } catch (IOException e) { e.printStackTrace(); } } }).start(); }
注意事项
- 必须在
AndroidManifest.xml添加网络权限:<uses-permission android:name="android.permission.INTERNET" /> - Android 9及以上系统默认禁止HTTP明文请求,若接口未使用HTTPS,需要在manifest的
<application>标签添加android:usesCleartextTraffic="true" PdfRenderer不支持直接读取输入流,必须落地为可随机访问的文件,写入私有缓存不属于用户感知的“下载到本地”,不会出现在系统下载目录中- 若需要更完善的PDF交互(缩放、翻页动画、书签等),可在流写入临时文件后,将文件路径传给第三方PDF预览组件,流处理逻辑无需修改
内容的提问来源于stack exchange,提问作者Learner
相关产品推荐
相关产品推荐

