通过 Glide 在 Android 中预览远程 PDF 时出现问题
Problem previewing remote PDF in Android via Glide
我正在尝试使用 bumptech 的 Glide 库 4.8.0 版远程预览(缩略图)PDF 文档。
为了实现这一点,按照出色的教程 Writing a custom ModelLoader,我编写了一个自定义 ModelLoader,一个用于 buildLoadData 方法的自定义 DataFetcher;添加了 AppGlideModule,实现了 ModelLoaderFactory 并注册了我的 ModelLoader。
在 DataFetcher 内部,我添加了一些逻辑来处理以下两种情况:
- 内容是图片。很有魅力!
- 内容为PDF文档。 W/Glide:加载大小为 [522x600] 的 https://www.testserver.net/folder/sample.pdf 失败
class com.bumptech.glide.load.engine.GlideException: 加载资源失败
一种方法是在本地下载 PDF 文件,然后渲染它(这确实有效),但是当必须从 url 下载文件并在本地复制时,它会增加相当大的延迟;另一方面,它没有利用 Glide 对缓存的使用。
我是否应该添加另一个额外的 ModelLoader 以使用 OkHttp3 而不是 Volley(默认)?
有任何想法吗?提前致谢!
public final class MyModelLoader implements ModelLoader<File, InputStream> {
private final Context context;
public MyModelLoader(Context context) {
this.context = context;
}
@NonNull
@Override
public ModelLoader.LoadData<InputStream> buildLoadData(@NonNull File model, int width, int height, @NonNull Options options) {
return new ModelLoader.LoadData<>(new ObjectKey(model), new MyDataFetcher(context, model));
}
@Override
public boolean handles(@NonNull File file) {
return true;
}
}
public class MyDataFetcher implements DataFetcher<InputStream> {
@SuppressWarnings("FieldCanBeLocal")
private final Context context;
private final File file;
private InputStream inputStream;
public MyDataFetcher(Context context, File file) {
this.context = context;
this.file = file;
}
@Override
public void loadData(@NonNull Priority priority, @NonNull DataCallback<? super InputStream> callback) {
try {
if (isPdf(file)) {
//We have a PDF document in "file" -- fail (if document is remote)
try {
//render first page of document PDF to bitmap, and pass to method 'onDataReady' as a InputStream
PdfRenderer pdfRenderer = new PdfRenderer(ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY));
PdfRenderer.Page page = pdfRenderer.openPage(0);
int width = 2048;
int height = (page.getHeight() * (width / page.getWidth()));
Bitmap pageBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
page.render(pageBitmap, null, null, PdfRenderer.Page.RENDER_MODE_FOR_DISPLAY);
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
pageBitmap.compress(Bitmap.CompressFormat.PNG, 0, outputStream);
ByteArrayInputStream stream = new ByteArrayInputStream(outputStream.toByteArray());
callback.onDataReady(stream);
} catch (IOException ignored) {}
} else {
//We have an image in "file" -- OK
FileInputStream fileInputStream = new FileInputStream(file);
callback.onDataReady(fileInputStream);
}
} catch (IOException ignored) {}
}
// checks for file content
public boolean isPdf(File f) throws IOException {
URLConnection connection = f.toURL().openConnection();
String mimeType = connection.getContentType();
return mimeType.equals("application/pdf");
}
@Override
public void cleanup() {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException ignored) {}
}
}
@Override
public void cancel() {
//empty
}
@NonNull
@Override
public Class<InputStream> getDataClass() {
return InputStream.class;
}
@NonNull
@Override
public DataSource getDataSource() {
return DataSource.REMOTE;
}
}
public class MyModelLoaderFactory implements ModelLoaderFactory<File, InputStream> {
private final Context context;
public MyModelLoaderFactory(Context context) {
this.context = context;
}
@NonNull
@Override
public ModelLoader<File, InputStream> build(@NonNull MultiModelLoaderFactory multiFactory) {
return new MyModelLoader(context);
}
@Override
public void teardown() {
//empty
}
}
@GlideModule public class MyAppGlideModule extends AppGlideModule {
@Override
public void registerComponents(@NonNull Context context, @NonNull Glide glide, Registry registry) {
registry.prepend(File.class, InputStream.class, new MyModelLoaderFactory(context));
}
}
最后,经过上述所有操作,调用的形式为:
GlideApp.with(image.getContext()).load("resource_url").into(image);
其中“resouce_url”可以是:https://www.testserver.net/folder/sample.pdf,例如
您可以使用 Glide 库显示图像和视频缩略图,如果您想显示 pdf 缩略图,则需要使用该库,例如Android PdfViewer。然后而不是使用 ImageView 使用 PdfView 并只加载第一页而不是所有例如.pages(0)
。而已。
在你的 xml/layout
<com.github.barteksc.pdfviewer.PDFView
android:id="@+id/pdfView"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
在你的class
pdfView.fromBytes(bytes)
.pages(0) //show only first page
.spacing(0)
.swipeHorizontal(false)
.enableSwipe(false)
.load();
好吧,我终于从另一个角度找到了解决问题的方法。
我没有使用 Glide ModelLoader 在客户端预处理 pdf,而是想出了一个离群但有效的花招:在服务器端进行。
通过 php Imagick 扩展,我修改了服务器 api 以便它自动在服务器上生成缩略图(在“upload.php" 模块),与保存 pdf 的路径相同。因此,假设我们已经加载了 pdf,我们执行以下操作:
// Create thumbnail, if pdf
if ($ext == 'pdf') {
$im = new imagick($base_path.$next_id["next"].".".$ext.'[0]');
$im->setImageFormat('jpg');
header('Content-Type: image/jpeg');
file_put_contents($base_path.$next_id["next"]."_thumbnail.jpg", $im);
}
(借助此 link 使用 Imagick 将 pdf 转换为 jpg:How to convert pdf to a preview image in PHP)。
另一方面,当一条记录被删除时,它可能关联的附件(如果有的话)也必须被删除。这使得有必要同时删除缩略图,如下所示:
// Remove uploaded file from server
unlink($base_path.$id.".".$ext);
// If pdf, we also have to remove the thumbnail
if ($ext == 'pdf') {
unlink($base_path.$id."_thumbnail.jpg");
}
现在我们有一组文件,一些 jpg/png 和另一个 pdf;但这对于 Glide 来说是无关紧要的,它只会显示 jpg/png 个图像,没有任何问题,即使它们是远程的;当然很快。客户端的代码是:
/* Have a pdf file, eg. "sample.pdf", Glide will load a file
with this name: "sample_thumbnail.jpg",
that contains first page of pdf file (preview)
(A single tap on one element will download the file
and launch an intent to display it)
*/
if (item.getType().equals("jpg") || item.getType().equals("jpeg") || item.getType().equals("png")) {
Glide.with(image.getContext()).load(item.getPath()).diskCacheStrategy(DiskCacheStrategy.RESOURCE).into(image);
} else if (item.getType().equals("pdf")) {
Glide.with(image.getContext()).load(getName(item.getPath()) + "_thumbnail.jpg").diskCacheStrategy(DiskCacheStrategy.RESOURCE).into(image);
} else {
throw new Exception("File type not supported");
}
虽然可能不是每个人都可以在服务器上安装 Imagick,但就我而言,这个解决方案非常有效。
我正在尝试使用 bumptech 的 Glide 库 4.8.0 版远程预览(缩略图)PDF 文档。 为了实现这一点,按照出色的教程 Writing a custom ModelLoader,我编写了一个自定义 ModelLoader,一个用于 buildLoadData 方法的自定义 DataFetcher;添加了 AppGlideModule,实现了 ModelLoaderFactory 并注册了我的 ModelLoader。 在 DataFetcher 内部,我添加了一些逻辑来处理以下两种情况:
- 内容是图片。很有魅力!
- 内容为PDF文档。 W/Glide:加载大小为 [522x600] 的 https://www.testserver.net/folder/sample.pdf 失败 class com.bumptech.glide.load.engine.GlideException: 加载资源失败
一种方法是在本地下载 PDF 文件,然后渲染它(这确实有效),但是当必须从 url 下载文件并在本地复制时,它会增加相当大的延迟;另一方面,它没有利用 Glide 对缓存的使用。
我是否应该添加另一个额外的 ModelLoader 以使用 OkHttp3 而不是 Volley(默认)? 有任何想法吗?提前致谢!
public final class MyModelLoader implements ModelLoader<File, InputStream> {
private final Context context;
public MyModelLoader(Context context) {
this.context = context;
}
@NonNull
@Override
public ModelLoader.LoadData<InputStream> buildLoadData(@NonNull File model, int width, int height, @NonNull Options options) {
return new ModelLoader.LoadData<>(new ObjectKey(model), new MyDataFetcher(context, model));
}
@Override
public boolean handles(@NonNull File file) {
return true;
}
}
public class MyDataFetcher implements DataFetcher<InputStream> {
@SuppressWarnings("FieldCanBeLocal")
private final Context context;
private final File file;
private InputStream inputStream;
public MyDataFetcher(Context context, File file) {
this.context = context;
this.file = file;
}
@Override
public void loadData(@NonNull Priority priority, @NonNull DataCallback<? super InputStream> callback) {
try {
if (isPdf(file)) {
//We have a PDF document in "file" -- fail (if document is remote)
try {
//render first page of document PDF to bitmap, and pass to method 'onDataReady' as a InputStream
PdfRenderer pdfRenderer = new PdfRenderer(ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY));
PdfRenderer.Page page = pdfRenderer.openPage(0);
int width = 2048;
int height = (page.getHeight() * (width / page.getWidth()));
Bitmap pageBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
page.render(pageBitmap, null, null, PdfRenderer.Page.RENDER_MODE_FOR_DISPLAY);
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
pageBitmap.compress(Bitmap.CompressFormat.PNG, 0, outputStream);
ByteArrayInputStream stream = new ByteArrayInputStream(outputStream.toByteArray());
callback.onDataReady(stream);
} catch (IOException ignored) {}
} else {
//We have an image in "file" -- OK
FileInputStream fileInputStream = new FileInputStream(file);
callback.onDataReady(fileInputStream);
}
} catch (IOException ignored) {}
}
// checks for file content
public boolean isPdf(File f) throws IOException {
URLConnection connection = f.toURL().openConnection();
String mimeType = connection.getContentType();
return mimeType.equals("application/pdf");
}
@Override
public void cleanup() {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException ignored) {}
}
}
@Override
public void cancel() {
//empty
}
@NonNull
@Override
public Class<InputStream> getDataClass() {
return InputStream.class;
}
@NonNull
@Override
public DataSource getDataSource() {
return DataSource.REMOTE;
}
}
public class MyModelLoaderFactory implements ModelLoaderFactory<File, InputStream> {
private final Context context;
public MyModelLoaderFactory(Context context) {
this.context = context;
}
@NonNull
@Override
public ModelLoader<File, InputStream> build(@NonNull MultiModelLoaderFactory multiFactory) {
return new MyModelLoader(context);
}
@Override
public void teardown() {
//empty
}
}
@GlideModule public class MyAppGlideModule extends AppGlideModule {
@Override
public void registerComponents(@NonNull Context context, @NonNull Glide glide, Registry registry) {
registry.prepend(File.class, InputStream.class, new MyModelLoaderFactory(context));
}
}
最后,经过上述所有操作,调用的形式为:
GlideApp.with(image.getContext()).load("resource_url").into(image);
其中“resouce_url”可以是:https://www.testserver.net/folder/sample.pdf,例如
您可以使用 Glide 库显示图像和视频缩略图,如果您想显示 pdf 缩略图,则需要使用该库,例如Android PdfViewer。然后而不是使用 ImageView 使用 PdfView 并只加载第一页而不是所有例如.pages(0)
。而已。
在你的 xml/layout
<com.github.barteksc.pdfviewer.PDFView
android:id="@+id/pdfView"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
在你的class
pdfView.fromBytes(bytes)
.pages(0) //show only first page
.spacing(0)
.swipeHorizontal(false)
.enableSwipe(false)
.load();
好吧,我终于从另一个角度找到了解决问题的方法。 我没有使用 Glide ModelLoader 在客户端预处理 pdf,而是想出了一个离群但有效的花招:在服务器端进行。 通过 php Imagick 扩展,我修改了服务器 api 以便它自动在服务器上生成缩略图(在“upload.php" 模块),与保存 pdf 的路径相同。因此,假设我们已经加载了 pdf,我们执行以下操作:
// Create thumbnail, if pdf
if ($ext == 'pdf') {
$im = new imagick($base_path.$next_id["next"].".".$ext.'[0]');
$im->setImageFormat('jpg');
header('Content-Type: image/jpeg');
file_put_contents($base_path.$next_id["next"]."_thumbnail.jpg", $im);
}
(借助此 link 使用 Imagick 将 pdf 转换为 jpg:How to convert pdf to a preview image in PHP)。
另一方面,当一条记录被删除时,它可能关联的附件(如果有的话)也必须被删除。这使得有必要同时删除缩略图,如下所示:
// Remove uploaded file from server
unlink($base_path.$id.".".$ext);
// If pdf, we also have to remove the thumbnail
if ($ext == 'pdf') {
unlink($base_path.$id."_thumbnail.jpg");
}
现在我们有一组文件,一些 jpg/png 和另一个 pdf;但这对于 Glide 来说是无关紧要的,它只会显示 jpg/png 个图像,没有任何问题,即使它们是远程的;当然很快。客户端的代码是:
/* Have a pdf file, eg. "sample.pdf", Glide will load a file
with this name: "sample_thumbnail.jpg",
that contains first page of pdf file (preview)
(A single tap on one element will download the file
and launch an intent to display it)
*/
if (item.getType().equals("jpg") || item.getType().equals("jpeg") || item.getType().equals("png")) {
Glide.with(image.getContext()).load(item.getPath()).diskCacheStrategy(DiskCacheStrategy.RESOURCE).into(image);
} else if (item.getType().equals("pdf")) {
Glide.with(image.getContext()).load(getName(item.getPath()) + "_thumbnail.jpg").diskCacheStrategy(DiskCacheStrategy.RESOURCE).into(image);
} else {
throw new Exception("File type not supported");
}
虽然可能不是每个人都可以在服务器上安装 Imagick,但就我而言,这个解决方案非常有效。