如何用Kotlin在Android Studio开发带图库背景与音乐控制的AOD应用?
开发自定义Always On Display(AOD)应用(Kotlin + Android Studio)
一、权限配置
首先在AndroidManifest.xml中添加必要权限,确保应用能正常运行:
<uses-permission android:name="android.permission.WAKE_LOCK" /> <uses-permission android:name="android.permission.POST_NOTIFICATIONS" /> <uses-permission android:name="android.permission.READ_MEDIA_IMAGES" android:minSdkVersion="33" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" /> <uses-permission android:name="android.permission.ACCESS_NOTIFICATION_POLICY" /> <!-- 声明前台服务 --> <service android:name=".AodService" android:foregroundServiceType="specialUse" />
二、基础AOD界面实现
通过前台服务+悬浮窗实现锁屏状态下的AOD显示,创建AodService.kt:
class AodService : Service() { private lateinit var windowManager: WindowManager private lateinit var aodView: View private lateinit var sharedPrefs: SharedPreferences private val screenReceiver = object : BroadcastReceiver() { override fun onReceive(context: Context?, intent: Intent?) { when (intent?.action) { Intent.ACTION_SCREEN_OFF -> aodView.visibility = View.VISIBLE Intent.ACTION_SCREEN_ON -> aodView.visibility = View.GONE } } } override fun onCreate() { super.onCreate() sharedPrefs = getSharedPreferences("AOD_PREFS", MODE_PRIVATE) windowManager = getSystemService(WINDOW_SERVICE) as WindowManager // 加载AOD布局 aodView = LayoutInflater.from(this).inflate(R.layout.layout_aod, null) // 配置悬浮窗参数(适配锁屏层级) val params = WindowManager.LayoutParams( WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.MATCH_PARENT, if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY else WindowManager.LayoutParams.TYPE_SYSTEM_ALERT, WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED or WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON or WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON, PixelFormat.TRANSLUCENT ) windowManager.addView(aodView, params) // 启动前台服务(避免被系统回收) createNotificationChannel() startForeground(1, buildNotification()) // 监听屏幕状态 registerReceiver(screenReceiver, IntentFilter().apply { addAction(Intent.ACTION_SCREEN_OFF) addAction(Intent.ACTION_SCREEN_ON) }) // 加载自定义背景 loadCustomBackground() // 初始化音乐控制 initMusicControls() } private fun createNotificationChannel() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { val channel = NotificationChannel( "AOD_CHANNEL", "Always On Display", NotificationManager.IMPORTANCE_LOW ) val notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager notificationManager.createNotificationChannel(channel) } } private fun buildNotification(): Notification { return Notification.Builder(this, "AOD_CHANNEL") .setContentTitle("AOD 运行中") .setSmallIcon(R.drawable.ic_aod) .build() } override fun onDestroy() { super.onDestroy() windowManager.removeView(aodView) unregisterReceiver(screenReceiver) } override fun onBind(intent: Intent): IBinder? = null }
对应的layout_aod.xml布局:
<?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#000000"> <!-- 自定义背景图 --> <ImageView android:id="@+id/iv_background" android:layout_width="match_parent" android:layout_height="match_parent" android:scaleType="centerCrop" android:alpha="0.3"/> <!-- 音乐控制按钮组 --> <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="bottom|center_horizontal" android:orientation="horizontal" android:padding="16dp" android:gap="16dp"> <ImageButton android:id="@+id/btn_prev" android:layout_width="48dp" android:layout_height="48dp" android:src="@drawable/ic_skip_previous" android:background="@null" android:tint="#FFFFFF"/> <ImageButton android:id="@+id/btn_play_pause" android:layout_width="64dp" android:layout_height="64dp" android:src="@drawable/ic_play_arrow" android:background="@null" android:tint="#FFFFFF"/> <ImageButton android:id="@+id/btn_next" android:layout_width="48dp" android:layout_height="48dp" android:src="@drawable/ic_skip_next" android:background="@null" android:tint="#FFFFFF"/> </LinearLayout> </FrameLayout>
三、自定义背景选择功能
在主Activity中实现图库选图逻辑,保存选中图片的Uri到SharedPreferences:
class MainActivity : AppCompatActivity() { private val PICK_IMAGE_REQUEST = 100 private val OVERLAY_PERMISSION_REQUEST = 200 private lateinit var sharedPrefs: SharedPreferences override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) sharedPrefs = getSharedPreferences("AOD_PREFS", MODE_PRIVATE) // 检查悬浮窗权限 if (!Settings.canDrawOverlays(this)) { startActivityForResult( Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:$packageName")), OVERLAY_PERMISSION_REQUEST ) } // 选择背景按钮 findViewById<Button>(R.id.btn_select_bg).setOnClickListener { val intent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI) } else { Intent(Intent.ACTION_GET_CONTENT).setType("image/*") } startActivityForResult(intent, PICK_IMAGE_REQUEST) } // 启动/停止AOD按钮 findViewById<Button>(R.id.btn_start_aod).setOnClickListener { startService(Intent(this, AodService::class.java)) } findViewById<Button>(R.id.btn_stop_aod).setOnClickListener { stopService(Intent(this, AodService::class.java)) } } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) when (requestCode) { PICK_IMAGE_REQUEST -> { if (resultCode == RESULT_OK && data != null) { data.data?.let { uri -> sharedPrefs.edit().putString("BACKGROUND_URI", uri.toString()).apply() } } } OVERLAY_PERMISSION_REQUEST -> { if (!Settings.canDrawOverlays(this)) { Toast.makeText(this, "需要开启悬浮窗权限才能使用AOD", Toast.LENGTH_SHORT).show() } } } } }
在AodService中添加加载背景的方法:
private fun loadCustomBackground() { val bgUriStr = sharedPrefs.getString("BACKGROUND_URI", null) bgUriStr?.let { val bgUri = Uri.parse(it) aodView.findViewById<ImageView>(R.id.iv_background).setImageURI(bgUri) } }
四、音乐控制功能
通过MediaControllerCompat实现系统音乐播放控制,在AodService中添加:
private lateinit var mediaController: MediaControllerCompat private fun initMusicControls() { try { // 获取活跃的媒体会话控制器 mediaController = MediaControllerCompat.getMediaController(this) } catch (e: IllegalStateException) { // 无活跃会话时尝试初始化默认控制器 val sessionManager = MediaSessionManager.getSessionManager(this) if (sessionManager.activeSessions.isNotEmpty()) { mediaController = sessionManager.activeSessions[0] } else { Toast.makeText(this, "未检测到活跃音乐会话", Toast.LENGTH_SHORT).show() return } } // 绑定按钮点击事件 val playPauseBtn = aodView.findViewById<ImageButton>(R.id.btn_play_pause) playPauseBtn.setOnClickListener { if (mediaController.playbackState?.state == PlaybackStateCompat.STATE_PLAYING) { mediaController.transportControls.pause() playPauseBtn.setImageResource(R.drawable.ic_play_arrow) } else { mediaController.transportControls.play() playPauseBtn.setImageResource(R.drawable.ic_pause) } } aodView.findViewById<ImageButton>(R.id.btn_prev).setOnClickListener { mediaController.transportControls.skipToPrevious() } aodView.findViewById<ImageButton>(R.id.btn_next).setOnClickListener { mediaController.transportControls.skipToNext() } // 监听播放状态变化 mediaController.registerCallback(object : MediaControllerCompat.Callback() { override fun onPlaybackStateChanged(state: PlaybackStateCompat?) { super.onPlaybackStateChanged(state) playPauseBtn.setImageResource( if (state?.state == PlaybackStateCompat.STATE_PLAYING) R.drawable.ic_pause else R.drawable.ic_play_arrow ) } }) }
五、优化建议
- 低亮度控制:通过调整屏幕亮度降低耗电,需添加
WRITE_SETTINGS权限并引导用户授权:
val settingsContentResolver = contentResolver Settings.System.putFloat(settingsContentResolver, Settings.System.SCREEN_BRIGHTNESS, 0.1f)
- 触摸交互:可添加点击AOD区域唤醒屏幕的逻辑,在
layout_aod.xml的根布局添加点击事件:
aodView.setOnClickListener { val powerManager = getSystemService(POWER_SERVICE) as PowerManager val wakeLock = powerManager.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK or PowerManager.ACQUIRE_CAUSES_WAKEUP, "AOD:WakeLock") wakeLock.acquire(1000) }
内容的提问来源于stack exchange,提问作者Cykonix
相关产品推荐
相关产品推荐

