2025年的第一篇Android適配,比以往來的更晚一些。廢話不多說,我們開始!!
準備工作
首先將我們項目中的 targetSdk
和compileSdk
升至 35。
- 推薦使用Android Studio Koala Feature Drop | 2024.1.2或更高版本。
- AGP版本最低升級到8.3.0
plugins {id 'com.android.application' version '8.3.0' apply false
}
影響Android 15上所有應用
1.最低可安裝的目標 API 級別提升
Android 15要求應用的targetSDK最低為24,否則無法安裝。這塊和之前Android 14一樣,以后每年應該都會加1,所以注意提前處理,避免新系統無法安裝。
2.16KB頁面大小支持
影響范圍
使用任何NDK庫的應用都需要重新編譯以支持16KB頁面大小的設備。這點之前還是默認開啟的,后面調整為了不強制適配。但是自 2025 年 11 月 1 日起,提交到 Google Play 且以 Android 15 及更高版本的設備為目標平臺的所有新應用和現有應用更新都必須支持 64 位設備上的 16 KB 頁面大小。
它帶來的性能提升有以下幾點:
-
應用啟動時間平均縮短3.16%,部分應用可達30%
-
應用啟動時耗電量平均減少4.56%
-
相機啟動速度:熱啟動平均加快4.48%,冷啟動平均加快6.60%
-
系統啟動時間平均改善8%
適配步驟
-
升級AGP版本到8.3或更高,推薦AGP 8.5.1 或更高版本。
-
使用16KB ELF對齊編譯應用
-
檢查引用特定頁面大小的代碼
具體操作,參考官方文檔。
我們是做出海項目的,所以比較關注這塊,目前使用到的三方sdk都有支持。比如我們項目中使用了MMKV,雖然MMKV 2.x版本支持16KB頁面大小,但不支持32位,所以還不能直接升級使用。1.3.x版本支持32位,但不支持16KB頁面大小。作者開始也明確不會支持,本來還打算自己修改重新編譯。好在前一陣谷歌有了上架的限制,作者也對MMKV 1.3.x版本做了相關適配支持,這里也是表示感謝。
3.當用戶強制停止應用時,widget被停用
如果用戶在搭載 Android 15 的設備上強制停止應用,系統會暫時停用該應用的所有widget。這些 widget 會灰顯,用戶無法與其互動。這是因為從 Android 15 開始,當系統強制停止應用時,會取消應用的所有待處理 intent
。
系統會在用戶下次啟動應用時重新啟用這些微件。
影響以Android 15或更高版本為目標平臺的應用
以下都是影響targetSDK >= 35的應用,適配時需要重點關注
1.edge-to-edge
首先說個影響面比較大的,那就是edge-to-edge,翻譯過來就是邊到邊,應用的顯示區域延伸到了全屏,不會避開狀態欄/導航欄區域。
首先狀態欄/手勢導航條區域默認將透明,三鍵導航默認不透明度為80%。
比如我之前都會給布局添加android:fitsSystemWindows="true"
和設置狀態欄顏色進行沉浸狀態欄的適配,但是由于Android 15狀態欄現在變為透明,且setStatusBarColor
和 R.attr#statusBarColor
已廢棄,對 Android 15 沒有任何作用。所以出現了下面的情況:
狀態欄透明,因此顏色為頁面的背景色,和標題欄顏色不同。
未添加android:fitsSystemWindows="true"
的布局,底部按鈕繪制在系統導航欄后面。
適配方法:
簡單粗暴的方法就是停用edge-to-edge,可以創建values-v35
,在theme中添加windowOptOutEdgeToEdgeEnforcement
并設為true:
<resources xmlns:tools="http://schemas.android.com/tools"><style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">...<item name="android:windowOptOutEdgeToEdgeEnforcement">true</item></style></resources>
不過windowOptOutEdgeToEdgeEnforcement
在以Android 16為目標平臺的應用將不再生效,無法停用edge-to-edge。所以這個只是緩兵之計,如果你的適配工作量比較大且緊急,可以使用此方法。
精細化適配方法,首先注意兩點:
setStatusBarColor
在Android 15上不生效。setNavigationBarColor
只針對三鍵導航欄有效果,手勢導航條無效。
如果你使用Compose的話:
- 使用Material 3 組件(androidx.compose.material3),例如
TopAppBar
、BottomAppBar
和NavigationBar
,這些組件不會受到影響,因為它們會自動處理insets
。 - 使用Material 2 組件(androidx.compose.material),或者自定義的 Composables,這些組件不會自動處理
insets
。需要自己設置padding
。
非compose應用:
- 在應用布局中添加
android:fitsSystemWindows="true"
,但是需要檢查狀態欄顏色是否符合UI要求。 - 未添加
android:fitsSystemWindows="true"
的布局,相信大多數都處理了狀態欄,所以需要檢查頁面底部在系統導航欄后面的情況。同時查看導航欄顏色是否符合UI要求。
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.VANILLA_ICE_CREAM) {ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.root), new OnApplyWindowInsetsListener() {@NonNull@Overridepublic WindowInsetsCompat onApplyWindowInsets(@NonNull View v, @NonNull WindowInsetsCompat insets) {Insets statusBarInsets = insets.getInsets(WindowInsetsCompat.Type.statusBars());Insets navigationBarInsets = insets.getInsets(WindowInsetsCompat.Type.navigationBars());v.setPadding(navigationBarInsets.left,statusBarInsets.top, // 如果之前處理了狀態欄,可以改為0navigationBarInsets.right,navigationBarInsets.bottom);return insets;}});
}
2.前臺服務相關變更
前臺服務超時限制
系統會限制某些前臺服務在應用處于后臺時允許運行的時長。目前,此限制僅適用于 dataSync
和 mediaProcessing
前臺服務類型。
限制規則:
-
24小時內總共只能運行6小時
-
達到限制后,系統調用
Service.onTimeout(int, int)
方法,服務將不再被視為前臺服務。 -
服務有幾秒鐘時間調用
Service.stopSelf()
,如果服務未調用,系統會拋出內部異常。 -
用戶將應用打開到前臺可重置計時。再開一個
dataSync
時間也不會重新計時。
適配代碼參考:
public class MyDataSyncService extends Service {@Overridepublic int onTimeout(int startId, int fgsType) {Log.w("ForegroundService", "服務超時,類型: " + fgsType);// 實現onTimeout方法,同時必須在幾秒內調用stopSelf()stopSelf();return super.onTimeout(startId, fgsType);}@Overridepublic IBinder onBind(Intent intent) {return null;}
}
如果24小時內已經運行了 6 個小時,則無法啟動另一個dataSync
前臺服務,系統會拋出ForegroundServiceStartNotAllowedException
,并顯示類似“前臺服務類型 dataSync 的時間限制已用完”的錯誤消息。
public class MainActivity extends AppCompatActivity {private void startDataSyncService() {Intent serviceIntent = new Intent(this, MyDataSyncService.class);try {if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {startForegroundService(serviceIntent);} else {startService(serviceIntent);}} catch (ForegroundServiceStartNotAllowedException e) {Log.e("Service", "前臺服務啟動被限制: " + e.getMessage());// 使用WorkManager等替代方案scheduleWorkWithWorkManager();}}private void scheduleWorkWithWorkManager() {// 使用WorkManager作為替代方案OneTimeWorkRequest workRequest = new OneTimeWorkRequest.Builder(DataSyncWorker.class).setConstraints(new Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED).build()).build();WorkManager.getInstance(this).enqueue(workRequest);}
}
BOOT_COMPLETED廣播接收器限制
在啟動 BOOT_COMPLETED
廣播接收器方面存在新限制前臺服務。BOOT_COMPLETED
接收器不能啟動以下類型的前臺服務:
- dataSync
- camera
- mediaPlayback
- phoneCall
- mediaProjection
- microphone(自 Android 14 起,microphone 就受到此限制)
如果 BOOT_COMPLETED
接收器嘗試啟動任何上述類型的前臺 服務,系統會拋出ForegroundServiceStartNotAllowedException
。
public class BootReceiver extends BroadcastReceiver {@Overridepublic void onReceive(Context context, Intent intent) {if (Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) {// ? 不能在BOOT_COMPLETED中啟動這些類型的前臺服務// Intent serviceIntent = new Intent(context, DataSyncService.class);// context.startForegroundService(serviceIntent);// ? 可以使用WorkManager調度任務實現你的需求OneTimeWorkRequest workRequest = new OneTimeWorkRequest.Builder(BootWorker.class).setInitialDelay(5, TimeUnit.SECONDS).build();WorkManager.getInstance(context).enqueue(workRequest);}}
}
SYSTEM_ALERT_WINDOW權限限制
持有SYSTEM_ALERT_WINDOW
權限的應用需要有可見的overlay窗口才能啟動前臺服務,也就是說,應用需要先啟動 TYPE_APPLICATION_OVERLAY
窗口,并且該窗口需要處于可見狀態,然后您才能啟動前臺服務。否則系統會拋出 ForegroundServiceStartNotAllowedException
。
public class OverlayService extends Service {private View overlayView;private WindowManager windowManager;@Overridepublic void onCreate() {super.onCreate();createOverlayWindow();}private void createOverlayWindow() {windowManager = (WindowManager) getSystemService(WINDOW_SERVICE);overlayView = LayoutInflater.from(this).inflate(R.layout.overlay_layout, null);WindowManager.LayoutParams params = new WindowManager.LayoutParams(WindowManager.LayoutParams.WRAP_CONTENT,WindowManager.LayoutParams.WRAP_CONTENT,WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY,WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,PixelFormat.TRANSLUCENT);windowManager.addView(overlayView, params);// 檢查窗口可見性overlayView.setOnWindowVisibilityChangedListener(new View.OnWindowVisibilityChangeListener() {@Overridepublic void onWindowVisibilityChanged(int visibility) {if (visibility == View.VISIBLE) {// 可見時才可以安全啟動前臺服務startForegroundServiceSafely();}}});}private void startForegroundServiceSafely() {if (overlayView != null && overlayView.getWindowVisibility() == View.VISIBLE) {try {Intent serviceIntent = new Intent(this, MyForegroundService.class);startForegroundService(serviceIntent);} catch (ForegroundServiceStartNotAllowedException e) {Log.e("Service", "前臺服務啟動失敗: " + e.getMessage());}}}
}
3.提高 intent 安全性
Intent
必須有``action`- 與目標
intent
過濾器匹配
Intent intent = new Intent(context, targetClass);
// 確保Intent有action
intent.setAction(Intent.ACTION_VIEW);
try {context.startActivity(intent);
} catch (ActivityNotFoundException e) {Log.e("Intent", "無法啟動Activity", e);
}
4.OpenJDK API 變更
Android 15 繼續推進 Android 核心庫的現代化,以與最新 OpenJDK LTS 版本(OpenJDK 17)的功能保持一致。這些變更可能會影響針對 Android 15(API 級別 35)的應用兼容性,特別是在字符串格式化、集合處理和隨機數生成等方面。
字符串格式化 API 變更
Android 15 對以下字符串格式化 API 的參數索引、標志、寬度和精度驗證變得更加嚴格:
-
String.format(String, Object[])
-
String.format(Locale, String, Object[])
-
Formatter.format(String, Object[])
-
Formatter.format(Locale, String, Object[])
使用參數索引 0 時會拋出異常:
// 錯誤示例 - 會拋出異常
String result = String.format("%0s", "test");
// IllegalFormatArgumentIndexException: Illegal format argument index = 0// 正確做法 - 使用索引 1
String result = String.format("%1$s", "test");// 或者不使用索引
String result = String.format("%s", "test");
Arrays.asList(…).toArray() 的組件類型變更
使用 Arrays.asList(…).toArray() 時,生成的數組的組件類型現在是 Object,而不是底層數組元素的類型。因此,以下代碼會拋出 ClassCastException:
String[] elements = (String[]) Arrays.asList("a", "b", "c").toArray();
適配方案
- 使用類型安全的方法‘
// 正確做法 - 指定數組類型
List<String> list = Arrays.asList("a", "b", "c");
String[] array = list.toArray(new String[0]);
- 使用 List.of()(推薦)
// 使用 Java 9+ 的 List.of()
List<String> list = List.of("a", "b", "c");
String[] array = list.toArray(new String[0]);
語言代碼處理方式變更
使用 Locale
API 時,希伯來語、意第緒語和印度尼西亞語的語言代碼不再轉換為已廢棄的形式(希伯來語:iw、意第緒語:ji、印度尼西亞語:in)。為其中一種語言區域指定語言代碼時,請改用 ISO 639-1 中的代碼(希伯來語:he、意第緒語:yi、印度尼西亞語:id)。
例如:
舊文件夾:
- values-iw/(希伯來語)
- values-ji/(意第緒語)
- values-in/(印度尼西亞語)新文件夾:
- values-he/(希伯來語)
- values-yi/(意第緒語)
- values-id/(印度尼西亞語)
其他還有PDF的優化,HDR 余量控制,請求音頻焦點的限制等變更。因為我實際也沒有適配,這里就不說明了,有需要的可以查看官方文檔。
參考
- Android 15 功能和變更列表