综合
minSdk 首选 28+; 自用支持MethodHandle的26+;targetSdk 首选 28+。
Android更多开发知识专题
Android各版本统计(英文版数据较新)
Android 14起Java 17 LTS新特性:HexFormat、record 和 多行文本块儿; Android 14起访问或选取媒体文件时会多出一项“选择照片和视频”(READ_MEDIA_VISUAL_USER_SELECTED权限),避免只能全部允许的风险。 Android 13起后台Toast弹框(前台则正常弹)必须静态和动态申请POST_NOTIFICATIONS权限,故只能换用前台Service? 中国大陆手机不允许安装未备案app应用,可通过断网安装或第三方安装器解决。 Android Studio Koala起支持(新项目 -> Gemini API Starter)Gemini聊天、文生图: https://developer.android.com/studio/preview/gemini-template?hl=zh-cn Google Play Console 发布PC游戏(上架要求arm64 + x86-64) -> 设置 -> 高级设置 -> 设备规格 -> +添加“Google Play 游戏电脑版”;玩家客户端在设置中启用“Hyper-V Hypervisor”才不会卡顿。 Google Play Games 电脑版开发者模拟器 - PC端上架前测试用(打开即进入Android桌面/返回键为Ctrl+B,Home屏为Ctrl+H/用adb安装apk),右下角图标右键"Validate Game Readiness"。
工具
Android Studio开发IDE官方下载(安装后空间占用10GiB+)
Android Debug Bridge(ADB/调试桥)官方下载
Android Gradle Plugin(AGP)版本列表
JADX - Android apk文件反编译工具(请合法使用)
Android官方apk/aab文件反编译GUI工具和命令行apkanalyzer
Java Keystores (.jks or .keystore): 说明 - 证书生成的参数相同,不代表生成后的证书,在全世界都是相同的,而是依照证书指纹来决定。 生成 debug.keystore 命令(命令参考/可选参数:-sigalg SHA256withRSA -keysize 2048 / -alias值大小写均可 ) keytool -genkey -alias AndroidDebugKey -keyalg RSA -dname "CN=Android Debug,O=Android,C=US" -validity 10950 -keypass android -keystore debug.keystore -storepass android 查看证书命令(输入密钥库口令: android /有效期默认为30年*365日=10950天) C:\Progra~1\Java\jdk-22\bin\keytool -list -v -keystore C:\Users\person\.android\debug.keystore 或gradle查看signingConfigs配置块儿签名信息、证书指纹: ./gradlew signingReport 为Android程序apk文件签名(默认并存v1至当前SDK支持的所有版本签名,安卓OS校验时忽略不识别的,之后选用版本高者,可主动禁用;首选v4及支持轮替的v3 / --v4-signing-enabled only ) apksigner sign --ks debug.keystore --v1-signing-enabled false --v2-signing-enabled false app.apk 查看签名版本 apksigner verify --verbose app.apk
aab转apks并部署:参数--connected-device和--adb均为可选 java -jar E:\bundletool-all-1.15.5.jar build-apks --bundle=D:\projects\NewTileMatching.aab --output=D:\projects\NewTileMatching.aab.apks --connected-device java -jar E:\bundletool-all-1.15.5.jar install-apks --apks=D:\projects\NewTileMatching.aab.apks --adb=C:\Users\aw\AppData\Local\Android\Sdk\platform-tools\adb.exe
文章
Android核心服务、系统组件
谷歌三件套
Google Services Framework(com.google.android.gsf)
Google Play Services(com.google.android.gms)
Google Play Store
发布、测试
Google Play应用市场上架要求 Google Play开发者账号闲置规则 2024年起创建的Google Play个人账号,新增了上架条件:必须 20 名测试人员连续参与测试 14 天。 Android反射non-sdk限制分类(unsupported=允许反射) 解除限制:adb shell settings put global hidden_api_policy 1 IDE: 通过Logcat查看日志 - level:error fullchannel Android Studio默认提供了两种测试类型package:Instrumented test(插桩测试)和Unit test(单元测试) 注意 - 可通过Firebase Test Lab控制台(“运行测试”按钮下拉选类型)上传apk或命令行、API等方式执行测试; Test Lab控制台插桩测试时(gradle assembleDebugAndroidTest),第一个apk上传含主启动Activity的apk,第二个上传测试apk;否则报:"The uploaded APK does not specify a main launcher activity."。 Test Lab控制台Robo测试时(gradle assembleDebug),若还未给Release版生成签名,可先上传Debug版本apk版解决无签名报错,同时还应上传在AS上录制的Robo脚本(Tools > Firebase -> Test Lab -> Record Robo Script)。 Test Lab控制台测试前,确认下真机版本跟待测app是否兼容。 Instrumented test(插桩测试) - 引入了UI测试库Espresso但未使用; 测试命令 - [只能Debug] gradle connectedStoreDebugAndroidTest 或 connectedFullDebugAndroidTest、connectedAndroidTest、cAT app/build/reports/androidTests/connected/debug/flavors/store/com.wangxiaodong.apps.playphone.ExampleInstrumentedTest.html assembleAndroidTest app\build\outputs\apk\androidTest\store\debug\app-store-debug-androidTest.apk 测试时截图 - https://github.com/android/testing-samples/blob/main/ui/espresso/ScreenshotSample/app/src/androidTest/java/com/example/android/testing/espresso/screenshotsample/ScreenshotJavaTest.java Google Play测试:开放式测试(暴露页面)、封闭式测试、内部测试(免审核) 注意 - 交由指定测试人员进行人工测试前,会先执行自动化测试,但也能关闭该前置自动化测试(即回放Robo预录脚本的“发布前测试报告”)。 内部测试 - xxx Android Chrome浏览器日志查看: 开发者模式 -> 启用 USB调试; 打开 Android Chrome 浏览器并访问待调试的页面; 然后打开桌面 Chrome 浏览器并访问 chrome://inspect/#devices 等待数秒就看到“Redmi K30i 5G”的remote device了,点击 inspect 链接进入“DevTools”。
编程
四大基本组件:Activity、Service服务、BroadcastReceiver广播接收器、Content Provider内容提供者
生命周期:
避免创建Application且启动较早做法 - Application attachBaseContext -> 【ContentProvider onCreate】 -> Application onCreate
pkg.R类导出前后均可见,包名由applicationId决定;而BuildConfig类则只存在于编译时,导出后所用之处会变为实际值,包名由namespace/package决定;
启用构建配置常量:BuildConfig.VERSION_NAME; gradle.properties必须配上 - android.defaults.buildfeatures.buildconfig=true
多变种时不能直接用R.string访问,应反射之:ViewCommon.getStringByResName(getApplicationContext(), "google_app_id");
布局:
多页面首选简单的TabLayout(可选划切ViewPager2+FragmentStateAdapter),次选需配置xml项的BottomNavigationView(Tabs区)+NavHostFragment(内容区)
implementation("androidx.viewpager2:viewpager2:1.0.0")
注意 - Fragment布局为ConstraintLayout时会被遮挡,故ViewPager2必须设为android:layout_height="match_parent"。
ViewPager2 vp2=findViewById(R.id.vp2); vp2.setAdapter(new VP2Adapter(this)); // 或用RecyclerView.Adapter
public class VP2Adapter extends FragmentStateAdapter {
public VP2Adapter(@NonNull FragmentActivity fragmentActivity) { super(fragmentActivity); }
List list = List.of(VP2Fragment.newInstance(null, null), VP2Fragment.newInstance(null, null));
@NonNull @Override // 延迟实例化可将List.of(f...)改为在createFragment中new对象。
public Fragment createFragment(int position) { return list.get(position); }
@Override public int getItemCount() { return list.size(); }
}
联动ViewPager2和TabLayout:
TabLayout tl = findViewById(R.id.tabs);
new TabLayoutMediator(tl, vp2, (tab, position) ->
tab.setText("Tab " + (position + 1))).attach();
自定义tab视图:tab.setCustomView(view); 或 tl.getTabAt(0).setCustomView(view);
最简Fragment:
public class MiniFragment extends Fragment {
@Nullable @Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_vp2, container, false);
}
}
Android后台线程执行前台UI操作:
// 解决 Can't toast on a thread that has not called Looper.prepare()
28+ Use:getApplicationContext().getMainExecutor().execute(() -> { });
new Handler(Looper.getMainLooper()).post(() -> {//避免用Looper.prepare()
Toast.makeText(context, "...", Toast.LENGTH_SHORT).show(); });
View控件:
根View对象(ViewGroup/含标题栏) - activity.this.getWindow().getDecorView() 等同 view.getRootView();
内容视图(布局文件/不含标题栏) - activity.this.findViewById(android.R.id.content)
Toast前台替代 - Snackbar.make(view.getRootView(), "msg", Snackbar.LENGTH_SHORT).show();
FloatingActionButton(FAB/基于ImageView) - 比普通控件多一个交互动画。
通过名字获取resId:
@SuppressLint("DiscouragedApi") // 允许用但性能差,为0则无此resId。
getResources().getIdentifier("google_app_id", "string", getPackageName());
权限:
静态权限 -
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="com.google.android.gms.permission.AD_ID" />
FOREGROUND_SERVICE(前台Service)、RECEIVE_BOOT_COMPLETED
需要用户明确同意的权限 -
POST_NOTIFICATIONS(通知)
需要Google Play控制台审批的权限 -
USE_FULL_SCREEN_INTENT(闹钟、电话可在Play控制台申请默认启用)
共享存储:
高于maxSdkVersion版本的则废弃此权限:
<uses-permission
android:name="android.permission.READ_EXTERNAL_STORAGE"
android:maxSdkVersion="32" />
<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="29" />
用以下细化权限项来替代:
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
以及 READ_MEDIA_VIDEO READ_MEDIA_AUDIO
Android 11起采用了包可见性机制,默认可见应用有:adb shell dumpsys package queries
forceQueryable:标签下的apk 表示 可见所有已安装的apk
queries via package name: 标签表示只有配置了包名 才对该app可见
queries via intent: 标签表示只有配置了intent 才对该app可见
queryable via uses-library:
queryable via interaction:
应用包安装:
显式安装权限 - android.permission.REQUEST_INSTALL_PACKAGES
静默安装权限(只能用于系统应用) - android.permission.INSTALL_PACKAGES
WebView载入目录索引页会报JavaScript脚本xxx未定义而页面空白
单文件+多文件分享:
intent.getParcelableExtra(Intent.EXTRA_STREAM);
intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
解决 AlertDialog+getApplicationContext() 报错 Unable to add window -- token null is not valid; 例外:Toast可用任何上下文及getApplicationContext()
new AlertDialog.Builder(view.getContext()).setMessage("msg").create().show();// or MainActivity.this
解决横竖屏切换阻止构建 android:screenOrientation="portrait" tools:ignore="LockedOrientationActivity" tools:replace="android:screenOrientation"
Gradle构建:
android {
buildFeatures { buildConfig = true } // 启用后才会生成BuildConfig.DEBUG
defaultConfig {
// x86_64、x86仅用于模拟器调试或ChromeOS系统
ndk.abiFilters 'arm64-v8a', 'x86_64','x86' // 'armeabi-v7a','arm64-v8a','x86','x86_64'
}
buildTypes {
debug { ndk.abiFilters 'arm64-v8a', 'x86_64' }
release { //noinspection ChromeOsAbiSupport
ndk.abiFilters 'arm64-v8a' } // 只保留需要支持的系统so库
// 本地依赖库无名为other的buildType时进行降级
other { matchingFallbacks = ['debug', 'qa', 'release'] }
}
}
Android插件Gradle国内仓库:
[铁定被墙] google() 大陆镜像 https://maven.aliyun.com/repository/google 包含 com.android.application 等插件。
gradlePluginPortal() 即 https://plugins.gradle.org/m2/ 大陆镜像 https://maven.aliyun.com/repository/gradle-plugin 包含 gradle 插件(除 Android 外)。
gradlePluginPortal()改url用法 - GRADLE_OPTS环境变量值添加 -Dorg.gradle.internal.plugins.portal.url.override=https://maven.aliyun.com/repository/gradle-plugin
mavenCentral() 大陆镜像 https://maven.aliyun.com/repository/central
开机解锁前应用执行 - android:directBootAware="true"只是标记下,并不会开机启动,还应标记下注册了LOCKED_BOOT_COMPLETED的receiver。
开机解锁后应用执行 - BOOT_COMPLETED
关闭代码分析提示:
android {
lint {
abortOnError = false
checkReleaseBuilds = false
}
// ...
}
解决 Duplicate class kotlin... - implementation(platform("org.jetbrains.kotlin:kotlin-bom:1.8.20"))