安卓手机移动端 - Android OS应用开发、Android Studio IDE编程技术
工具
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 Studio在启用了Hyper-V组件时会走Windows Hypervisor Platform (WHPX),未启用则走Android Emulator Hypervisor Driver (AEHD)。 显式Intent: var explicitIntent = new Intent("com.example.action.APP_ACTION") explicitIntent.setPackage(context.getPackageName()); context.startActivity(explicitIntent); 隐式Intent: 即使是内部组件,也要导出android:exported="true",否则报ActivityNotFoundException context.startActivity(new Intent("com.example.action.APP_ACTION")); 待定Intent: PendingIntent - 即达到特定条件才执行的Intent,通知、闹钟等。 必须携带 PendingIntent.FLAG_IMMUTABLE(防修改) 或 FLAG_MUTABLE 标志。 服务: bindService(Intent)会跟随上下文消亡(用于AIDL),而startService(Intent)则会持续运行,可同时调用。 权限: 普通权限直接xml声明即可;危险权限需要代码动态申请 特殊权限则需要Intent请求 - SYSTEM_ALERT_WINDOW或WRITE_SETTINGS 手动开启应用权限:设置》应用》权限》勾上麦克风等 依赖库权限合并情况 - build\intermediates\manifest_merge_blame_file\ 删除第三方权限 - <uses-permission android:name="android.permission.X" tools:node="remove" />;谷歌gms和firebase库依赖的datatransport均引入了网络访问权限ACCESS_NETWORK_STATE。 获取xml声明的权限列表(即<uses-permission />) - pm.getPackageInfo(pkg, PackageManager.GET_PERMISSIONS).requestedPermissions; 授权框 - com.google.android.permissioncontroller/com.android.permissioncontroller.permission.ui.GrantPermissionsActivity 重置app权限状态(首次和二次拒绝) - ./adb shell pm clear-permission-flags com.example.myapplication android.permission.CAMERA user-set user-fixed 说明 - AS IDE权限检查的抑制注解:@SuppressLint("MissingPermission") 改RequestPermission为RequestMultiplePermissions可同时请求多个权限 运行时权限 - Protection level: dangerous 标识了normal的安装时即获取 https://developer.android.com/reference/android/Manifest.permission 注意 - 需要动态授权的项目,比如TakePicturePreview不能直接launch(null),必须外层用RequestPermission包裹下。 实例: 说明 - Android 12、15 授权框必须后退才会dismiss(),Android 14 则失去焦点即触发dismiss()。 ActivityResultCallback<Boolean> arc = isGranted -> { System.out.println("isGranted - " + isGranted); if (isGranted) { /* 做业务 */ } else { System.out.println("拒绝"); // dismiss()也会进入。 } }; ActivityResultLauncher<String> arl = registerForActivityResult( new ActivityResultContracts.RequestPermission(), arc); // [弹框检测方案]预热下授权框,避免设备重启后首次载入较慢;将未定义权限名请求加入onCreate: ActivityCompat.requestPermissions(this, new String[]{UUID.randomUUID().toString()}, Integer.MAX_VALUE); // 上方代码声明为本类全局,下方则放调用处。 var p = android.Manifest.permission.CAMERA; if (ContextCompat.checkSelfPermission(this, p) == PackageManager.PERMISSION_GRANTED) { arc.onActivityResult(true); // 授权后代码归并至一处 } else if (this.shouldShowRequestPermissionRationale(p)) { arl.launch(p); // First Time Deny } else { System.out.println("First Time Launch and Second Time Deny - else"); arl.launch(android.Manifest.permission.CAMERA); new Handler().postDelayed(() -> { // 为true则指已拒绝2次不再弹框,为false则由授权框dismiss()触发。 // or 回调快慢方案 time = System.currentTimeMillis(); (System.currentTimeMillis() - time)<400; if (notShownGrantPermissionsActivity(MainActivity.this)) { new AlertDialog.Builder(this).setMessage("[扫码]功能需要摄像头拍照"+"\n\uD83D\uDE4F[CAMERA]") .setPositiveButton("✅ 开始授权", (di, i) -> PermissionCommon.toAppSettings(MainActivity.this)) .setNegativeButton("\uD83D\uDEAB 拒绝授权(功能受限)", (di, i) -> Toast.makeText(this, "仅基本功能", Toast.LENGTH_SHORT).show()).show(); } }, 250); } public static boolean notShownGrantPermissionsActivity(Activity activity) { boolean dialogIsHide = false; var activityManager = (ActivityManager) activity.getSystemService(Context.ACTIVITY_SERVICE); var infos = activityManager.getRunningTasks(Integer.MAX_VALUE); for (var info : infos) { if (info.id == activity.getTaskId()) { Log.d("TaskInfo", "Found task: " + info.topActivity); dialogIsHide = activity.getComponentName().equals(info.topActivity); } } return dialogIsHide; } try { var packageInfo = getPackageManager().getPackageInfo(getPackageName(), PackageManager.GET_PERMISSIONS); var ps = packageInfo.requestedPermissions; // 静态权限列表,非runtime权限。 System.out.println("permissions - " + ps); if (ps != null) { for (var permission : ps) { var permissionInfo = pm.getPermissionInfo(permission, PackageManager.GET_META_DATA); System.out.println("permissionInfo - " + permissionInfo); // 获取 flag 值,可能需要反射 int flags = permissionInfo.flags; // 根据 flags 值判断权限状态 // PackageManager.java final int FLAG_PERMISSION_USER_SET = 1 << 0; final int FLAG_PERMISSION_USER_FIXED = 1 << 1; System.out.println("flags - " + flags); if ((flags & FLAG_PERMISSION_USER_FIXED) != 0) { // 用户已明确拒绝权限 System.out.println("用户已明确拒绝权限"); } else if ((flags & FLAG_PERMISSION_USER_SET) != 0) { // 用户曾手动设置过该权限 System.out.println("用户曾手动设置过该权限"); } } } } catch (PackageManager.NameNotFoundException e) { e.printStackTrace(System.err); } 全屏Intent: if (notificationManager.canUseFullScreenIntent()) { notificationManager.notify(5, notificationBuilder.build()) } else { // Android 14+ 打开全屏Intent设置页 val intent = Intent(Settings.ACTION_MANAGE_APP_USE_FULL_SCREEN_INTENT) intent.data = Uri.fromParts("package", requireActivity().packageName, null) startActivity(intent) } 传参:intent.putExtra("intentObj", intentAsParcelable); // 会隐式转换为Parcelable类型。 首次触发用onCreate,回发则用: @Override protected void onNewIntent(@NonNull Intent intent) { super.onNewIntent(intent); setIntent(intent); // 不set则仍为onCreate传入intent。 System.out.println("onNewIntent"); } Android 13+ 支持每个应用有自己的语言设置窗口和API配置。 WIFI连接adb: 扫码字样为 WIFI:T:ADB;S:studio-UTHN-m^l5P;P:*r!2Sr>-Mq>!;; 组件: Android Activity 任务栈启动模式(Launch Mode) Android Studio IDEA Marketplace 插件: 列出任务栈和Activity顺序 - https://plugins.jetbrains.com/plugin/22717-android-activity-back-stack-viewer AdMob for Android Plugin: // AdMob task processFullDebugGoogleServices: No matching client found for package name afterEvaluate { project.tasks.forEach { task -> if (task.name.startsWith("processFull") && task.name.endsWith("GoogleServices") ) { println("Skip " + task.name + " task.") task.onlyIf { return@onlyIf false } } } }
音频
播放系统声音: var r = RingtoneManager.getRingtone(this, RingtoneManager.getActualDefaultRingtoneUri(this,RingtoneManager.TYPE_NOTIFICATION)); r.setLooping(false); r.play(); // getActualDefaultRingtoneUri 已取代 getDefaultUri 播放音频文件: // 常量FLAG_AUDIBILITY_ENFORCED使声音高分贝;不支持进度 var aa = new AudioAttributes.Builder().setFlags(AudioAttributes.FLAG_AUDIBILITY_ENFORCED).build(); // Android 12 以前为 "/system/media/audio/ui/camera_click.ogg" new AsyncPlayer(null).play(this, Uri.parse("/product/media/audio/ui/camera_click.ogg"), false, aa);
截图录屏
Android 14+ 起每次必须弹框,且不能重用mp实例再次调createVirtualDisplay(...): // MediaProjection 对象未调用 stop() 则录像图标就会一直显示在系统状态栏。 var mp = mpm.getMediaProjection(Activity.RESULT_OK, (Intent) data); mp.createVirtualDisplay("ScreenShot", ir.getWidth(), ir.getHeight(), getResources().getDisplayMetrics().densityDpi, DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR, ir.getSurface(), null, null); // Android 14+ 必须注册该回调: mp.registerCallback(new MediaProjection.Callback() { @Override public void onStop() { System.out.println("MediaProjection.Callback onStop"); } }, null);
其他
android.os.NetworkOnMainThreadException解决:new Thread(Runnable).start(); Only the original thread that created a view hierarchy can touch its views.解决:handler.post(Runnable); 单文件+多文件分享: <intent-filter> <action android:name="android.intent.action.SEND" /> intent.getParcelableExtra(Intent.EXTRA_STREAM); <action android:name="android.intent.action.SEND_MULTIPLE" /> intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM); <category android:name="android.intent.category.DEFAULT" /> <data android:mimeType="image/*" /> </intent-filter> Android衍生: 鸿蒙(HarmonyOS/OpenHarmony)应用开发工具 - HUAWEI DevEco Studio 疑虑app列表: MIUI优化、反诈组件?(com.miui.guardprovider) https://www.v2ex.com/t/982068#reply9 卸载重装 adb shell cmd package install-existing com.miui.guardprovider 小米、红米手机设备解锁:https://www.miui.com/unlock/ 首先下载PC版“解锁工具”,然后连电脑USB,接着登录小米账号,最后按提示操作。