从此
📄文章 #️⃣专题 🌐上网 📺 🛒 📱

Android OS系统和应用开发


综合

MinSDK - 首选支持MethodHandle的26+。 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

工具

Android Studio开发IDE官方下载(安装后空间占用10GiB+)

Android Debug Bridge(ADB)官方下载

Android Gradle Plugin(AGP)版本列表

Android apk文件反编译工具(请合法使用)
Android官方apk/aab文件反编译GUI工具和命令行apkanalyzer

bundletool官方下载

  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产品展示


Android核心服务、组件

谷歌三件套

Google Services Framework(com.google.android.gsf)

Google Play Services(com.google.android.gms)

Google Play Store


发布、测试

  Google Play应用市场上架要求 Google Play开发者账号闲置规则
  Android反射non-sdk限制分类(unsupported=允许反射) 解除限制:adb shell settings put global hidden_api_policy  1


  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

编程


  布局:
    多页面首选简单的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()).create();// 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'] } } } 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 } } } } 开机解锁前应用执行 - android:directBootAware="true"只是标记下,并不会开机启动,还应标记下注册了LOCKED_BOOT_COMPLETED的receiver。 开机解锁后应用执行 - BOOT_COMPLETED 解决 Duplicate class kotlin... - implementation(platform("org.jetbrains.kotlin:kotlin-bom:1.8.20"))


厂商开源分支、Bootloader

原则 - 设备是消费者花钱购买的,解锁Bootloader是基本权利,欺骗用户支持解锁但购买后卡关,尽量少买这种诈骗厂家的手机。

一加

描述
...
内容
  • 无条件支持解锁Bootloader
  • ...

小米

描述
...
内容
  • 小米开源内核
  • 【变相不允许解锁】解锁Bootloader - 需要5级社区等级(发帖约半年才能达到)并通过社区的bl解锁申请(Android编程考题要满分)。

其他

  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,接着登录小米账号,最后按提示操作。