Android 8/9 适配指南:通知渠道与前台服务

3 阅读4分钟

Android 8/9 适配指南:通知渠道与前台服务

Android 8.0(API 26)和 9.0(API 28)虽然版本较旧,但在存量设备中仍有相当比例。本文梳理这两个版本的适配要点,帮助开发者兼容旧设备。


一、Android 8.0(API 26)适配要点

Android 8.0 是行为变更较大的一代,以下变更在 targetSdk ≥ 26 时生效。


1.1 通知渠道(Notification Channel)【必须适配】

背景: Android 8.0 引入了通知渠道概念,应用必须创建通知渠道才能显示通知。

Manifest 声明:

<!-- Android 8.0+ 需要小型图标和颜色 -->
<meta-data
    android:name="com.google.firebase.messaging.default_notification_icon"
    android:resource="@drawable/ic_stat_notification" />
<meta-data
    android:name="com.google.firebase.messaging.default_notification_color"
    android:resource="@color/color_accent" />

创建通知渠道(兼容代码):

fun createNotificationChannels(context: Context) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        // 重要通知渠道
        val importantChannel = NotificationChannel(
            "important_channel_id",
            "重要通知",
            NotificationManager.IMPORTANCE_HIGH
        ).apply {
            description = "订单状态、支付通知等重要消息"
            enableVibration(true)
            vibrationPattern = longArrayOf(0, 100, 200, 300)
        }
        
        // 普通通知渠道
        val defaultChannel = NotificationChannel(
            "default_channel_id",
            "普通通知",
            NotificationManager.IMPORTANCE_DEFAULT
        ).apply {
            description = "系统通知、活动通知等"
        }
        
        // 静默通知渠道
        val silentChannel = NotificationChannel(
            "silent_channel_id",
            "静默通知",
            NotificationManager.IMPORTANCE_LOW
        ).apply {
            description = "日志同步、后台状态等非紧急通知"
        }
        
        val notificationManager = context.getSystemService(NotificationManager::class.java)
        notificationManager.createNotificationChannels(listOf(
            importantChannel,
            defaultChannel,
            silentChannel
        ))
    }
}

发送通知(兼容代码):

fun showNotification(context: Context, title: String, message: String) {
    val builder = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        Notification.Builder(context, "default_channel_id")
    } else {
        Notification.Builder(context)
    }
    
    builder.setContentTitle(title)
        .setContentText(message)
        .setSmallIcon(R.drawable.ic_stat_notification)
        .setAutoCancel(true)
    
    val notificationManager = context.getSystemService(NotificationManager::class.java)
    notificationManager.notify(1, builder.build())
}

⚠️ 重要提示:

  • 渠道一旦创建,用户可以在系统设置中修改渠道属性,应用无法再次修改
  • 渠道 ID 是唯一的,不要随意更换
  • 建议在应用首次启动时创建所有渠道

1.2 后台执行限制

变更说明: Android 8.0 对后台应用限制了 Service 的创建。

影响:

  • 应用处于后台时,不再允许创建后台服务
  • 需要使用 JobIntentServiceWorkManager 替代

适配方案:使用 JobIntentService

// 定义 JobIntentService
class MyBackgroundService : JobIntentService() {
    
    companion object {
        private const val JOB_ID = 1000
        
        fun enqueueWork(context: Context, work: Intent) {
            enqueueWork(context, MyBackgroundService::class.java, JOB_ID, work)
        }
    }
    
    override fun onHandleWork(intent: Intent) {
        // 在这里执行后台任务(已在独立线程中)
        val data = intent.getStringExtra("data")
        processData(data)
    }
}

// 启动任务
val intent = Intent(context, MyBackgroundService::class.java).apply {
    putExtra("data", "some data")
}
MyBackgroundService.enqueueWork(context, intent)

更好的方案:使用 WorkManager(推荐)

// 定义 Worker
class MyWorker(context: Context, params: WorkerParameters) : Worker(context, params) {
    override fun doWork(): Result {
        val data = inputData.getString("data") ?: return Result.failure()
        processData(data)
        return Result.success()
    }
}

// 调度任务
val workRequest = OneTimeWorkRequestBuilder<MyWorker>()
    .setInputData(
        Data.Builder().putString("data", "some data").build()
    )
    .build()

WorkManager.getInstance(context).enqueue(workRequest)

1.3 安装 APK 需要权限

变更说明: Android 8.0 开始,安装未知来源应用需要 REQUEST_INSTALL_PACKAGES 权限。

Manifest 声明:

<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />

请求安装权限:

fun installApk(context: Context, apkFile: File) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        if (!context.packageManager.canRequestPackageInstalls()) {
            // 请求权限
            val intent = Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES).apply {
                data = Uri.parse("package:${context.packageName}")
            }
            (context as Activity).startActivityForResult(intent, REQUEST_INSTALL_PERMISSION)
            return
        }
    }
    
    // 安装 APK
    val uri = FileProvider.getUriForFile(
        context,
        "${context.packageName}.fileprovider",
        apkFile
    )
    
    val installIntent = Intent(Intent.ACTION_VIEW).apply {
        setDataAndType(uri, "application/vnd.android.package-archive")
        addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
    }
    context.startActivity(installIntent)
}

1.4 透明 Activity 限制

变更说明: Android 8.0 不允许透明主题的全屏 Activity。

适配方案:

<!-- ❌ 错误:透明主题 + 全屏 -->
<style name="AppTheme.Transparent">
    <item name="android:windowIsTranslucent">true</item>
    <item name="android:windowFullscreen">true</item>
</style>

<!-- ✅ 正确:如果需要悬浮效果,使用 Dialog 主题 -->
<style name="AppTheme.Dialog">
    <item name="android:windowIsFloating">true</item>
    <item name="android:windowBackground">@android:color/transparent</item>
</style>

二、Android 9.0(API 28)适配要点


2.1 Apache HTTP 客户端移除

变更说明: Android 9.0 默认移除了 org.apache.http.legacy 库。

适配方案1:在 Manifest 中声明使用

<!-- AndroidManifest.xml -->
<application ...>
    <uses-library
        android:name="org.apache.http.legacy"
        android:required="false" />
</application>

适配方案2:使用 HttpURLConnection 或第三方库(推荐)

// 使用 HttpURLConnection(系统自带,无需额外依赖)
fun httpGet(urlString: String): String {
    val url = URL(urlString)
    val connection = url.openConnection() as HttpURLConnection
    connection.requestMethod = "GET"
    
    return connection.inputStream.bufferedReader().use { it.readText() }
}

// 或使用 OkHttp(推荐)
// implementation "com.squareup.okhttp3:okhttp:4.12.0"
val client = OkHttpClient()
val request = Request.Builder().url(urlString).build()
val response = client.newCall(request).execute()
val body = response.body?.string()

2.2 前台服务需要权限

变更说明: Android 9.0 开始,启动前台服务需要 FOREGROUND_SERVICE 权限。

Manifest 声明:

<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />

启动前台服务:

// 创建通知(Android 8.0+ 需要渠道)
val notification = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
    Notification.Builder(this, "default_channel_id")
        .setContentTitle("服务运行中")
        .setSmallIcon(R.drawable.ic_notification)
        .build()
} else {
    Notification.Builder(this)
        .setContentTitle("服务运行中")
        .setSmallIcon(R.drawable.ic_notification)
        .build()
}

// 启动前台服务
startForeground(1, notification)

2.3 刘海屏(Display Cutout)适配

背景: Android 9.0 正式支持刘海屏(Notch)。

获取刘海区域:

fun getDisplayCutout(rootView: View) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
        val cutout = rootView.rootWindowInsets?.displayCutout
        cutout?.let {
            val boundingRect = it.boundingRects
            // 处理刘海区域
            boundingRect.forEach { rect ->
                Log.d("Cutout", "刘海区域:$rect")
            }
        }
    }
}

使用 WindowInsets 处理刘海:

// 在 Activity 的 onCreate 中
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
    window.attributes.layoutInDisplayCutoutMode =
        WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
}

2.4 HTTPS 网络请求限制

变更说明: Android 9.0 默认禁止明文流量(HTTP 请求)。

适配方案1:启用明文流量(仅开发环境)

<!-- res/xml/network_security_config.xml -->
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
    <domain-config>
        <domain includeSubdomains="true">example.com</domain>
        <trust-anchors>
            <certificates src="user" />
        </trust-anchors>
    </domain-config>
</network-security-config>
<!-- AndroidManifest.xml -->
<application
    android:networkSecurityConfig="@xml/network_security_config"
    ... >

适配方案2:全量迁移到 HTTPS(推荐)

// 使用 HTTPS 进行网络请求
val url = "https://api.example.com/data"

三、Gradle 配置建议

// app/build.gradle
android {
    compileSdk 34
    
    defaultConfig {
        minSdk 21          // 支持 Android 5.0+
        targetSdk 34      // 紧跟最新版本
    }
    
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_17
        targetCompatibility JavaVersion.VERSION_17
    }
}

四、适配检查清单

完成 Android 8/9 适配后,建议对照以下清单进行检查:

  • targetSdk ≥ 26 时,已创建通知渠道
  • 后台任务已迁移到 WorkManagerJobIntentService
  • 安装 APK 功能已适配 REQUEST_INSTALL_PACKAGES 权限
  • 如果使用了 org.apache.http,已在 Manifest 中声明 <uses-library>
  • 前台服务已声明 FOREGROUND_SERVICE 权限
  • 刘海屏设备已测试 UI 是否被遮挡
  • 网络请求已全量使用 HTTPS(或已配置网络安全配置)
  • 在 Android 8.x 和 9.0 设备上分别测试

五、参考资源


如果本文对你有帮助,欢迎点赞收藏。如有疑问,欢迎在评论区交流。