使用 dart extension
如何使用
dart extension 的使用场景是无法修改原类的时候,通过扩展的方式来增加原类的方法,也可以增加 getter,setters,and operators。
比如
|
1 |
int.parse('42') |
如果 String 有 pareseInt 方法,我们可以这样写
|
1 |
'42'.parseInt() |
为了达到这个目标,需要写一个 Extension
|
1 2 3 4 5 6 |
extension NumberParsing on String { int parseInt() { return int.parse(this); } // ··· } |
然后就可以使用了。
|
1 2 3 4 5 |
// Import a library that contains an extension on String. import 'string_apis.dart'; // ··· print('42'.padLeft(5)); // Use a String method. print('42'.parseInt()); // Use an extension method. |
处理冲突
注意到前面加的 NumberParsing 了吗?这个是为 extension 起的名字。起名字的作用是有冲突的时候可以方便的控制显隐。比如 NumberParsing2 也定义了 parseInt 方法与 NumberParsing 的 parseInt 冲突,如果只想要 NumberParsing 的 parseInt,只需要把 NumberParsing2 隐藏就好了。
|
1 2 3 4 5 6 7 8 9 10 |
// Defines the String extension method parseInt(). import 'string_apis.dart'; // Also defines parseInt(), but hiding NumberParsing2 // hides that extension method. import 'string_apis_2.dart' hide NumberParsing2; // ··· // Uses the parseInt() defined in 'string_apis.dart'. print('42'.parseInt()); |
或者可以 给 NumberParsing2 经起个别名
|
1 2 3 4 5 6 7 8 9 10 |
import 'string_apis.dart'; import 'string_apis_2.dart' as rad; print(NumberParsing('42').parseInt()); // Use the ParseNumbers extension from string_apis_3.dart. print(rad.NumberParsing('42').parseInt()); // Only string_apis_3.dart has parseNum(). print('42'.parseNum()); |
泛型 extension
可以只给 int 类型的 list 加 sum 方法,其它类型的 list 不能使用 sum。
|
1 2 3 4 5 |
// Import a library that contains an extension on String. import 'string_apis.dart'; // ··· print('42'.padLeft(5)); // Use a String method. print('42'.parseInt()); // Use an extension method. |
|
1 2 3 |
["1", "2", "3"].sum(); //Error: The method 'sum' isn't defined for the class 'List<String>' [1,2,3].sum(); // ok, output 6 |
要注意的问题
dart extension 不可以用于 dynamic 类型
|
1 2 |
dynamic d = '2'; print(d.parseInt()); // Runtime exception: NoSuchMethodError |
dart extension 权限很大,这也意味着,很可能被过度使用。所以在使用的时候一定要再三权衡,一定要可读性,可维护性优先。
上面的代码是比较简单的,但是如果类型比较复杂,不明确指定类型,类似如下的形式,也是可能会报错的:
|
1 2 3 4 5 6 7 8 9 10 |
class Provider { late final d; Provider() { this.d = ''; this.d.parseInt(); // Runtime exception: NoSuchMethodError } } |
参考链接
智能硬件Nvidia Jetson Nano B01
产品参数
| GPU | 128-core Maxwell |
|---|---|
| CPU | Quad-core ARM A57 @ 1.43 GHz |
| 内存 | 4 GB 64-bit LPDDR4 25.6 GB/s |
| 存储 | micro SD 卡 |
| 视频编码 | 4K @ 30 | 4x 1080p @ 30 | 9x 720p @ 30 (H.264/H.265) |
| 视频解码 | 4K @ 60 | 2x 4K @ 30 | 8x 1080p @ 30 | 18x 720p @ 30 (H.264/H.265) |
| 摄像头 | 2x MIPI CSI-2 D-PHY lanes |
| 联网 | 千兆以太网,M.2 Key E 接口外扩 (可外接: AC8265 双模网卡 ) |
| 显示 | HDMI 和 DP 显示接口 |
| USB | 4x USB 3.0,USB 2.0 Micro-B |
| 扩展接口 | GPIO,I2C,I2S,SPI,UART |
| 其他 | 260-pin 连接器 |
Jetson Nano系统安装
1、JetPack介绍
JetPack SDK包括最新的Linux驱动程序包(L4T),带有Linux操作系统和CUDA-X加速库,以及用于深度学习、计算机视觉、加速计算和多媒体的API。 它还包括用于主机和开发人员套件的示例、文档和开发人员工具,并支持更高级别的SDK,例如用于流式视频分析的DeepStream和用于机器人的Isaac。
2、JetPack 4.4
JetPack 目前最新版本是4.4,支持Vulkan 1.2、TensorRT 7.1.3 、cuDNN 8.0、CUDA 10.2 等。
3、下载和安装
- 下载 Jetson Nano镜像,镜像中包含提供引导加载程序、Ubuntu18.04、必要的固件、NVIDIA驱动程序、示例文件系统等。
- 使用 Etcher 或者 Raspberry Pi Imager 将镜像烧录到SD卡(建议至少32G)中。
设置VNC服务
1.执行更新
|
1 |
$ sudo apt-get update |
2.安装vino服务端
这个vino服务端我使用的镜像文件是安装好了的,但是古早版的镜像文件可能没有,所以可以执行下代码看看是否有安装。
|
1 |
$ sudo apt-get install vino |
3.开启VNC 服务
|
1 |
$ sudo ln -s ../vino-server.service /usr/lib/systemd/user/graphical-session.target.wants |
4.配置VNC服务
|
1 2 3 |
$ gsettings set org.gnome.Vino prompt-enabled false $ gsettings set org.gnome.Vino require-encryption false |
设置开机自启动
1.创建VNC自动启动文件
创建文件夹,然后创建一个自动启动文件
|
1 2 3 |
$ mkdir -p ~/.config/autostart $ sudo gedit ~/.config/autostart/vino-server.desktop |
2.添加以下内容到vino-server.desktop文件中
|
1 2 3 4 5 6 7 8 9 |
[Desktop Entry] Type=Application Name=Vino VNC server Exec=/usr/lib/vino/vino-server NoDisplay=true |
这个时候,虽说是自动启动了,但是只有进入桌面后才自动启动服务,所以需要取消登录密码,启动就进入桌面。
参考链接
- 玩转智能硬件之Jetson Nano(一)安装篇
- NVIDIA JetPack Documentation
- Nvidia Jetson Nano B01 初体验
- JetBot
- Get Started With Jetson Nano Developer Kit
- NVIDIA Jetson Nano
- 【从踩坑到入门】基于Jetson Nano的深度学习模型部署教程
- Xubuntu-Core / XFCE4 18.04.2 - Custom Image for the Jetson Nano
- Jetson nano 通过 vnc 实现远程桌面控制(已在nano实现)
- Step 1 - Download the pre-built JetBot SD card image
解决 Only fullscreen opaque activities can request orientation
这段时间把App的targetSDKVersion升级到了27,昨晚上线之后今早看到后台一堆崩溃,全是 Android 8.0 的设备,因为手头设备有限,测试的时候只测了Android 8.1的设备,没想到还有一个这个坑埋在这里,记录一下处理办法。
问题分析
当targetSDKVersion为26或者27时,在 Android 8.0 的设备上,一些设置了windowIsTranslucent标志,将背景设为透明,同事将屏幕方向锁定的Activity,会崩溃并抛出这个异常:
|
1 2 3 4 5 6 |
Caused by: java.lang.IllegalStateException: Only fullscreen opaque activities can request orientation at android.app.Activity.onCreate(Activity.java:1007) at android.support.v4.app.SupportActivity.onCreate(SupportActivity.java:66) at android.support.v4.app.FragmentActivity.onCreate(FragmentActivity.java:321) at android.support.v7.app.AppCompatActivity.onCreate(AppCompatActivity.java:84) at ... |
这个问题网上有很多讨论以及解决方法,问题的原因出自这里
|
1 2 3 4 |
if (ActivityInfo.isFixedOrientation(requestedOrientation) && !fullscreen && appInfo.targetSdkVersion >= O) { throw new IllegalStateException("Only fullscreen activities can request orientation"); } |
这里做了当屏幕方向锁定了并且不为全屏并且 App 的targetSdkVersion 大于 Android O的话,就会抛出这个异常。
是否为全屏的判定如下:
|
1 2 3 4 5 6 7 8 9 10 |
public static boolean isTranslucentOrFloating(TypedArray attributes) { final boolean isTranslucent = attributes.getBoolean( com.android.internal.R.styleable.Window_windowIsTranslucent, false); final boolean isSwipeToDismiss = !attributes.hasValue(com.android.internal.R.styleable.Window_windowIsTranslucent) && attributes.getBoolean(com.android.internal.R.styleable.Window_windowSwipeToDismiss, false); final boolean isFloating = attributes.getBoolean(com.android.internal.R.styleable.Window_windowIsFloating, false); return isFloating || isTranslucent || isSwipeToDismiss; } |
手头的 Android 8.1 的机器并没有触发这个问题,是因为这个问题在 8.1 里已经修复了。
解决方案
解决方法有如下几种:
-
降级
targetSDKVersion到26以下(废话!!) -
移除
mainfest文件里的screenOrientation属性 -
取消Activity主题里的
windowIsTranslucent属性或者windowSwipeToDismiss属性或者windowIsFloating属性(根据你设置了什么属性来具体分析) -
(推荐)移除
manifest文件里的screenOrientation属性,并在Activity的onCreate方法里设置屏幕方向
|
1 2 3 |
if (Build.VERSION.SDK_INT != Build.VERSION_CODES.O) { setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); } |
参考链接
解决 Only fullscreen opaque activities can request orientation
Android 12 SplashScreen API以及Jetpack兼容库
前言
1、什么是Android的冷启动时间?
冷启动时间是指点击桌面LOGO那一刻起到启动页面的Activity调用onCreate()之前的时间段。
2、冷启动的时间段内发生了什么?
在一个Activity打开时,如果Application还没有启动,系统会创建一个进程,进程的创建和初始化会消耗一些时间,在这个时间中,WindowManager会先加载APP的主题样式里的窗口背景(windowBackground)作为预览元素,然后才会真正的加载布局,如果这个时间过长,会给用户造成一种错觉:APP卡顿不流畅
常见做法
- 将背景设置为APP Logo,市面上大部分APP都是这么做的
|
1 2 3 |
<style name="AppTheme" parent="BaseTheme"> <item name="android:windowBackground">@drawable/window_splash_screen_content</item> </style> |
- 将背景设置为透明色,当点击桌面LOGO时并不会立即启动APP,而是在桌面停留一会(其实已经启动)
|
1 2 3 |
<style name="AppTheme" parent="BaseTheme"> <item name="android:windowBackground">@android:color/transparent</item> </style> |
上面做法可以达到秒开效果,但属于掩耳盗铃。
Android 8.0
Google以前不推荐使用闪屏的使用,但是后来很多APP都在使用闪屏,Google希望让启动屏的制作更简单。
Android Oreo中提供了Splash Screen API,允许开发者把一个drawable资源设置为闪屏。
新建values-v26目录:
|
1 2 3 4 5 |
<resources> <style name="AppTheme" parent="BaseTheme"> <item name="android:windowSplashscreenContent">@drawable/window_splash_screen_content</item> </style> </resources> |
通过windowSplashscreenContent设置的drawable资源将会覆盖在windowBackground顶部,在系统状态栏之下,如果不想受到System Bars限制,请使用全屏主题。
Android中使用System.exit(0)退出后App又重新启动
System.exit(0):终止当前正在运行的 Java 虚拟机。参数用作状态码;根据惯例,非 0 的状态码表示异常终止。
System.exit(0)正常终止程序,有时候在退出安卓应用会使用到。
使用这个方法如果前面存在没有finish()掉的Activity会重新启动,导致退出失败。
MainActivity代码:
直接启动第二个Activity:
|
1 2 3 4 5 6 7 |
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Intent intent=new Intent(MainActivity.this,NewActivity.class); startActivity(intent); } |
NewActivity代码:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
public class NewActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Button button=new Button(NewActivity.this); button.setText("退出测试"); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { System.exit(0); } }); setContentView(button); } } |
此时点击button退出应用重启,修改MainActivity:启动新的Activity,finish存在MainAcitvity
|
1 2 3 4 5 6 7 8 |
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Intent intent=new Intent(MainActivity.this,NewActivity.class); startActivity(intent); this.finish(); } |
总结:
因为应用栈中还存在别的activity没有finish,导致应用重新启动。
使用System.exit(0)时,确保任务栈中所有activity已经finish。
参考链接
Ubuntu 22.04 (x64)树莓派4B(Raspberry Pi 4B)源代码编译
树莓派上的操作
树莓派使用的系统是通过 Raspberry Pi Imager 安装的 2023-05-03-raspios-bullseye-armhf.img.xz 。
1.升级到最新版内核保证与下载的内核源码版本一致
|
1 |
$ sudo rpi-update |
2.升级完整后重启
|
1 |
$ sudo reboot |
3.查看内核版本
|
1 |
$ uname -r |
4.把最新版本的内核配置保存到.config中,以备以后编译内核使用
|
1 |
$ sudo modprobe configs |
文件被存储到了/proc/config.gz中。
目前最新版本是 6.1.12,当前内核启动默认会切换到 64位内核了,即使安装的是32位系统镜像也是这样。
如果想从32位内核启动,那么需要在 config.txt 中配置 arm_64bit。
多线程压缩工具pigz使用
学习Linux系统时都会学习这么几个压缩工具:gzip、bzip2、zip、xz,以及相关的解压工具。关于这几个工具的使用和相互之间的压缩比以及压缩时间对比可以看:Linux中归档压缩工具学习
那么Pigz是什么呢?简单的说,就是支持并行压缩的gzip。Pigz默认用当前逻辑cpu个数来并发压缩,无法检测个数的话,则默认并发8个线程,也可以使用-p指定线程数。需要注意的是其CPU使用比较高。
安装
|
1 2 3 4 5 |
# centos $ yum install pigz # debian / ubuntu $ sudo apt-ge tinstall pigz |
使用方法
|
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 |
$ pigz --help Usage: pigz [options] [files ...] will compress files in place, adding the suffix '.gz'. If no files are specified, stdin will be compressed to stdout. pigz does what gzip does, but spreads the work over multiple processors and cores when compressing. Options: -0 to -9, -11 Compression level (11 is much slower, a few % better) --fast, --best Compression levels 1 and 9 respectively -b, --blocksize mmm Set compression block size to mmmK (default 128K) -c, --stdout Write all processed output to stdout (won't delete) -d, --decompress Decompress the compressed input -f, --force Force overwrite, compress .gz, links, and to terminal -F --first Do iterations first, before block split for -11 -h, --help Display a help screen and quit -i, --independent Compress blocks independently for damage recovery -I, --iterations n Number of iterations for -11 optimization -k, --keep Do not delete original file after processing -K, --zip Compress to PKWare zip (.zip) single entry format -l, --list List the contents of the compressed input -L, --license Display the pigz license and quit -M, --maxsplits n Maximum number of split blocks for -11 -n, --no-name Do not store or restore file name in/from header -N, --name Store/restore file name and mod time in/from header -O --oneblock Do not split into smaller blocks for -11 -p, --processes n Allow up to n compression threads (default is the number of online processors, or 8 if unknown) -q, --quiet Print no messages, even on error -r, --recursive Process the contents of all subdirectories -R, --rsyncable Input-determined block locations for rsync -S, --suffix .sss Use suffix .sss instead of .gz (for compression) -t, --test Test the integrity of the compressed input -T, --no-time Do not store or restore mod time in/from header -v, --verbose Provide more verbose output -V --version Show the version of pigz -z, --zlib Compress to zlib (.zz) instead of gzip format -- All arguments after "--" are treated as files |
原目录大小:
|
1 2 3 4 5 6 |
[20:30 root@hulab /DataBase/Human/hg19]$ du -h 8.1G ./refgenome 1.4G ./encode_anno 4.2G ./hg19_index/hg19 8.1G ./hg19_index 18G . |
接下来我们分别使用gzip以及不同线程数的pigz对h19_index目录进行压缩,比较其运行时间。
|
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 |
### 使用gzip进行压缩(单线程) [20:30 root@hulab /DataBase/Human/hg19]$ time tar -czvf index.tar.gz hg19_index/ hg19_index/ hg19_index/hg19.tar.gz hg19_index/hg19/ hg19_index/hg19/genome.8.ht2 hg19_index/hg19/genome.5.ht2 hg19_index/hg19/genome.7.ht2 hg19_index/hg19/genome.6.ht2 hg19_index/hg19/genome.4.ht2 hg19_index/hg19/make_hg19.sh hg19_index/hg19/genome.3.ht2 hg19_index/hg19/genome.1.ht2 hg19_index/hg19/genome.2.ht2 real 5m28.824s user 5m3.866s sys 0m35.314s ### 使用4线程的pigz进行压缩 [20:36 root@hulab /DataBase/Human/hg19]$ ls encode_anno hg19_index index.tar.gz refgenome [20:38 root@hulab /DataBase/Human/hg19]$ time tar -cvf - hg19_index/ | pigz -p 4 > index_p4.tar.gz hg19_index/ hg19_index/hg19.tar.gz hg19_index/hg19/ hg19_index/hg19/genome.8.ht2 hg19_index/hg19/genome.5.ht2 hg19_index/hg19/genome.7.ht2 hg19_index/hg19/genome.6.ht2 hg19_index/hg19/genome.4.ht2 hg19_index/hg19/make_hg19.sh hg19_index/hg19/genome.3.ht2 hg19_index/hg19/genome.1.ht2 hg19_index/hg19/genome.2.ht2 real 1m18.236s user 5m22.578s sys 0m35.933s ### 使用8线程的pigz进行压缩 [20:42 root@hulab /DataBase/Human/hg19]$ time tar -cvf - hg19_index/ | pigz -p 8 > index_p8.tar.gz hg19_index/ hg19_index/hg19.tar.gz hg19_index/hg19/ hg19_index/hg19/genome.8.ht2 hg19_index/hg19/genome.5.ht2 hg19_index/hg19/genome.7.ht2 hg19_index/hg19/genome.6.ht2 hg19_index/hg19/genome.4.ht2 hg19_index/hg19/make_hg19.sh hg19_index/hg19/genome.3.ht2 hg19_index/hg19/genome.1.ht2 hg19_index/hg19/genome.2.ht2 real 0m42.670s user 5m48.527s sys 0m28.240s ### 使用16线程的pigz进行压缩 [20:43 root@hulab /DataBase/Human/hg19]$ time tar -cvf - hg19_index/ | pigz -p 16 > index_p16.tar.gz hg19_index/ hg19_index/hg19.tar.gz hg19_index/hg19/ hg19_index/hg19/genome.8.ht2 hg19_index/hg19/genome.5.ht2 hg19_index/hg19/genome.7.ht2 hg19_index/hg19/genome.6.ht2 hg19_index/hg19/genome.4.ht2 hg19_index/hg19/make_hg19.sh hg19_index/hg19/genome.3.ht2 hg19_index/hg19/genome.1.ht2 hg19_index/hg19/genome.2.ht2 real 0m23.643s user 6m24.054s sys 0m24.923s ### 使用32线程的pigz进行压缩 [20:43 root@hulab /DataBase/Human/hg19]$ time tar -cvf - hg19_index/ | pigz -p 32 > index_p32.tar.gz hg19_index/ hg19_index/hg19.tar.gz hg19_index/hg19/ hg19_index/hg19/genome.8.ht2 hg19_index/hg19/genome.5.ht2 hg19_index/hg19/genome.7.ht2 hg19_index/hg19/genome.6.ht2 hg19_index/hg19/genome.4.ht2 hg19_index/hg19/make_hg19.sh hg19_index/hg19/genome.3.ht2 hg19_index/hg19/genome.1.ht2 hg19_index/hg19/genome.2.ht2 real 0m17.523s user 7m27.479s sys 0m29.283s ### 解压文件 [21:00 root@hulab /DataBase/Human/hg19]$ time pigz -p 8 -d index_p8.tar.gz real 0m27.717s user 0m30.070s sys 0m22.515s |
各个压缩时间的比较:
| 程序 | 线程数 | 时间 |
|---|---|---|
| gzip | 1 | 5m28.824s |
| pigz | 4 | 1m18.236s |
| pigz | 8 | 0m42.670s |
| pigz | 16 | 0m23.643s |
| pigz | 32 | 0m17.523s |
从上面可以看出,使用多线程pigz进行压缩能进行大大的缩短压缩时间,特别是从单线程的gzip到4线程的pigz压缩时间缩短了4倍,继续加多线程数,压缩时间减少逐渐不那么明显。
虽然pigz能大幅度的缩短运行时间,但这是以牺牲cpu为代价的,所以对于cpu使用较高的场景不太宜使用较高的线程数,一般而言使用4线程或8线程较为合适。
参考链接
从Intel版本MacBook Pro 2013迁移到MacBook Pro 2023(Apple M2 Pro)后HomeBrew报错"Error: Cannot install in Homebrew on ARM processor in Intel default prefix (/usr/local)"
通过 TimeMachine 从 Intel 版本 MacBook Pro 2013 迁移到 MacBook Pro 2023(Apple M2 Pro) 后 HomeBrew 报错,如下:
|
1 2 3 4 5 6 |
Error: Cannot install in Homebrew on ARM processor in Intel default prefix (/usr/local)! Please create a new installation in /opt/homebrew using one of the "Alternative Installs" from: https://docs.brew.sh/Installation You can migrate your previously installed formula list with: brew bundle dump |
该报错的原因是 HomeBrew 修改了 Apple ARM 版本的设备上的默认目录,默认目录从 /usr/local 调整到了 /opt/homebrew。
由于大量的路径被硬编码到了代码中,结果就导致各种运行、安装异常。
我们能做的就是,重新在 /opt/homebrew目录下安装一遍。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
# 导出环境配置,和已经通过brew安装的应用列表,为后续重新安装这些应用进行准备 $ brew bundle dump # 执行完成后,当前目录下生成的 Brewfile 就是我们已经安装的应用列表,后续可以执行这个脚本进行恢复安装 # 卸载已经安装的 HomeBrew $ /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/uninstall.sh)" # 移除整个 /usr/local 目录,此处谨慎操作,只有确保没有与homebrew无关的应用没有安装到这个目录下才能执行删除操作 # sudo rm -rf /usr/local # 重新安装 HomeBrew $ /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" # 把路径增加到环境变量中,比如 .bashrc $ export PATH="/opt/homebrew/bin:$PATH" # 重新配置HomeBrew 并安装之前的软件 $ brew bundle --file=Brewfile |
参考链接
从Intel版本MacBook Pro 2013迁移到MacBook Pro 2023(Apple M2 Pro)后运行Android模拟器崩溃
MacBook Pro 2023(Apple M2 Pro) 从 Intel 版本 MacBook Pro 2013 通过 TimeMachine 迁移后运行模拟器,启动就崩溃。
崩溃的原因是原来运行的模拟器是通过 Intel HAXM 来加速的,但是 MacBook Pro 2023(Apple M2 Pro) 已经是 ARM 指令集了。这样的组合就诱发了虚拟机的崩溃。
此处,我们需要手工卸载已经安装的 Intel x86 Emulator Accelerator(HAXM installer) 与 Android Emulator 之后重新安装即可解决。
如下图:
继续阅读从Intel版本MacBook Pro 2013迁移到MacBook Pro 2023(Apple M2 Pro)后运行Android模拟器崩溃

