Android(AOSP)编程开发
综合/最新
Android开发、Android UI、Android API Levels、Android玩机
Android系统开放源码(页首代码搜索/墙外)
新:
通过NsdManager/UPNP+TCP/UDP暴漏一个Android Service,自家App共享其已授权的能力。
综合:
Jetpack Compose 和 SwiftUI 延续了 React 框架的声明式和虚拟DOM(Diff算法/状态驱动)两大特性。
Android Studio 安装后首次启动应指定下 Custom -> Android SDK Location: F:\big\ide-as\Sdk\,可节约 15GiB 空间。
Android Studio 分为基础版和 Feature Drop 版,前者只升级IDEA底层,而后者则会在前者Beta后启动开发,并添加 Android 新特性,后者正式发布后,前者结束更新。
Android SDK Manager 已不再提供 SDK Docs 了; curl.exe https://dl.google.com/android/repository/repository2-3.xml -I
Android SDK 版本:
minSdk 指应用不能安装在低于该版本的Android OS中,与限制策略无关:
首选28+,依赖少则26+(支持MethodHandle),最小24+(因Android 8的26启动服务需要通知),或19+(PlayServices最低支持)
忽略库较高minSdk - <uses-sdk tools:overrideLibrary="com.lib1, com.x"/>
Android 9(28)+系统支持v3签名;图片动画和ImageDecoder取代BitmapFactory、BiometricPrompt生物识别框、Wi-Fi RTT室内定位、类似安全芯片TPM的StrongBox Keymaster技术
Google Play services只支持Android 4.4+/19+系统
Android 11+/30+系统支持v4签名、ControlsProviderService
targetSdk - 首选28+;29+(演示/后台无法启动Activity/但支持WorkManager调试REQUEST_DIAGNOSTICS),Google Play要求31+(后台禁止启动窗体)
指即使运行在高于其指定版本的Android OS中,依然维持老版本的调用行为,不执行高版本的限制策略,例如6.0起的显式权限申请。另外,如果想用新的特性,提升targetSdk即可。
compileSdk(仅编译期检查用)和buildToolsVersion(可忽略)对运行时没有任何影响,故取最高即可。
死记、偏门:
图标:Android 图标尺寸最低为 256x256 像素;Google Play商店网页展示图标最低要求为 32 位 PNG(含 alpha 透明层)、尺寸 512x512 像素、文件大小上限 1024 KB。
可触摸 UI 元件的标准为 48dp=72px=9mm,即一个手指能够准确舒适触摸的最小尺寸,最小可点击区域。
暂时禁用日志刷屏的应用:./adb shell pm disable-user com.google.android.apps.youtube.music
约定:java包nonui下放BroadcastReceiver和Service,ui包则放Activity。
授权通知才能Toast(前台Activity则永远能弹Toast) - NotificationService Error: Suppressing toast from package app.name by user request.
录屏排除悬浮窗 -
Android应用层方案:layoutParams.setTitle("已豁免的厂商录屏App悬浮窗口名")。
Android系统层方案:设置 LayoutParams.FLAG_SECURE 后系统默认会用黑色填充,直接将宽高归零不就录不上了;
surfaceflinger/LayerFE.cpp 中 if(blackOutLayer) 的 prepareClearClientComposition(...) 增加 layerSettings.geometry.boundaries = FloatRect(0.f, 0.f, 0.f, 0.f);
或 surfaceflinger/CompositionEngine/src/Output.cpp rebuildLayerStacks(...)
全局异常处理:
Application onCreate() - Thread.setDefaultUncaughtExceptionHandler(new UEH());
public class UEH implements Thread.UncaughtExceptionHandler {
Thread.UncaughtExceptionHandler defaultHandler;
public UEH() { defaultHandler = Thread.getDefaultUncaughtExceptionHandler();}
@Override public void uncaughtException(@NonNull Thread t, @NonNull Throwable e) {
if (defaultHandler != null) { defaultHandler.uncaughtException(t, e); }
} // 也可交回默认异常处理器
}
进程生命周期:
⬇前台进程(foreground process/oom_adj=0) - 有Activity界面直接交互的情况。
⬇可见进程(visible process) - 存在执行了Service.startForeground()的Service,则处于该状态;同时,还可通过startService()嵌套调startService()。
⬇服务进程(service process) - 如果有通过startService()启动的Service,则处于该状态,但在“无可见UI+无前台服务”数分钟后会强制降级为“缓存进程”,若在该Service里嵌套调startService()会报“Not allowed to start service Intent”,在降级前嵌套调startForegroundService()则不会报错;bindService()方式则不受影响;SYSTEM_ALERT_WINDOW+可见悬浮窗也可后台调startForegroundService()。
⬇缓存进程(cached process) - 降级为该状态后,系统会主动触发stopService();内存不足后会先拿其开刀,但若被其他app调起,则会提升至上方进程状态。
无进程 - 即强制停止后的IMPORTANCE_GONE状态。
查看PID: ./adb shell pidof com.openle.v1app.social.fullchannel
查看优先级: ./adb shell cat /proc/[PID]/oom_adj
前台服务(Foreground Services/FGS)任务管理器:下拉顶部通知栏,再二阶段下拉就能看到左下角的前台服务列表了,若用户在此点了“Stop”会连带整个应用关闭,但与“强制停止”不同的是,闹钟和WorkManager不会失效。
[似乎已废弃]前台服务在24小时内连续运行20小时以上,会弹通知引起用户注意(“system-notification-long-running-fgs”),抑制1像素保活方案。
系统源码添加白名单 - com.android.server.am.OomAdjuster、ProcessList.setOomAdj?
开发者选项的“不保留活动”开启后,会导致进入二级页面时,按Home键就会销毁MainActivity了,二级页面再finish()就找不到MainActivity了,故时间充裕的话,应测测看:
判断后提示用户关掉:Settings.Global.getInt(getContentResolver(), Settings.Global.ALWAYS_FINISH_ACTIVITIES, 0);
Android虚拟机:
Android Runtime(ART)虚拟机取代了早期的Dalvik虚拟机,但均使用由d8转换后的DEX字节码格式(*.dex或*.aar),无法识别纯Java编译打包的*.class或*.jar文件。
Java转Dex命令:d8 MyProject/app/build/intermediates/classes/debug/*/*.class 或 d8 java.jar --output out.dex
动态载入及反射调用:DexClassLoader对象。
内容
Android BroadcastReceiver:
开机广播:
说明 - [targetSdk = 34 亲测]能启动部分前台服务,但只有授权通知后,才允许Toast和显示“前台服务运行通知”;[未测]起个悬浮窗是否能Toast?
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<receiver android:name=".nonui.BootReceiver" android:enabled="true" android:exported="true">
<intent-filter><action android:name="android.intent.action.BOOT_COMPLETED" /></intent-filter>
</receiver>
Android Service:
前台服务:
说明 - Activity启动的前台服务会弹出“前台服务运行通知”,但BOOT_COMPLETED则必须再授予通知权限后才会通知,不授权则只是不通知,而不会影响该服务运行。
短期服务(android:foregroundServiceType="shortService") - 3分钟后系统会触发onTimeout(int startId)来stopSelf(),优势是支持在 BOOT_COMPLETED 内启动,且不需要上架Google Play的人工审核。
调起Android系统内置邮箱应用: startActivity(new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_APP_EMAIL).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
Android网络广播默认禁用,需要WifiManager.createMulticastLock(String tag)来启用。
存储:
常用目录 - getCacheDir()、getFilesDir()。
临时写入文件:
try (var fos = new FileOutputStream(File.createTempFile("x".repeat(3), ".txt",
new File(System.getProperty("java.io.tmpdir")))); // 即 getCacheDir(),不提供则由系统决定。
var ch = fos.getChannel()) { // 取代 InputStream/OutputStream;返回写入字节数。
ch.write(ByteBuffer.wrap("Hi".getBytes()));
} catch (IOException e) { }
通过 Environment.DIRECTORY_PICTURES 可获取用户更改的默认图片存储位置,实际路径通常是: /storage/emulated/0/Pictures/;但应首选非写死的 ContentUri。
contentValues.put(MediaStore.Images.ImageColumns.RELATIVE_PATH, Environment.DIRECTORY_PICTURES); // 指定约定目录或子目录;或 put(MediaStore.Images.Media.RELATIVE_PATH, "Pictures/subDir")
保活:
Android一像素Activity进程保活
15分钟区间保活:WorkManager+闹钟
ROOT保活:
./adb root
./adb remount
./adb push system/priv-app/folder_name/ apk_path
./adb shell sync
./adb reboot
进程活跃度受限:
var usm = (UsageStatsManager) getSystemService(Context.USAGE_STATS_SERVICE);
var asb = usm.getAppStandbyBucket(); // ./adb shell am get-standby-bucket PACKAGE_NAME
if (asb > UsageStatsManager.STANDBY_BUCKET_ACTIVE) {
//应用可能受限
System.out.println(asb);
}
|
FGS 任务管理器 |
向上滑动 |
强行停止 |
立即从内存中移除应用 |
✔ |
|
✔ |
停止媒体播放 |
✔ |
|
✔ |
停止 FGS/移除关联的通知 |
✔ |
|
✔ |
移除 activity 返回堆栈 |
✔ |
✔ |
✔ |
从历史记录中移除应用 |
|
✔ |
✔ |
取消预定作业 |
|
|
✔ |
取消闹钟 |
|
|
✔ |
UI
资源对象:
格式 - PixelFormat.RGBA_8888(系统默认) 比 RGB_888 多一个Alpha透明通道位;RGBA_8888 bytes排列顺序为byte[0]=R、byte[1]=G、byte[2]=B、byte[3]=A、byte[4]=R...。
android.media.Image - 图像原始数据和格式信息,通常为 YUV, PNG, RAW, RGBA; RGBA_8888 只用到了首个[0]平面 xImage.getPlanes()[0].getBuffer();
BMP格式规定按行进行四字节对齐 - 补byte255:逐像素(RGBA) plane.getPixelStride(); 整行(width即宽度) plane.getRowStride(); 32 位图像(pixelStride=4)可以忽略 stride;后续研究 如果指定了与RowStride不能整除的width,是否要额外处理!?
android.graphics.Bitmap - 已解码图像,通常为 RGB/PixelFormat.RGBA_8888 格式,无法区分PNG or WEBP格式了;比Image更适用于资源的设置。
android.graphics.drawable.Drawable - 位图、矢量图容器
图片存储:
var file = File.createTempFile(UUID.randomUUID().toString(), ".png", getCacheDir());
try (var fos = new FileOutputStream(file)) {
bitmap.compress(Bitmap.CompressFormat.PNG, 100, fos);
bitmap.recycle();
}
可视化:
Spinner:
Containers -> Spinner 拖拽至布局中,然后 Attributes -> entries -> 选择现有数组名 或 ➕Array Resource File -> File name:arr-res
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string-array name="str_arr"><item>1</item><item>2</item></string-array>
</resources>
取选中值: s.getSelectedItem().toString();
其他
华为鸿蒙系统(基于AOSP)
mDNS:
根据树莓派服务类型检索网络地址:nsdManager.discoverServices("_workstation._tcp.",...);
触发onServiceFound入参判断nsdServiceInfo.getServiceName().startsWith("raspberrypi")
树莓派注册的服务类型:nsdServiceInfo.setServiceType("_workstation._tcp.");
解决录屏1分钟左右报: [ImageReader-102x223f1m60-3963-0](id:f7b00000003,api:1,p:1052,c:3963) dequeueBuffer: BufferQueue has been abandoned
将 ImageReader 或 SurfaceTexture 变量从方法内提升为类级别,就不会被GC很快回收了。