make: *** No rule to make target `sqlite_cfg.h', needed by `.target_source'

前置条件

  • macOS Sonoma (14.4.1) 
  • MacBook Pro 2023-Apple M2 Pro (4能效核、8性能核、32GB内存、2TB磁盘)
  • Homebrew (4.2.18 或更高版本)
  • Xcode Version 15.3 (15E204a)
  • DevEco Studio NEXT Developer Preview2 4.1.3.700

错误信息

源代码编译 sqlite3

# https://github.com/clemensg/sqlite3pod
$ wget https://www.sqlite.org/2024/sqlite-src-3450100.zip

$ unzip sqlite-src-3450100.zip

$ cd sqlite-src-3450100

$ ./configure

$ make

或者使用 pod 安装 sqlite3 报错:

$ pod try sqlite3
Updating spec repositories

Trying sqlite3
[!] /opt/homebrew/bin/bash -c 
set -e
cd sqlite-src-3450100
./configure
make sqlite3.c sqlite3.h sqlite3ext.h

checking build system type... arm-apple-darwin23.4.0
checking host system type... arm-apple-darwin23.4.0
checking for gcc... gcc
checking whether the C compiler works... yes
checking for C compiler default output file name... a.out
checking for suffix of executables... 
checking whether we are cross compiling... no
checking for suffix of object files... o
checking whether we are using the GNU C compiler... yes
checking whether gcc accepts -g... yes
checking for gcc option to accept ISO C89... none needed
checking for a sed that does not truncate output... /opt/homebrew/bin/gsed
checking for grep that handles long lines and -e... /opt/homebrew/bin/ggrep
checking for egrep... /opt/homebrew/bin/ggrep -E
checking for fgrep... /opt/homebrew/bin/ggrep -F
checking for ld used by gcc... /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/ld
checking if the linker (/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/ld) is GNU ld... no
checking for BSD- or MS-compatible name lister (nm)... /usr/bin/nm -B
checking the name lister (/usr/bin/nm -B) interface... BSD nm
checking whether ln -s works... yes
checking the maximum length of command line arguments... 786432
checking whether the shell understands some XSI constructs... yes
checking whether the shell understands "+="... yes
checking for /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/ld option to reload object files... -r
checking for objdump... objdump
checking how to recognize dependent libraries... pass_all
checking for ar... ar
checking for strip... strip
checking for ranlib... ranlib
checking command to parse /usr/bin/nm -B output from gcc object... ok
checking for dsymutil... dsymutil
checking for nmedit... nmedit
checking for lipo... lipo
checking for otool... otool
checking for otool64... no
checking for -single_module linker flag... no
checking for -exported_symbols_list linker flag... yes
checking how to run the C preprocessor... gcc -E
checking for ANSI C header files... yes
checking for sys/types.h... yes
checking for sys/stat.h... yes
checking for stdlib.h... yes
checking for string.h... yes
checking for memory.h... yes
checking for strings.h... yes
checking for inttypes.h... yes
checking for stdint.h... yes
checking for unistd.h... yes
checking for dlfcn.h... yes
checking for objdir... .libs
checking if gcc supports -fno-rtti -fno-exceptions... yes
checking for gcc option to produce PIC... -fno-common -DPIC
checking if gcc PIC flag -fno-common -DPIC works... yes
checking if gcc static flag -static works... no
checking if gcc supports -c -o file.o... yes
checking if gcc supports -c -o file.o... (cached) yes
checking whether the gcc linker (/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/ld) supports shared libraries... yes
checking dynamic linker characteristics... darwin23.4.0 dyld
checking how to hardcode library paths into programs... immediate
checking whether stripping libraries is possible... yes
checking if libtool supports shared libraries... yes
checking whether to build shared libraries... yes
checking whether to build static libraries... yes
checking for a BSD-compatible install... /opt/homebrew/bin/ginstall -c
checking for special C compiler options needed for large files... no
checking for _FILE_OFFSET_BITS value needed for large files... no
checking for int8_t... yes
checking for int16_t... yes
checking for int32_t... yes
checking for int64_t... yes
checking for intptr_t... yes
checking for uint8_t... yes
checking for uint16_t... yes
checking for uint32_t... yes
checking for uint64_t... yes
checking for uintptr_t... yes
checking for sys/types.h... (cached) yes
checking for stdlib.h... (cached) yes
checking for stdint.h... (cached) yes
checking for inttypes.h... (cached) yes
checking malloc.h usability... no
checking malloc.h presence... no
checking for malloc.h... no
checking for fdatasync... yes
checking for gmtime_r... yes
checking for isnan... yes
checking for localtime_r... yes
checking for localtime_s... no
checking for malloc_usable_size... no
checking for strchrnul... no
checking for usleep... yes
checking for utime... yes
checking for pread... yes
checking for pread64... no
checking for pwrite... yes
checking for pwrite64... no
checking for tclsh8.7... no
checking for tclsh8.6... tclsh8.6
configure: Version set to 3.45
configure: Release set to 3.45.1
checking for WASI SDK directory... no
checking whether to support threadsafe operation... yes
checking for library containing pthread_create... none required
checking for library containing pthread_mutexattr_init... none required
checking whether to support shared library linked as release mode or not... no
checking whether to use an in-ram database for temporary tables... no
checking if executables have the .exe suffix... unknown
checking for Tcl configuration... found /opt/homebrew/Cellar/tcl-tk/8.6.14/lib/tclConfig.sh
checking for existence of /opt/homebrew/Cellar/tcl-tk/8.6.14/lib/tclConfig.sh... loading
checking for library containing readline... -ledit
not using linenoise
checking for library containing fdatasync... none required
checking build type... release
checking zlib.h usability... yes
checking zlib.h presence... yes
checking for zlib.h... yes
checking for library containing deflate... -lz
checking for library containing dlopen... none required
checking whether to support math functions... yes
checking for library containing ceil... none required
checking whether to support JSON functions... yes
checking whether to support MEMSYS5... no
checking whether to support MEMSYS3... no
checking whether to support FTS3... no
checking whether to support FTS4... no
checking whether to support FTS5... no
checking whether to support LIMIT on UPDATE and DELETE statements... no
checking whether to support GEOPOLY... no
checking whether to support RTREE... no
checking whether to support SESSION... no
configure: creating ./config.status
config.status: creating Makefile
config.status: creating sqlite3.pc
config.status: creating sqlite_cfg.h
config.status: sqlite_cfg.h is unchanged
config.status: executing libtool commands
gcc  -g -O2 -o mkkeywordhash -DSQLITE_ENABLE_MATH_FUNCTIONS  /private/var/folders/z8/_cvsdvbd4x51vm4szw5xkw0w0000gn/T/CocoaPods/Try/sqlite3/sqlite-src-3450100/tool/mkkeywordhash.c
./mkkeywordhash >keywordhash.h
gcc  -g -O2 -o lemon /private/var/folders/z8/_cvsdvbd4x51vm4szw5xkw0w0000gn/T/CocoaPods/Try/sqlite3/sqlite-src-3450100/tool/lemon.c
cp /private/var/folders/z8/_cvsdvbd4x51vm4szw5xkw0w0000gn/T/CocoaPods/Try/sqlite3/sqlite-src-3450100/tool/lempar.c .
cp /private/var/folders/z8/_cvsdvbd4x51vm4szw5xkw0w0000gn/T/CocoaPods/Try/sqlite3/sqlite-src-3450100/src/parse.y .
./lemon -DSQLITE_ENABLE_MATH_FUNCTIONS  -S parse.y
sh /private/var/folders/z8/_cvsdvbd4x51vm4szw5xkw0w0000gn/T/CocoaPods/Try/sqlite3/sqlite-src-3450100/tool/cktclsh.sh 8.4 tclsh8.6
touch has_tclsh84
cat parse.h /private/var/folders/z8/_cvsdvbd4x51vm4szw5xkw0w0000gn/T/CocoaPods/Try/sqlite3/sqlite-src-3450100/src/vdbe.c | tclsh8.6 /private/var/folders/z8/_cvsdvbd4x51vm4szw5xkw0w0000gn/T/CocoaPods/Try/sqlite3/sqlite-src-3450100/tool/mkopcodeh.tcl >opcodes.h
tclsh8.6 /private/var/folders/z8/_cvsdvbd4x51vm4szw5xkw0w0000gn/T/CocoaPods/Try/sqlite3/sqlite-src-3450100/tool/mkopcodec.tcl opcodes.h >opcodes.c
make: *** No rule to make target `sqlite_cfg.h', needed by `.target_source'.  Stop.

观察源代码目录,也确实没有 sqlite_cfg.h 文件生成。

继续阅读make: *** No rule to make target `sqlite_cfg.h', needed by `.target_source'

修复 SecurityException: getDataNetworkTypeForSubscriber 问题

1.问题

  • 用户切换改变网络的过程中,应用概率会出现崩溃,日志如下

API level: '30'
OS version: '11'
ABI list: 'arm64-v8a,armeabi-v7a,armeabi'
Manufacturer: 'OnePlus'
Brand: 'OnePlus'
Model: 'GM1900'
Build fingerprint: 'OnePlus/OnePlus7_CH/OnePlus7:11/RKQ1.201022.002/2108161921:user/release-keys'
pid: 29126, tid: 29126, name: main  >>> package name <<<

java stacktrace:
java.lang.SecurityException: getDataNetworkTypeForSubscriber
    at android.os.Parcel.createExceptionOrNull(Parcel.java:2373)
    at android.os.Parcel.createException(Parcel.java:2357)
    at android.os.Parcel.readException(Parcel.java:2340)
    at android.os.Parcel.readException(Parcel.java:2282)
    at com.android.internal.telephony.ITelephony$Stub$Proxy.getNetworkTypeForSubscriber(ITelephony.java:8803)
    at android.telephony.TelephonyManager.getNetworkType(TelephonyManager.java:3070)
    at android.telephony.TelephonyManager.getNetworkType(TelephonyManager.java:3034)
    at j.k.b0.a.e.b.c(Unknown Source:51)
    at j.k.b.q.c.w.onCreateView(:14)
    at androidx.fragment.app.Fragment.performCreateView(Unknown Source:15)
    at h.l.d.q.V(:18)
    at h.l.d.q.T(:1)
    at h.l.d.q.U(Unknown Source:47)
    at h.l.d.a.n(Unknown Source:182)
    at h.l.d.q.E(:7)
    at h.l.d.q.b0(Unknown Source:84)
    at h.l.d.q.D(Unknown Source:31)
    at h.l.d.a.e(:2)
    at h.l.d.v.finishUpdate(Unknown Source:12)
    at androidx.viewpager.widget.ViewPager.r(:2)
    at androidx.viewpager.widget.ViewPager.onMeasure(:1)
    at android.view.View.measure(View.java:25671)
    at androidx.constraintlayout.widget.ConstraintLayout.e(:2)
    at androidx.constraintlayout.widget.ConstraintLayout.onMeasure(:70)
    at android.view.View.measure(View.java:25671)
    at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6987)
    at android.widget.FrameLayout.onMeasure(FrameLayout.java:194)
    at androidx.appcompat.widget.ContentFrameLayout.onMeasure(Unknown Source:154)
    at android.view.View.measure(View.java:25671)
    at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6987)
    at android.widget.LinearLayout.measureChildBeforeLayout(LinearLayout.java:1552)
    at android.widget.LinearLayout.measureVertical(LinearLayout.java:842)
    at android.widget.LinearLayout.onMeasure(LinearLayout.java:721)
    at android.view.View.measure(View.java:25671)
    at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6987)
    at android.widget.FrameLayout.onMeasure(FrameLayout.java:194)
    at android.view.View.measure(View.java:25671)
    at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6987)
    at android.widget.LinearLayout.measureChildBeforeLayout(LinearLayout.java:1552)
    at android.widget.LinearLayout.measureVertical(LinearLayout.java:842)
    at android.widget.LinearLayout.onMeasure(LinearLayout.java:721)
    at android.view.View.measure(View.java:25671)
    at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6987)
    at android.widget.FrameLayout.onMeasure(FrameLayout.java:194)
    at com.android.internal.policy.DecorView.onMeasure(DecorView.java:776)
    at android.view.View.measure(View.java:25671)
    at android.view.ViewRootImpl.performMeasure(ViewRootImpl.java:3713)
    at android.view.ViewRootImpl.measureHierarchy(ViewRootImpl.java:2496)
    at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:2771)
    at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:2182)
    at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:8730)
    at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1352)
    at android.view.Choreographer.doCallbacks(Choreographer.java:1149)
    at android.view.Choreographer.doFrame(Choreographer.java:1049)
    at android.view.Choreographer$FrameHandler.handleMessage(Choreographer.java:1275)
    at android.os.Handler.dispatchMessage(Handler.java:106)
    at android.os.Looper.loop(Looper.java:233)
    at android.app.ActivityThread.main(ActivityThread.java:8010)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:631)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:978)

另外一种场景就是集成了华为的二维码扫描  com.huawei.hms:scanplus:1.3.2.300 ,这个  SDK 比较奇葩的地方在于,使用 Wi-Fi 网络情况下是不会出现问题的。

但是在手机流量的情况下,不申请 android.permission.READ_PHONE_STATE 权限,初始化这个 SDK 会导致应用闪退。

崩溃堆栈如下:

java.lang.SecurityException: getNetworkTypeForSubscriber
	at android.os.Parcel.createExceptionOrNull(Parcel.java:2437)
	at android.os.Parcel.createException(Parcel.java:2421)
	at android.os.Parcel.readException(Parcel.java:2404)
	at android.os.Parcel.readException(Parcel.java:2346)
	at com.android.internal.telephony.ITelephony$Stub$Proxy.getNetworkTypeForSubscriber(ITelephony.java:9325)
	at android.telephony.TelephonyManager.getNetworkType(TelephonyManager.java:3002)
	at android.telephony.TelephonyManager.getNetworkType(TelephonyManager.java:2964)
	at com.huawei.hms.mlkit.common.ha.d.b(HianalyticsLogUtils.java:68)
	at com.huawei.hms.mlkit.common.ha.HianalyticsLogProvider.logEnd(HianalyticsLogProvider.java:6315)
	at com.huawei.hms.ml.camera.g$a.a(HiAnalyticsThread.java:109)
	at com.huawei.hms.ml.camera.g$a.handleMessage(HiAnalyticsThread.java:78)
	at android.os.Handler.dispatchMessage(Handler.java:114)
	at android.os.Looper.loopOnce(Looper.java:206)
	at android.os.Looper.loop(Looper.java:296)
	at com.huawei.hms.ml.camera.g.run(HiAnalyticsThread.java:51)

2.分析

  • 根据 SecurityException: getDataNetworkTypeForSubscriber 可以看到,这是一个安全性异常,所以猜测应用在 Android11 的权限有关,由于缺少该权限导致无法访问接口而异常。

  • 找到网络状态检测方法,可以看到调用了 TelephonyManager.getNetworkType()接口获取网络类型,该方法是需要 READ_PHONE_STATE 权限的,该方法上面也有 RequiresPermission 注解声明

    @RequiresPermission(value = "android.permission.READ_PHONE_STATE")
    public int getNetworkState(Context context) {
        if (null == mConnectivityManager) { // 为空则认为无网络
            return NETWORK_NONE;
        }
        // 获取网络类型,如果为空,返回无网络
        NetworkInfo activeNetInfo = mConnectivityManager.getActiveNetworkInfo();
        if (activeNetInfo == null || !activeNetInfo.isAvailable()) {
            return NETWORK_NONE;
        }
        // 判断是否为WIFI
        NetworkInfo wifiInfo = mConnectivityManager.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
        if (null != wifiInfo) {
            NetworkInfo.State state = wifiInfo.getState();
            if (null != state) {
                if (state == NetworkInfo.State.CONNECTED || state == NetworkInfo.State.CONNECTING) {
                    return NETWORK_WIFI;
                }
            }
        }
        // 若不是WIFI,则去判断是2G、3G、4G网
        TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
        int networkType = telephonyManager.getNetworkType();
        ...
    }
  • 进一步分析,接口的调用堆栈为:

TelephonyManager.getNetworkType ->PhoneInterfaceManager.getNetworkTypeForSubscriber->TelephonyPermissions.checkCallingOrSelfReadPhoneState->TelephonyPermissions.checkReadPhoneState->ContextImpl.enforcePermission->ContextImpl.checkPermission->PermissionManager.checkPermission->PermissionTelephonyManager.getNetworkType ->
PhoneInterfaceManager.getNetworkTypeForSubscriber->
TelephonyPermissions.checkCallingOrSelfReadPhoneState->
TelephonyPermissions.checkReadPhoneState->
ContextImpl.enforcePermission->
ContextImpl.checkPermission->
PermissionManager.checkPermission->
PermissionManager.checkPermissionUncached->
ActivityManager.getService->
IActivityManager.checkPermission->

3.解决方法

private void requestPermission() {
        LogUtil.printE(HomeActivity.class, ">>> etrunc requestPermission SDK-VERSION= " + Build.VERSION.SDK_INT);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { //Android 11 授权读写权限
            XXPermissions.with(HomeActivity.this)
                    // 不适配 Android 11 可以这样写
                    //.permission(Permission.Group.STORAGE)
                    // 适配 Android 11 需要这样写,这里无需再写 Permission.Group.STORAGE
                    .permission(Permission.MANAGE_EXTERNAL_STORAGE, Permission.READ_PHONE_STATE)
                    .request(new OnPermissionCallback() {

                        @Override
                        public void onGranted(List<String> permissions, boolean all) {
                            if (all) {
                                ToastUtil.showToast(getApplication(), R.string.permission_success_tip);
                            }
                        }

                        @Override
                        public void onDenied(List<String> permissions, boolean never) {
                            if (never) {
                                ToastUtil.showToast(getApplication(), R.string.permissions_refuse_authorization);
                                // 如果是被永久拒绝就跳转到应用权限系统设置页面
                                XXPermissions.startPermissionActivity(HomeActivity.this, permissions);
                            } else {
                                ToastUtil.showToast(getApplication(), R.string.permissions_error);
                            }
                        }
                    });
        }
    }

参考链接


修复 SecurityException: getDataNetworkTypeForSubscriber 问题

Android: 通过Intent筛选多种类型文件

一般使用setType()方法来实现文件过滤,如:只显示PDF文件:

     int requestCode = 100;
     Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
     intent.setType("application/pdf");
     intent.addCategory(Intent.CATEGORY_OPENABLE);
     startActivityForResult(intent, requestCode); 

但如果要指定多种类型呢,同时要指定pdf,excel,word,ppt这些类型的文件,那要怎样设置呢?

指定多种类型文件

在网上查了,有些答案是

错误方式1——setType中进行拼接:

intent.setType(“video/*;image/*”);//同时选择视频和图片

错误方式2——调用多次setType:

intent.setType("video/*");
intent.setType("image/*");

这两种方式都是错误的

我们看下源码

/**
     * Set an explicit MIME data type.
     *
     * <p>This is used to create intents that only specify a type and not data,
     * for example to indicate the type of data to return.
     *
     * <p>This method automatically clears any data that was
     * previously set (for example by {@link #setData}).
     *
     * <p><em>Note: MIME type matching in the Android framework is
     * case-sensitive, unlike formal RFC MIME types.  As a result,
     * you should always write your MIME types with lower case letters,
     * or use {@link #normalizeMimeType} or {@link #setTypeAndNormalize}
     * to ensure that it is converted to lower case.</em>
     *
     * @param type The MIME type of the data being handled by this intent.
     *
     * @return Returns the same Intent object, for chaining multiple calls
     * into a single statement.
     *
     * @see #getType
     * @see #setTypeAndNormalize
     * @see #setDataAndType
     * @see #normalizeMimeType
     */
    public Intent setType(String type) {
        mData = null;
        mType = type;
        return this;
    }

我们可以看到,setType每次都是重新赋值,没有添加到list和数组中,因此这两种方式是实现不了指定多种类型文件的。
既然这种方式实现不了,那么Intent会不会提供字段以便我们传递过滤数据,我们通过官方文档及源码,发现Intent提供了EXTRA_MIME_TYPES这个字段来传递,而且是数组类型:

/**
     * Extra used to communicate a set of acceptable MIME types. The type of the
     * extra is {@code String[]}. Values may be a combination of concrete MIME
     * types (such as "image/png") and/or partial MIME types (such as
     * "audio/*").
     *
     * @see #ACTION_GET_CONTENT
     * @see #ACTION_OPEN_DOCUMENT
     */
    public static final String EXTRA_MIME_TYPES = "android.intent.extra.MIME_TYPES";

因此结果就简单了,我们要指定ppt,word,excel,pdf类型的文件,代码如下

public static void openDirChooseFile(Activity activity, int requestCode, String[] mimeTypes) {
        Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
        if (mimeTypes != null) {
            intent.putExtra(Intent.EXTRA_MIME_TYPES, mimeTypes);
        }
        intent.setType("*/*");
        intent.addCategory(Intent.CATEGORY_OPENABLE);
        // intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);//多选
        activity.startActivityForResult(intent, requestCode);
    }
 public static void chooseFile(Activity activity, int requestCode) {
        String[] mimeTypes = {MimeType.DOC, MimeType.DOCX, MimeType.PDF, MimeType.PPT, MimeType.PPTX, MimeType.XLS, MimeType.XLSX};
        FileUtil.openDirChooseFile(activity, requestCode, mimeTypes);
    }

MimeType文件

public class MimeType {
    public static final String DOC = "application/msword";
    public static final String DOCX = "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
    public static final String XLS = "application/vnd.ms-excel application/x-excel";
    public static final String XLSX = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
    public static final String PPT = "application/vnd.ms-powerpoint";
    public static final String PPTX = "application/vnd.openxmlformats-officedocument.presentationml.presentation";
    public static final String PDF = "application/pdf";
}

Intent指定多种类型的文件,正确的做法,是通过Intent.EXTRA_MIME_TYPES传递Mime类型数组实现

 intent.setType("*/*");
 intent.putExtra(Intent.EXTRA_MIME_TYPES, mimeTypes);

MimeType 文件列表参考

 1. Mozilla MDN Web Docs: https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types

扩展名 文档类型 MIME 类型
.aac AAC audio audio/aac
.abw AbiWord document application/x-abiword
.arc Archive document (multiple files embedded) application/x-freearc
.avi AVI: Audio Video Interleave video/x-msvideo
.azw Amazon Kindle eBook format application/vnd.amazon.ebook
.bin Any kind of binary data application/octet-stream
.bmp Windows OS/2 Bitmap Graphics image/bmp
.bz BZip archive application/x-bzip
.bz2 BZip2 archive application/x-bzip2
.csh C-Shell script application/x-csh
.css Cascading Style Sheets (CSS) text/css
.csv Comma-separated values (CSV) text/csv
.doc Microsoft Word application/msword
.docx Microsoft Word (OpenXML) application/vnd.openxmlformats-officedocument.wordprocessingml.document
.eot MS Embedded OpenType fonts application/vnd.ms-fontobject
.epub Electronic publication (EPUB) application/epub+zip
.gif Graphics Interchange Format (GIF) image/gif
.htm
.html
HyperText Markup Language (HTML) text/html
.ico Icon format image/vnd.microsoft.icon
.ics iCalendar format text/calendar
.jar Java Archive (JAR) application/java-archive
.jpeg
.jpg
JPEG images image/jpeg
.js JavaScript text/javascript
.json JSON format application/json
.jsonld JSON-LD format application/ld+json
.mid
.midi
Musical Instrument Digital Interface (MIDI) audio/midi audio/x-midi
.mjs JavaScript module text/javascript
.mp3 MP3 audio audio/mpeg
.mpeg MPEG Video video/mpeg
.mpkg Apple Installer Package application/vnd.apple.installer+xml
.odp OpenDocument presentation document application/vnd.oasis.opendocument.presentation
.ods OpenDocument spreadsheet document application/vnd.oasis.opendocument.spreadsheet
.odt OpenDocument text document application/vnd.oasis.opendocument.text
.oga OGG audio audio/ogg
.ogv OGG video video/ogg
.ogx OGG application/ogg
.otf OpenType font font/otf
.png Portable Network Graphics image/png
.pdf Adobe Portable Document Format (PDF) application/pdf
.ppt Microsoft PowerPoint application/vnd.ms-powerpoint
.pptx Microsoft PowerPoint (OpenXML) application/vnd.openxmlformats-officedocument.presentationml.presentation
.rar RAR archive application/x-rar-compressed
.rtf Rich Text Format (RTF) application/rtf
.sh Bourne shell script application/x-sh
.svg Scalable Vector Graphics (SVG) image/svg+xml
.swf Small web format (SWF) or Adobe Flash document application/x-shockwave-flash
.tar Tape Archive (TAR) application/x-tar
.tif
.tiff
Tagged Image File Format (TIFF) image/tiff
.ttf TrueType Font font/ttf
.txt Text, (generally ASCII or ISO 8859-n) text/plain
.vsd Microsoft Visio application/vnd.visio
.wav Waveform Audio Format audio/wav
.weba WEBM audio audio/webm
.webm WEBM video video/webm
.webp WEBP image image/webp
.woff Web Open Font Format (WOFF) font/woff
.woff2 Web Open Font Format (WOFF) font/woff2
.xhtml XHTML application/xhtml+xml
.xls Microsoft Excel application/vnd.ms-excel
.xlsx Microsoft Excel (OpenXML) application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
.xml XML application/xml 代码对普通用户来说不可读 (RFC 3023, section 3)
text/xml 代码对普通用户来说可读 (RFC 3023, section 3)
.xul XUL application/vnd.mozilla.xul+xml
.zip ZIP archive application/zip
.3gp 3GPP audio/video container video/3gpp
audio/3gpp(若不含视频)
.3g2 3GPP2 audio/video container video/3gpp2
audio/3gpp2(若不含视频)
.7z 7-zip archive application/x-7z-compressed

2. AOSP MediaFle:  

    public static String getMimeTypeForFile(String path) {
        MediaFileType mediaFileType = getFileType(path);
        return (mediaFileType == null ? null : mediaFileType.mimeType);
    }
    static {
        /// M: Add more audio file types to maps. {@
        addFileType("3GP", FILE_TYPE_3GPP3, "audio/3gpp");
        addFileType("3GA", FILE_TYPE_3GA, "audio/3gpp");
        addFileType("MOV", FILE_TYPE_QUICKTIME_AUDIO, "audio/quicktime");
        addFileType("QT", FILE_TYPE_QUICKTIME_AUDIO, "audio/quicktime");
        /// Add to support Apple Lossless Codec(audio/alac)
        addFileType("CAF", FILE_TYPE_CAF, "audio/alac");
        /// Add to support PCM(audio/wav)
        addFileType("WAV", FILE_TYPE_WAV, "audio/wav", MtpConstants.FORMAT_WAV);
        addFileType("OGG", FILE_TYPE_OGG, "audio/vorbis", MtpConstants.FORMAT_OGG);
        addFileType("OGG", FILE_TYPE_OGG, "audio/webm", MtpConstants.FORMAT_OGG);
        /// Add to support MP2, first add video/mp2p, so that use MP2 can return as audio type
        addFileType("MP2", FILE_TYPE_MP2PS, "video/mp2p");
        addFileType("MP2", FILE_TYPE_MP2, "audio/mpeg");
        /// Add to support Monkey's Audio APE(audio/ape)
        if (SystemProperties.getBoolean("ro.mtk_audio_ape_support", false)) {
            addFileType("APE", FILE_TYPE_APE, "audio/ape");
        }
        /// Add to support OMA DRM audio type DCF
        if (SystemProperties.getBoolean("ro.mtk_oma_drm_support", false)) {
            addFileType("DCF", FILE_TYPE_MP3, "audio/mpeg");
        }
        /// @}

        addFileType("MP3", FILE_TYPE_MP3, "audio/mpeg", MtpConstants.FORMAT_MP3);
        addFileType("MPGA", FILE_TYPE_MP3, "audio/mpeg", MtpConstants.FORMAT_MP3);
        addFileType("M4A", FILE_TYPE_M4A, "audio/mp4", MtpConstants.FORMAT_MPEG);
        addFileType("WAV", FILE_TYPE_WAV, "audio/x-wav", MtpConstants.FORMAT_WAV);
        addFileType("AMR", FILE_TYPE_AMR, "audio/amr");
        addFileType("AWB", FILE_TYPE_AWB, "audio/amr-wb");
        if (isWMAEnabled()) {
            addFileType("WMA", FILE_TYPE_WMA, "audio/x-ms-wma", MtpConstants.FORMAT_WMA);
        }
        addFileType("OGG", FILE_TYPE_OGG, "audio/ogg", MtpConstants.FORMAT_OGG);
        addFileType("OGG", FILE_TYPE_OGG, "application/ogg", MtpConstants.FORMAT_OGG);
        addFileType("OGA", FILE_TYPE_OGG, "application/ogg", MtpConstants.FORMAT_OGG);
        addFileType("AAC", FILE_TYPE_AAC, "audio/aac", MtpConstants.FORMAT_AAC);
        addFileType("AAC", FILE_TYPE_AAC, "audio/aac-adts", MtpConstants.FORMAT_AAC);
        addFileType("MKA", FILE_TYPE_MKA, "audio/x-matroska");

        addFileType("MID", FILE_TYPE_MID, "audio/midi");
        addFileType("MIDI", FILE_TYPE_MID, "audio/midi");
        addFileType("XMF", FILE_TYPE_MID, "audio/midi");
        addFileType("RTTTL", FILE_TYPE_MID, "audio/midi");
        addFileType("SMF", FILE_TYPE_SMF, "audio/sp-midi");
        addFileType("IMY", FILE_TYPE_IMY, "audio/imelody");
        addFileType("RTX", FILE_TYPE_MID, "audio/midi");
        addFileType("OTA", FILE_TYPE_MID, "audio/midi");
        addFileType("MXMF", FILE_TYPE_MID, "audio/midi");

        /// M: Add more video file types to maps. {@
        addFileType("MTS", FILE_TYPE_MP2TS, "video/mp2ts");
        addFileType("M2TS", FILE_TYPE_MP2TS, "video/mp2ts");
        addFileType("MOV", FILE_TYPE_QUICKTIME_VIDEO, "video/quicktime");
        addFileType("QT", FILE_TYPE_QUICKTIME_VIDEO, "video/quicktime");
        addFileType("OGV", FILE_TYPE_OGM, "video/ogm");
        addFileType("OGM", FILE_TYPE_OGM, "video/ogm");
        if (SystemProperties.getBoolean("ro.mtk_flv_playback_support", false)) {
            addFileType("FLV", FILE_TYPE_FLV, "video/x-flv");
            addFileType("F4V", FILE_TYPE_FLV, "video/x-flv");
            addFileType("PFV", FILE_TYPE_FLV, "video/x-flv");
            addFileType("FLA", FILE_TYPE_FLA, "audio/x-flv");
        }
        if (SystemProperties.getBoolean("ro.mtk_mtkps_playback_support", false)) {
            addFileType("PS", FILE_TYPE_MP2PS, "video/mp2p");
            /// Only support VOB when mtkps feature option is enabled
            addFileType("VOB", FILE_TYPE_MP2PS, "video/mp2p");
            /// DAT files should not be scanned as mpeg2 PS if PS is not supported.
            addFileType("DAT", FILE_TYPE_MP2PS, "video/mp2p");
        }
        /// @}

        addFileType("MPEG", FILE_TYPE_MP4, "video/mpeg", MtpConstants.FORMAT_MPEG);
        addFileType("MPG", FILE_TYPE_MP4, "video/mpeg", MtpConstants.FORMAT_MPEG);
        addFileType("MP4", FILE_TYPE_MP4, "video/mp4", MtpConstants.FORMAT_MPEG);
        addFileType("M4V", FILE_TYPE_M4V, "video/mp4", MtpConstants.FORMAT_MPEG);
        addFileType("3GP", FILE_TYPE_3GPP, "video/3gpp",  MtpConstants.FORMAT_3GP_CONTAINER);
        addFileType("3GPP", FILE_TYPE_3GPP, "video/3gpp", MtpConstants.FORMAT_3GP_CONTAINER);
        addFileType("3G2", FILE_TYPE_3GPP2, "video/3gpp2", MtpConstants.FORMAT_3GP_CONTAINER);
        addFileType("3GPP2", FILE_TYPE_3GPP2, "video/3gpp2", MtpConstants.FORMAT_3GP_CONTAINER);
        addFileType("MKV", FILE_TYPE_MKV, "video/x-matroska");
        addFileType("WEBM", FILE_TYPE_WEBM, "video/webm");
        addFileType("TS", FILE_TYPE_MP2TS, "video/mp2ts");
        addFileType("AVI", FILE_TYPE_AVI, "video/avi");

        if (isWMVEnabled()) {
            addFileType("WMV", FILE_TYPE_WMV, "video/x-ms-wmv", MtpConstants.FORMAT_WMV);
            addFileType("ASF", FILE_TYPE_ASF, "video/x-ms-asf");
        }

        /// M: Add more image file types to maps. {@
        if (!SystemProperties.getBoolean("ro.mtk_bsp_package", false)) {
            /// Mpo files should not be scanned as images in BSP
            addFileType("MPO", FILE_TYPE_MPO, "image/mpo");
        }
        /// @}

        addFileType("JPG", FILE_TYPE_JPEG, "image/jpeg", MtpConstants.FORMAT_EXIF_JPEG);
        addFileType("JPEG", FILE_TYPE_JPEG, "image/jpeg", MtpConstants.FORMAT_EXIF_JPEG);
        addFileType("GIF", FILE_TYPE_GIF, "image/gif", MtpConstants.FORMAT_GIF);
        addFileType("PNG", FILE_TYPE_PNG, "image/png", MtpConstants.FORMAT_PNG);
        addFileType("BMP", FILE_TYPE_BMP, "image/x-ms-bmp", MtpConstants.FORMAT_BMP);
        addFileType("WBMP", FILE_TYPE_WBMP, "image/vnd.wap.wbmp");
        addFileType("WEBP", FILE_TYPE_WEBP, "image/webp");

        addFileType("M3U", FILE_TYPE_M3U, "audio/x-mpegurl", MtpConstants.FORMAT_M3U_PLAYLIST);
        addFileType("M3U", FILE_TYPE_M3U, "application/x-mpegurl", MtpConstants.FORMAT_M3U_PLAYLIST);
        addFileType("PLS", FILE_TYPE_PLS, "audio/x-scpls", MtpConstants.FORMAT_PLS_PLAYLIST);
        addFileType("WPL", FILE_TYPE_WPL, "application/vnd.ms-wpl", MtpConstants.FORMAT_WPL_PLAYLIST);
        addFileType("M3U8", FILE_TYPE_HTTPLIVE, "application/vnd.apple.mpegurl");
        addFileType("M3U8", FILE_TYPE_HTTPLIVE, "audio/mpegurl");
        addFileType("M3U8", FILE_TYPE_HTTPLIVE, "audio/x-mpegurl");

        addFileType("FL", FILE_TYPE_FL, "application/x-android-drm-fl");

        addFileType("TXT", FILE_TYPE_TEXT, "text/plain", MtpConstants.FORMAT_TEXT);
        addFileType("HTM", FILE_TYPE_HTML, "text/html", MtpConstants.FORMAT_HTML);
        addFileType("HTML", FILE_TYPE_HTML, "text/html", MtpConstants.FORMAT_HTML);
        addFileType("PDF", FILE_TYPE_PDF, "application/pdf");
        addFileType("DOC", FILE_TYPE_MS_WORD, "application/msword", MtpConstants.FORMAT_MS_WORD_DOCUMENT);
        addFileType("XLS", FILE_TYPE_MS_EXCEL, "application/vnd.ms-excel", MtpConstants.FORMAT_MS_EXCEL_SPREADSHEET);
        addFileType("PPT", FILE_TYPE_MS_POWERPOINT, "application/vnd.ms-powerpoint", MtpConstants.FORMAT_MS_POWERPOINT_PRESENTATION);
        addFileType("FLAC", FILE_TYPE_FLAC, "audio/flac", MtpConstants.FORMAT_FLAC);
        addFileType("ZIP", FILE_TYPE_ZIP, "application/zip");
        addFileType("MPG", FILE_TYPE_MP2PS, "video/mp2p");
        addFileType("MPEG", FILE_TYPE_MP2PS, "video/mp2p");

        /// M: Add more Other popular file types to maps. {@
        addFileType("ICS", FILE_TYPE_ICS, "text/calendar");
        addFileType("ICZ", FILE_TYPE_ICZ, "text/calendar");
        addFileType("VCF", FILE_TYPE_VCF, "text/x-vcard");
        addFileType("VCS", FILE_TYPE_VCS, "text/x-vcalendar");
        addFileType("APK", FILE_TYPE_APK, "application/vnd.android.package-archive");
        addFileType("DOCX", FILE_TYPE_MS_WORD, "application/vnd.openxmlformats-officedocument.wordprocessingml.document");
        addFileType("DOTX", FILE_TYPE_MS_WORD, "application/vnd.openxmlformats-officedocument.wordprocessingml.template");
        addFileType("XLSX", FILE_TYPE_MS_EXCEL, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        addFileType("XLTX", FILE_TYPE_MS_EXCEL, "application/vnd.openxmlformats-officedocument.spreadsheetml.template");
        addFileType("PPTX", FILE_TYPE_MS_POWERPOINT, "application/vnd.openxmlformats-officedocument.presentationml.presentation");
        addFileType("POTX", FILE_TYPE_MS_POWERPOINT, "application/vnd.openxmlformats-officedocument.presentationml.template");
        addFileType("PPSX", FILE_TYPE_MS_POWERPOINT, "application/vnd.openxmlformats-officedocument.presentationml.slideshow");
        /// @}
    }

根据不同的MIME类型,显示不同的图标:

    /**
     * This method gets the drawable id based on the mimetype
     *
     * @param mimeType the mimeType of a file/folder
     * @return the drawable icon id based on the mimetype
     */
    public static int getDrawableId(Context context, String mimeType) {
        if (TextUtils.isEmpty(mimeType)) {
            return R.drawable.fm_unknown;
        } else if (mimeType.startsWith("application/vnd.android.package-archive")) {
            return R.drawable.fm_apk;
        } else if (mimeType.startsWith("application/zip")) {
            return R.drawable.fm_zip;
        } else if (mimeType.startsWith("application/ogg")) {
            return R.drawable.fm_audio;
        } else if (mimeType.startsWith("audio/")) {
            return R.drawable.fm_audio;
        } else if (mimeType.startsWith("image/")) {
            return R.drawable.fm_picture;
        } else if (mimeType.startsWith("text/")) {
            return R.drawable.fm_txt;
        } else if (mimeType.startsWith("video/")) {
            return R.drawable.fm_video;
        }
        return getCustomDrawableId(context, mimeType);
    }

参考链接


android: 通过Intent筛选多种类型文件

macOS Sonoma(14.2.1)通过Docker编译Android 12.1源码过程总结(MacBook Pro 2023-Apple M2 Pro)

前置条件


根据 Google 官方文档,2021年6月22日之后的Android系统版本不支持在macOS系统上构建,我们在 Applic SiliconmacOS 系统是不能直接成功构建后续版本的,但是之前的版本可以在修改编译配置后成功编译,只是是否能正常运行存疑

另外,我们需要安装 Rosetta 2 支持运行部分 x86_64 应用。注意  Rosetta 2 只支持 64 位应用,不支持 32 位应用。 参考 Does Rosetta 2 support 32-bit Intel apps?

继续阅读macOS Sonoma(14.2.1)通过Docker编译Android 12.1源码过程总结(MacBook Pro 2023-Apple M2 Pro)

Android:Installed Build Tools revision 33.0.2 is corrupted.

Remove and install again using the SDK Manager.

使用33.0.2及以上版本的build-tools编译Android应用时。

有些人会按照提示去SDK Manager中重新安装build tools,然后发现这样做是无用的

编译时会收到:

Windows:

Build-tool 33.0.2 is missing DX at D:\Sdk\build-tools\33.0.2\dx.bat

Linux/macOS:

Build-tool 33.0.2 is missing DX
解决方案:

更改批处理文件名称

Windows系统:

  1. 找到build tools目录中的d8.bat,将文件名修改为dx.bat。
  2. 找到build tools目录中的lib/d8.jar,将文件名修改为dx.jar。
  3. 回到Android Studio重新打包。

Linux/macOS系统:

  1. 找到build tools目录中的d8,创建软链接 ln -s d8 dx
  2. 找到build tools目录中的lib/d8.jar,创建软链接 ln -s d8.jar dx.jar
  3. 回到Android Studio重新打包。

参考链接


Android:Installed Build Tools revision 33.0.2 is corrupted.

使用UrlQuerySanitizer来处理URL

网上对于 UrlQuerySanitizer 的资料比较少,这个是 Android 提供的一个用来处理 url 的 API。由于项目的需要,需要对 url 的 query 参数进行排序,因此需要解析 url 并处理 query 参数。

最初的方法是使用 Uri:

public void parseUrl(String url) {
	Uri uri = Uri.parse(url);
	Set<String> query = uri.getQueryParameterNames();
	if (!query.isEmpty()) {
		TreeSet<String> treeQuery = new TreeSet<>(query);
		for (String key : treeQuery) {
			String value = uri.getQueryParameter(key);
		}
	}
}

通过这样的方式就可以解析 url,并获取到各个 query 参数。但后来发现 Uri 不能处理一些特殊字符,比如#,Uri 会截断#以后的内容,这样就不能满足开发需求。经过各种 google,最后发现了一个 UrlQuerySanitizer 的 API:

public void parseUrl(String url) {
	UrlQuerySanitizer sanitizer = new UrlQuerySanitizer();
	sanitizer.setAllowUnregisteredParamaters(true);
	sanitizer.setUnregisteredParameterValueSanitizer(UrlQuerySanitizer.getAllButNulLegal());
	sanitizer.parseUrl(url);
	final Set<String> query = sanitizer.getParameterSet();
	if (!query.isEmpty()) {
		TreeSet<String> treeQuery = new TreeSet<>(query);
		for (String key : treeQuery) {
			String value = sanitizer.getValue(key);
		}
	}
}

首先要使用 setAllowUnregisteredParamaters 让其支持特殊字符,然后使用 setUnregisteredParameterValueSanitizer 来设置支持哪些特殊字符,UrlQuerySanitizer 提供了集中默认的 ValueSanitizer:

/**
 * Return a value sanitizer that does not allow any special characters,
 * and also does not allow script URLs.
 * @return a value sanitizer
 */
public static final ValueSanitizer getAllIllegal() {
	return sAllIllegal;
}

/**
 * Return a value sanitizer that allows everything except Nul ('\0')
 * characters. Script URLs are allowed.
 * @return a value sanitizer
 */
public static final ValueSanitizer getAllButNulLegal() {
	return sAllButNulLegal;
}
/**
 * Return a value sanitizer that allows everything except Nul ('\0')
 * characters, space (' '), and other whitespace characters.
 * Script URLs are allowed.
 * @return a value sanitizer
 */
public static final ValueSanitizer getAllButWhitespaceLegal() {
	return sAllButWhitespaceLegal;
}
/**
 * Return a value sanitizer that allows all the characters used by
 * encoded URLs. Does not allow script URLs.
 * @return a value sanitizer
 */
public static final ValueSanitizer getUrlLegal() {
	return sURLLegal;
}
/**
 * Return a value sanitizer that allows all the characters used by
 * encoded URLs and allows spaces, which are not technically legal
 * in encoded URLs, but commonly appear anyway.
 * Does not allow script URLs.
 * @return a value sanitizer
 */
public static final ValueSanitizer getUrlAndSpaceLegal() {
	return sUrlAndSpaceLegal;
}
/**
 * Return a value sanitizer that does not allow any special characters
 * except ampersand ('&'). Does not allow script URLs.
 * @return a value sanitizer
 */
public static final ValueSanitizer getAmpLegal() {
	return sAmpLegal;
}
/**
 * Return a value sanitizer that does not allow any special characters
 * except ampersand ('&') and space (' '). Does not allow script URLs.
 * @return a value sanitizer
 */
public static final ValueSanitizer getAmpAndSpaceLegal() {
	return sAmpAndSpaceLegal;
}
/**
 * Return a value sanitizer that does not allow any special characters
 * except space (' '). Does not allow script URLs.
 * @return a value sanitizer
 */
public static final ValueSanitizer getSpaceLegal() {
	return sSpaceLegal;
}
/**
 * Return a value sanitizer that allows any special characters
 * except angle brackets ('<' and '>') and Nul ('\0').
 * Allows script URLs.
 * @return a value sanitizer
 */
public static final ValueSanitizer getAllButNulAndAngleBracketsLegal() {
	return sAllButNulAndAngleBracketsLegal;
}

每种 ValueSanitizer 都对应过滤哪些字符,被过滤掉的特殊字符会被替换成_或者空格。
如果默认的 ValueSanitizer 不能满足开发需求,还可以自己构造 ValueSanitizer:

public void parseUrl(String url) {
	.....
	ValueSanitizer sanitizer = new UrlQuerySanitizer.IllegalCharacterValueSanitizer(UrlQuerySanitizer.IllegalCharacterValueSanitizer.ALL_OK);
	setUnregisteredParameterValueSanitizer(sanitizer);
	.....
}

UrlQuerySanitizer 也可以通过 key 来获取相应的 value,比如给一个 url:http://coolerfall.com?name=vincent:

public void parseUrl(String url) {
	UrlQuerySanitizer sanitizer = new UrlQuerySanitizer();
	sanitizer.setAllowUnregisteredParamaters(true);
	sanitizer.setUnregisteredParameterValueSanitizer(UrlQuerySanitizer.getAllButNulLegal());
	sanitizer.parseUrl(url);
	String name = sanitizer.getValue("name");
}

UrlQuerySanitizer 还可以只解析 query 参数,比如:name=vincent&article=first:

public void parseUrl(String query) {
	UrlQuerySanitizer sanitizer = new UrlQuerySanitizer();
	sanitizer.setAllowUnregisteredParamaters(true);
	sanitizer.setUnregisteredParameterValueSanitizer(UrlQuerySanitizer.getAllButNulLegal());
	sanitizer.parseQuery(query);
	String name = sanitizer.getValue("name");
	.....
}

以上就是 UrlQuerySanitizer 大致用法,用来解析处理 url 非常的方便。

参考链接


使用 UrlQuerySanitizer 来处理 url

macOS Sonoma(14.2.1)通过UTM虚拟机编译Android 12.1源码过程总结(MacBook Pro 2023-Apple M2 Pro)

前置条件


根据 Google 官方文档,2021年6月22日之后的Android系统版本不支持在macOS系统上构建,我们在 Applic SiliconmacOS 系统是不能直接成功构建后续版本的,但是之前的版本可以在修改编译配置后成功编译,只是是否能正常运行存疑

尝试过使用 Podman Desktop / Docker 方式进行编译、也尝试过借助 OrbStackLima 这些纯虚拟机通过安装 ubuntu 22.04 系统镜像的方式进行编译,结果都在执行 lunch 命令的时候长时间卡住,观察系统进程发现名为 nsjail 进程的 CPU 占用持续卡住在 100% 上无法继续编译,并且由于 Docker 或者虚拟机文件系统是 Linux 文件系统,而宿主机的文件系统是 AppleFS 文件系统,导致需要进行文件转换,中间的转换性能代价非常高,性能很差。

相反,目前测试来看,直接在 UTM 虚拟机里执行编译的性能反倒更好一些。

目前测试发现存在严重的文件系统缺陷,编译/大量文件复制过程中,经常出现文件系统损坏,导致编译失败。

原因参考:

https://github.com/utmapp/UTM/pull/5869
https://github.com/utmapp/UTM/pull/5919

需要等待 UTM 合并到主分支。

另外,我们需要安装 Rosetta 2 支持运行部分 x86_64 应用。注意  Rosetta 2 只支持 64 位应用,不支持 32 位应用。 参考 Does Rosetta 2 support 32-bit Intel apps?

继续阅读macOS Sonoma(14.2.1)通过UTM虚拟机编译Android 12.1源码过程总结(MacBook Pro 2023-Apple M2 Pro)

macOS Ventura(13.6)/macOS Sonoma(14.0)编译Android 10.0/Android 11.0源码过程总结(MacBook Pro 2023-Apple M2 Pro)

前置条件


  • macOS Ventura(13.6)/macOS Sonoma(14.0) MacBook Pro 2023-Apple M2 Pro (4能效核、8性能核、32GB内存、2TB磁盘)
  • Homebrew (4.0.28 或更高版本)
  • Xcode Version 15.0 (15A240d)
  • Android Studio Giraffe | 2022.3.1 Patch 1

从Intel版本MacBook Pro迁移到MacBook Pro 2023(Apple M2 Pro)的设备,参考 从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)" 重新安装 Homebrew

根据 Google 官方文档,2021年6月22日之后的Android系统版本不支持在macOS系统上构建,我们在 Applic SiliconmacOS 系统是不能直接成功构建后续版本的,但是之前的版本可以在修改编译配置后成功编译。

尝试过使用 Podman Desktop / Docker 方式进行编译、也尝试过借助 OrbStackLima 这些纯虚拟机通过安装 ubuntu 22.04 系统镜像的方式进行编译,结果都在执行 lunch 命令的时候长时间卡住,观察系统进程发现名为 nsjail 进程的 CPU 占用持续卡住在 100% 上无法继续编译,并且由于 Docker 或者虚拟机文件系统是 Linux 文件系统,而宿主机的文件系统是 AppleFS 文件系统,导致需要进行文件转换,中间的转换性能代价非常高,性能很差。

通过 UTM 虚拟机,使用 MacOS 提供的虚拟机执行编译的话,性能会更好。但是目前测试发现存在严重的文件系统缺陷,编译/大量文件复制过程中,经常出现文件系统损坏,导致编译失败。

需要在 M2 上使用UTM 虚拟机编译的,可以参考 macOS Sonoma(14.1.1)通过UTM虚拟机编译Android 11.0源码过程总结(MacBook Pro 2023-Apple M2 Pro) 通过 UTM 虚拟机借助 Rosetta 2 的方式进行编译。目前测试来看,性能勉强能用。

另外,我们需要安装 Rosetta 2 支持运行部分 x86_64 应用。注意  Rosetta 2 只支持 64 位应用,不支持 32 位应用。 参考 Does Rosetta 2 support 32-bit Intel apps?

继续阅读macOS Ventura(13.6)/macOS Sonoma(14.0)编译Android 10.0/Android 11.0源码过程总结(MacBook Pro 2023-Apple M2 Pro)

解决 Only fullscreen opaque activities can request orientation

这段时间把App的targetSDKVersion升级到了27,昨晚上线之后今早看到后台一堆崩溃,全是 Android 8.0 的设备,因为手头设备有限,测试的时候只测了Android 8.1的设备,没想到还有一个这个坑埋在这里,记录一下处理办法。

问题分析

targetSDKVersion为26或者27时,在 Android 8.0 的设备上,一些设置了windowIsTranslucent标志,将背景设为透明,同事将屏幕方向锁定的Activity,会崩溃并抛出这个异常:

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 ...

这个问题网上有很多讨论以及解决方法,问题的原因出自这里

if (ActivityInfo.isFixedOrientation(requestedOrientation) && !fullscreen 
	&& appInfo.targetSdkVersion >= O) {
	throw new IllegalStateException("Only fullscreen activities can request orientation");
}

这里做了当屏幕方向锁定了并且不为全屏并且 App 的targetSdkVersion 大于 Android O的话,就会抛出这个异常。

是否为全屏的判定如下:

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 里已经修复了。

解决方案

解决方法有如下几种:

  1. 降级targetSDKVersion到26以下(废话!!)

  2. 移除mainfest文件里的screenOrientation属性

  3. 取消Activity主题里的windowIsTranslucent属性或者windowSwipeToDismiss属性或者windowIsFloating属性(根据你设置了什么属性来具体分析)

  4. (推荐)移除manifest文件里的screenOrientation属性,并在ActivityonCreate方法里设置屏幕方向

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都是这么做的
<style name="AppTheme" parent="BaseTheme">
    <item name="android:windowBackground">@drawable/window_splash_screen_content</item>
</style>
  • 将背景设置为透明色,当点击桌面LOGO时并不会立即启动APP,而是在桌面停留一会(其实已经启动)
<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目录:

<resources>
    <style name="AppTheme" parent="BaseTheme">
        <item name="android:windowSplashscreenContent">@drawable/window_splash_screen_content</item>
    </style>
</resources>

通过windowSplashscreenContent设置的drawable资源将会覆盖在windowBackground顶部,在系统状态栏之下,如果不想受到System Bars限制,请使用全屏主题。

继续阅读Android 12 SplashScreen API以及Jetpack兼容库