Android Studio 要求: 3.0以上版本 运行的真机或模拟器要求:最好8.0以上系统,低版本的手机获取不到数据。
Ubuntu 22.04搭建TimeMachine
- 创建一个目录,作为 TimeMachine 保存数据的目录。
1 |
$ sudo mkdir /home/data/.timemachine |
- 安装
netatalk
服务和avahi-daemon
服务。
1 |
$ sudo apt install netatalk avahi-daemon |
- 编辑
netatalk
的配置文件:
1 |
$ sudo nano /etc/netatalk/afp.conf |
- 在该文件原来的基础上,或新增以下配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
[TimeCapsule] path = /home/data/.timemachine time machine = yes ; 关闭spotlight索引,这个在Centos6上是不能开的,缺依赖,7上面可以开,但是不建议,吃资源 spotlight = no ; 指定某用户有读写权限 rwlist = senra ; 强制指定用户 force user = senra ; 限制TM存储容量,单位为MB vol size limit = 100000 |
- 重启服务。
1 |
$ sudo service netatalk restart |
现在,你在 TimeMachine 上应该可以看到这个备份服务了,选择该备份服务就可以开始你的第一次备份了。
注意: Debian 12.5 系统上不能安装 netatalk
,原因是这个项目已经长时间没人维护了,后续建议直接使用 SMB 建立 TimeMachine 的备份服务器。
参考链接
Flutter CustomPainter自动绘制属性repaint原理
这篇文章主要讨论的是Fluter中CustomPainter中使用repaint属性实现自动paint的原理。
首先使用CustomPaint创建一个StatefulWidget,demo中根据点击位置,对蓝色的圆进行位置变换。
代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
import 'package:flutter/material.dart'; void main() => runApp(MyPaint()); class MyPaint extends StatefulWidget { const MyPaint({Key? key}) : super(key: key); @override _MyPaintState createState() => _MyPaintState(); } class _MyPaintState extends State<MyPaint> { ValueNotifier<double> _vn = ValueNotifier<double>(0); @override Widget build(BuildContext context) { return GestureDetector( onPanUpdate: parse, child: CustomPaint( painter: BoxPainter(vn: _vn), ), ); } dynamic parse(DragUpdateDetails details) { _vn.value = details.globalPosition.dy; } } class BoxPainter extends CustomPainter { ValueNotifier<double> vn; BoxPainter({@required this.vn}) : super(repaint: vn); @override void paint(Canvas canvas, Size size) { canvas.translate(size.width / 2, 0); canvas.drawCircle(Offset(0, vn.value), 66, Paint()..color = Colors.blue); } @override bool shouldRepaint(BoxPainter oldDelegate) => false; @override bool shouldRebuildSemantics(BoxPainter oldDelegate) => false; } |
flutter升级到3.3.9后报错“Library not loaded: @rpath/libswiftCore.dylib”
flutter 升级到 3.3.9 后,代码编译报错,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
$ flutter run Launching lib/main.dart on iPhone in debug mode... Automatically signing iOS for device deployment using specified development team in Xcode project: 4XS4BW9489 Running Xcode build... └─Compiling, linking and signing... 7.6s Xcode build done. 35.3s (lldb) dyld[627]: Library not loaded: @rpath/libswiftCore.dylib Referenced from: <8B0D72DF-EF41-3748-B8B5-8EF789672FF7> /private/var/containers/Bundle/Application/89C785E1-48B5-4139-8D0B-F392A3865D0B/Runner.app/Runner Reason: tried: '/private/var/containers/Bundle/Application/89C785E1-48B5-4139-8D0B-F392A3865D0B/Runner.app/Frameworks/libswiftCore.dylib' (errno=2), '/private/var/containers/Bundle/Application/89C785E1-48B5-4139-8D0B-F392A3865D0B/Runner.app/Frameworks/libswiftCore.dylib' (errno=2), '/private/preboot/Cryptexes/OS@rpath/libswiftCore.dylib' (errno=2), '/private/var/containers/Bundle/Application/89C785E1-48B5-4139-8D0B-F392A3865D0B/Runner.app/Frameworks/libswiftCore.dylib' (errno=2), '/private/var/containers/Bundle/Application/89C785E1-48B5-4139-8D0B-F392A3865D0B/Runner.app/Frameworks/libswiftCore.dylib' (errno=2), '/usr/local/lib/libswiftCore.dylib' (errno=2), '/usr/lib/libswiftCore.dylib' (errno=2, not in dyld cache) * thread #1, stop reason = signal SIGABRT frame #0: 0x00000001e6efeebc -> 0x1e6efeebc: b.lo 0x1e6efeedc 0x1e6efeec0: pacibsp 0x1e6efeec4: stp x29, x30, [sp, #-0x10]! 0x1e6efeec8: mov x29, sp Target 0: (Runner) stopped. Installing and launching... 26.6s Error launching application on iPhone. |
解决方法就是在工程的 "Build Settings->Linking->Runpath Search Paths" 加上 "/usr/lib/swift" 。
如下图:
继续阅读flutter升级到3.3.9后报错“Library not loaded: @rpath/libswiftCore.dylib”
VirtualBox 7.0.4安装macOS Monterey 12.6.1
硬件要求
Intel E3-1230 v3 可以成功安装, AMD Ryzen 5900 CPU 在 Linux 系统上,需要配置 CPU 模仿 Intel CPU 的特性。并且 CPU 部分,不要启用 嵌套VT-x/AMD-V 。
生成系统安装镜像
在 macOS (实验使用的系统是 macOS Big Sur (11.7.1))系统上,执行如下命令,生成系统安装镜像
1 2 3 4 5 6 7 8 9 10 11 |
$ hdiutil create -o /tmp/Monterey.cdr -size 15000m -layout SPUD -fs HFS+J $ hdiutil attach /tmp/Monterey.cdr.dmg -noverify -mountpoint /Volumes/install_build $ sudo ~/Install\ macOS\ Monterey.app/Contents/Resources/createinstallmedia --volume /Volumes/install_build $ hdiutil detach /Volumes/Install\ macOS\ Monterey $ mv /tmp/Monterey.cdr.dmg ~/Desktop/InstallSystem.dmg $ hdiutil convert ~/Desktop/InstallSystem.dmg -format UDTO -o ~/Desktop/Monterey.iso |
ubuntu 22.04升级到cpp-12后flutter编译报错"找不到 -lstdc++"
在今天晚上 ubuntu 22.04 执行如下升级命令之后
1 2 3 4 5 6 7 8 9 10 11 12 13 |
$ sudo apt-get dist-upgrade 正在读取软件包列表... 完成 正在分析软件包的依赖关系树... 完成 正在读取状态信息... 完成 正在计算更新... 完成 下列【新】软件包将被安装: cpp-12 gcc-12 libasan8 libgcc-12-dev libtsan2 下列软件包将被升级: apport apport-gtk dkms firmware-sof-signed libexpat1 libexpat1:i386 libexpat1-dev libflac8 libfreerdp-client2-2 libfreerdp-server2-2 libfreerdp2-2 libwinpr2-2 python3-apport python3-problem-report rsync xserver-common xserver-xephyr xserver-xorg-core xserver-xorg-legacy xwayland 升级了 20 个软件包,新安装了 5 个软件包,要卸载 0 个软件包,有 0 个软件包未被升级。 |
再次执行 flutter 的构建命令,报错。
操作过程如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
$ flutter run Launching lib/main.dart on Linux in debug mode... Building Linux application... /usr/include/glib-2.0/glib/glib-typeof.h:39:10: fatal error: 'type_traits' file not found /usr/include/glib-2.0/glib/glib-typeof.h:39:10: fatal error: 'type_traits' file not found Exception: Build process failed $ flutter --version Flutter 3.3.9 • channel stable • https://github.com/flutter/flutter.git Framework • revision b8f7f1f986 (2 天前) • 2022-11-23 06:43:51 +0900 Engine • revision 8f2221fbef Tools • Dart 2.18.5 • DevTools 2.15.0 $ flutter clean $ flutter run Multiple devices found: Linux (desktop) • linux • linux-x64 • Ubuntu 22.04.1 LTS 5.15.0-53-generic Chrome (web) • chrome • web-javascript • Chromium 107.0.5304.110 [1]: Linux (linux) [2]: Chrome (chrome) Please choose one (To quit, press "q/Q"): 1 Running "flutter pub get" in xxxx... 6.2s Launching lib/main.dart on Linux in debug mode... CMake Error at /usr/share/cmake-3.22/Modules/CMakeTestCXXCompiler.cmake:62 (message): The C++ compiler "/usr/bin/clang++" is not able to compile a simple test program. It fails with the following output: Change Dir: /home/xxxx/xxxx/xxxxx/build/linux/x64/debug/CMakeFiles/CMakeTmp Run Build Command(s):/usr/bin/ninja cmTC_8cfda && [1/2] Building CXX object CMakeFiles/cmTC_8cfda.dir/testCXXCompiler.cxx.o [2/2] Linking CXX executable cmTC_8cfda FAILED: cmTC_8cfda : && /usr/bin/clang++ CMakeFiles/cmTC_8cfda.dir/testCXXCompiler.cxx.o -o cmTC_8cfda && : /usr/bin/ld: 找不到 -lstdc++: 没有那个文件或目录 clang: error: linker command failed with exit code 1 (use -v to see invocation) ninja: build stopped: subcommand failed. CMake will not be able to correctly generate this project. Call Stack (most recent call first): CMakeLists.txt:3 (project) Building Linux application... Exception: Unable to generate build files |
解决方法是手工安装依赖:
1 |
$ sudo apt install lib32stdc++-12-dev |
之后再执行构建命令。
参考链接
Android R(11)文件选择兼容
对于 Android R(11) 使用 ContentResolver 检索图片,音乐,视频文件,参考 Android R(11) ContentResolver报错java.lang.IllegalArgumentException: Invalid token limit 里面的代码即可实现。但是如果想上传 PDF,TXT等文件的时候,则会发现系统无任何数据返回。
下面探讨一下如何解决这个问题:
一.储存
首先,我们需要对Android的储存有所了解
Android储存器可分为内部储存和外部储存,这里的内部储存和外部储存不是说有两个物理储存器而是系统在硬盘上划分了两个专用目录用作内部储存和外部储存。简单来说,我们通过系统文件管理器看到的目录都属于外部储存,外部储存又可分为三类目录,私有目录、公共目录、其它目录,而内部储存对于用户是隐藏的,如数据库、SharedPreferences等文件都放在内部储存中。
内部储存
1 2 3 4 5 6 |
//内部储存的文件目录获取方法,打印路径:/data/user/0/{应用包名}/files // Context.getFilesDir() //内部储存的缓存目录获取方法,打印路径:/data/user/0/{应用包名}/cache Context.getCacheDir() |
内部储存对应的目录为/data/user/0/{应用包名},该目录下应用有权限进行文件操作,目录对外不可见,应用删除对应的目录也会被删除。
外部储存
1.私有目录
1 2 3 4 5 6 |
//私有目录的文件目录获取方法,打印路径:/storage/emulated/0/Android/data/{应用包名}/files //方法参数可选,例如传入Environment.DIRECTORY_PICTURES拿到的目录为/storage/emulated/0/Android/data/com.example.android11/files/Pictures Context.getExternalFilesDir(null) //私有目录的缓存目录获取方法,打印路径:/storage/emulated/0/Android/data/{应用包名}/cache Context.getExternalCacheDir() |
私有目录获取和内部储存获取方式类似,都有file和cache目录,且该目录下应用有权限进行文件操作,目录对外可见,应用删除对应的目录也会被删除。
从Android11开始,私有目录不能被外部访问,即使获取了“所有文件管理”权限也不行(当然也是有其它方式可以实现Data目录的访问,不过目前看来并不完美)
2.公共目录
Downloads、Documents、Pictures 、DCIM、Movies、Music、Ringtones等目录都是公共目录,Android11前可以通过文件路径直接访问,Android11后需要通过MediaStore来进行访问。
3.其它目录
外部储存中除了私有目录和公共目录外都是其它目录,Android11后不能直接对其它目录进行访问。
二.分区存储
Android10中已经加入了分区储存机制,不过是非强制的,适配Android10只需在AndroidManifest.xml中添加 android:requestLegacyExternalStorage="true"即可。而在Android11已经强制应用使用分区储存。
三.兼容方式
1.MANAGE_EXTERNAL_STORAGE(不推荐)
1 2 3 |
<!-- manifest中注册 --> <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" tools:ignore="ScopedStorage" /> |
开启授权页面
1 2 3 4 5 |
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && !Environment.isExternalStorageEmulated()) { val intent = Intent(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION) intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) startActivity(intent) } |
获取“所有文件管理”的权限可以读写除私有目录外的所有文件,但是这种权限一般为文件管理类的软件才需要申请。一般APP申请此类权限若上架Google,华为等应用市场大概率被拒。
2.存储访问框架 (SAF)(推荐)
应用如果有做文件选择上传类的功能可以使用此方式,通过启动一个系统的文件浏览页面,选择需要的文件后返回一个uri,之后将uri转成流上传或者将通过uri复制文件到私有目录再操作复制后的文件进行上传,这里切记不能直接将uri转成File去进行操作。
1 2 3 4 5 |
//启动SAF文件选择 Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); intent.addCategory(Intent.CATEGORY_OPENABLE); intent.setType(MimeTypeMap.getSingleton().getMimeTypeFromExtension("pdf"));//这里以打开PDF选择为例 startActivityForResult(intent, 10086); |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
@Override public void onActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode == 10086 && resultCode == getActivity().RESULT_OK) { if (data.getData() != null) { uriToFileApiQ(this, data.getData()) } } } //将uri对应的文件复制一份到私有目录,之后就可以操作复制后的File了 @RequiresApi(Build.VERSION_CODES.Q) public File uriToFileApiQ(Context context, Uri uri) { File file = null; if (uri == null) return file; //android10以上转换 if (uri.getScheme().equals(ContentResolver.SCHEME_FILE)) { file = new File(uri.getPath()); } else if (uri.getScheme().equals(ContentResolver.SCHEME_CONTENT)) { //把文件复制到沙盒目录 ContentResolver contentResolver = context.getContentResolver(); String displayName = "uritofile" + "." + MimeTypeMap.getSingleton().getExtensionFromMimeType(contentResolver.getType(uri)); InputStream is = null; try { is = contentResolver.openInputStream(uri); File cache = new File(context.getCacheDir().getAbsolutePath(), displayName); FileOutputStream fos = new FileOutputStream(cache); byte[] b = new byte[1024]; while ((is.read(b)) != -1) { fos.write(b);// 写入数据 } file = cache; fos.close(); is.close(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } return file; } |
参考链接
如何将其他设备通过 Windows 10 自带的“连接”功能无线投屏到计算机中
请确保计算机端已打开连接窗口,再使用无线投屏功能。
在计算机端打开“连接”窗口
- 点击任务栏中的网络图标,开启 WLAN 功能。
- 点击 Windows 图标。
- 在连接窗口,您可以看到您的计算机设备名称,并显示您的计算机已准备好无线连接方式。
使用无线投屏功能
在其他设备端(如手机、平板、其他计算机)中打开无线投屏模式,搜索投影设备为上述计算机名称,进行连接即可。
- 不同的设备打开无线投屏模式的操作不一样,具体请咨询设备供应商。
- 通过华为电脑管家将手机与计算机连接,可能会影响到手机的无线投屏功能,请在我的设备>我的手机>断开连接中断开连接,并在计算机端关闭连接窗口,再重新尝试。
以华为 P30 为例:
- 下拉打开通知面板,开启 WLAN 功能,选择无线投屏。
- 手机端将自动搜索附近的可投屏设备,在可投屏的设备中选择计算机设备名称。
- 计算机端的连接窗口中将显示手机的投屏界面。
- 若要断开无线投屏,点击连接窗口右上角的关闭按钮,或关闭手机端的无线投屏功能即可。
参考链接
Windows 11右键设计反人类?教你恢复完整右键菜单
微软已经在10月5日发布了Windows 11正式版,很多朋友也已经升级了。不过对于Windows 11的一些新设计,并不是所有人都能适应的,例如新的右键快捷菜单,就不少朋友表示接受不了。
Windows 11的新右键菜单相比之前的旧款式,颜值上的确大有提升,不仅使用了Fluent Design设计语言,而且优化了文字排版,行间距更宽,便于阅读和触控。然而,Win11的右键菜单隐藏了很多选项,如果想要找到一些常用的功能,需要点击“显示更多选项”才能展开,这操作起来颇为麻烦。怎么办?今天就来给大家分享一些恢复Windows 11完整右键菜单的方法!
使用注册表修改
首先,通过修改注册表,我们就可以将Win11的右键菜单改为老样式。下面是具体的方法。
·运行“regedit”,开启注册表编辑器,定位到“HKEY_CURRENT_USER\SOFTWARE\CLASSES\CLSID”;
·接着,右键点击“CLSID”键值,新建一个名为{86ca1aa0-34aa-4e8b-a509-50c905bae2a2}的项;
·右键点击新创建的项,新建一个名为InprocServer32的项,按下回车键保存;
·最后选择新创建的项,然后双击右侧窗格中的默认条目,什么内容都不需要输入,按下回车键。
保存注册表后,重启explorer.exe,即可看到右键菜单恢复成旧样式了。
如果想要恢复成为Windows 11的设计,那么删掉InprocServer32的项就可以了。
参考链接
Android:加载PDF几种方法汇总对比
在安卓项目中,加载PDF文件,是一个比较常见的需求。又分为两大类,
1.加载网络PDF
2.加载一个本地静态PDF。
查阅资料,纵观网上在安卓中打开PDF的各种方式,大致可以分为以下几类,
1.直接使用第三方软件打开(包括浏览器打开和第三方软件打开)
如果是在app内部加载PDF文件,虽然安卓原生API对于PDF的支持又不是太好,但是各种第三方专门的阅读或者办公软件支持的是很不错的,可以通过Intent配置data和type实现。
其中,在实际需求中又会分为加载本地PDF和网络PDF的情况。
使用浏览器打开PDF:(APP外部打开,适用于加载网络PDF)
1 2 3 4 5 6 7 8 9 10 |
public static void openPDFInBrowser(Context context, String url) { Uri uri = Uri.parse(url); Intent intent = new Intent(Intent.ACTION_VIEW, uri); intent.putExtra(Browser.EXTRA_APPLICATION_ID, context.getPackageName()); try { context.startActivity(intent); } catch (ActivityNotFoundException e) { Log.w("error", "Activity was not found for intent, " + intent.toString()); } } |
使用手机上已安装的可以打开PDF的第三方软件来打开PDF:(APP外部打开,适用于加载本地PDF)
(使用这种方式缺点是:手机上如果一个可以打开PDF的软件,那么就尴尬了~)
1 2 3 4 5 6 7 8 9 10 11 12 |
public static void openPDFInNative(Context context, String FILE_NAME) { File file = new File(context.getExternalCacheDir(),FILE_NAME); Intent intent = new Intent(Intent.ACTION_VIEW); Uri uri = Uri.fromFile(file); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.setDataAndType(uri, "application/pdf"); try { context.startActivity(intent); } catch (ActivityNotFoundException e) { Log.w("URLSpan", "Activity was not found for intent, " + intent.toString()); } } |
2.连接Google服务器解析
安卓的WebView不支持PDF解析,因此通过连接Google的一个服务器转换成功后返回给WebView显示。但是,但是,但是呢,大家都懂的,天朝和Google之间有一道高高的墙。方法还是贴出来,作为国际化APP的一种方案。
1 |
mWebView.loadUrl("http://docs.google.com/gview?embedded=true&url="+ pdfUrl); |
3.用第三方库加载
Github上有一个Java开源项目 https://github.com/barteksc/AndroidPdfViewer ,
这个库的大致原理,是内置了一个PDF解析器,以流的方式将网络PDF从网上Down下来,然后再以流的方式将其还原成PDF展示出来(具体细节没关注)。亲测中,这个库每次进入webview页面都会解析加载一遍PDF,如果PDF过大,费时无缓存不说,最致命的问题乃是,
APK包体积会瞬间增大15M左右,
具体原因不明,估计应该是内置PDF解析器的问题。于是,此方法被我抛弃了。
4.使用Moliza开源的Pdf.js
这个JS类库也是很强大的,配合原生的WebView使用,支持预览,缩放,翻页的功能,实现效果和WKWebView没差。同样也有体积问题,全部放到本地apk的话包大小差不多会增加5M左右。但相比上一种方式还是轻量一些:
http://www.jb51.net/article/136364.htm
5.使用安卓自带的PdfRenderer类加载
如果要求支持的功能不是很多,用安卓提供的PdfRender就可以满足需求了。PdfRender加载多页的话可以配合ViewPager或者RecyclerView使用。需要注意的是使用PdfRender需要先将PDF文件下载到本地,是线程不安全的,并且API>=21才能使用。因为这种方式是将PDF下载到本地,于是就产生了新问题:占内存。如果是静态的PDF文件不大还好,但是如果是频繁加载网络PDF的需求,那就头疼了,这种方式需要做好定时清理删除PDF的工作,否则,GG。
这里提供的示例是阿里巴巴Android开发手册,放到assets目录下.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 |
@TargetApi(Build.VERSION_CODES.LOLLIPOP) public class MainActivity extends AppCompatActivity { private ViewPager vpPdf; private LayoutInflater mInflater; private ParcelFileDescriptor mDescriptor; private PdfRenderer mRenderer; public static final String FILE_NAME = "alibaba.pdf"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mInflater = LayoutInflater.from(this); vpPdf =findViewById(R.id.vp_pdf); try { openRender(); } catch (IOException e) { e.printStackTrace(); } } @TargetApi(Build.VERSION_CODES.LOLLIPOP) private void openRender() throws IOException { File file = new File(getExternalCacheDir(), FILE_NAME); if (!file.exists()) { //复制文件到本地存储 InputStream asset = getAssets().open(FILE_NAME); FileOutputStream fos = new FileOutputStream(file); byte[] buffer = new byte[1024]; int size; while ((size = asset.read(buffer)) != -1) { fos.write(buffer, 0, size); } asset.close(); fos.close(); } //初始化PdfRender mDescriptor = ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY); if (mDescriptor != null) { mRenderer = new PdfRenderer(mDescriptor); } //初始化ViewPager的适配器并绑定 MyAdapter adapter = new MyAdapter(); vpPdf.setAdapter(adapter); } @Override protected void onDestroy() { //销毁页面的时候释放资源,习惯 try { closeRenderer(); } catch (IOException e) { e.printStackTrace(); } super.onDestroy(); } private void closeRenderer() throws IOException{ mRenderer.close(); mDescriptor.close(); } class MyAdapter extends PagerAdapter{ @Override public int getCount() { return mRenderer.getPageCount(); } @Override public boolean isViewFromObject(View view, Object object) { return view==object; } @Override public Object instantiateItem(ViewGroup container, int position) { View view = mInflater.inflate(R.layout.item_pdf, null); PhotoView pvPdf = view.findViewById(R.id.iv_pdf); pvPdf.enable(); if (getCount() <= position) { return view; } PdfRenderer.Page currentPage = mRenderer.openPage(position); Bitmap bitmap = Bitmap.createBitmap(1080, 1760, Bitmap.Config .ARGB_8888); currentPage.render(bitmap, null, null, PdfRenderer.Page.RENDER_MODE_FOR_DISPLAY); pvPdf.setImageBitmap(bitmap); //关闭当前Page对象 currentPage.close(); container.addView(view); return view; } @Override public void destroyItem(ViewGroup container, int position, Object object) { //销毁需要销毁的视图 container.removeView((View) object); } } } |
PdfRender的大致调用方式是:
- 初始化PdfRender,将PDF文件转换为ParcelFileDescriptor作为入参
- 通过getPageCount()获得Pdf的总页数
- 循环遍历,通过openPage()获得一个封装了当前页Pdf参数的内部类Page
- 创建一个Bitmap对象,,用上步获取的Page加载,相当于把当前Pdf页的内容渲染到了Bitmap上
- 对Bitmap进行你想要的操作,每次循环结束后关闭当前Page。
大部分都是常规操作,但要注意两点:
1. 每次循环创建的Page对象在使用完毕后必须调用close()。否则其内部的openPage()判断会抛异常;
2.Bitmap占用内存较大,如果用ViewPager加载一定要重写Adapter中的destroyItem(),及时的将需要销毁的视图从父容器中移除,否则快速滑动几页后会OOM。