OS 부팅 시 프로그램이 자동실행 되도록 할 수 있도록 사용중이였다.
Android 10 버전 이후의 기기에선 동작하지 않았다.
이유를 알아보자.
부팅 시 자동 실행에 관하여 검색해보면 대부분의 개발자들이 아래의 방식을 사용하고 있다.
하지만, 아래 방식은 Android Q 이상부터 적용되지 않는다.
기존에 사용하던 코드를 한번 살펴보자.
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="your package">
<!--AutoStart-->
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<application
...>
<activity
android:name=".activity.MainActivity"
...>
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<receiver
android:name=".boot.BootCompletedReceiver"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</receiver>
</application>
</manifest>
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.widget.Toast;
public class BootCompletedReceiver extends BroadcastReceiver {
private static final String TAG = "BootCompletedReceiver";
@Override
public void onReceive(Context context, Intent intent) {
Toast.makeText(context, context.getString(R.string.auto_start), Toast.LENGTH_SHORT).show();
if (intent.getAction().equals("android.intent.action.BOOT_COMPLETED")) {
Intent i = new Intent(context, MainActivity.class);
i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(i);
}
}
}
Manifest에서 아래 권한으로 OS 부팅 시 앱 실행에 대한 권한을 받을 수 있다.
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
그리고, BootCompletedReceiver를 통해 intent의 action이 "BOOT_COMPLETE"인 경우 로직을 수행할 수 있다.
위 코드를 보면 부팅이 되었을 때 MainActivity를 context에서 startActivity()를 호출하여 실행하도록 되어있다.
하지만, Android Q 이상에서는 BroadcastReceiver는 동작하였으나, startActivity()가 실행되지 않았다.
Android Developer에서 원인을 찾을 수 있었다.
https://developer.android.com/about/versions/12/behavior-changes-12#notification-trampolines
Android 12에서 앱 성능과 UX를 개선하기 위해 백그라운드에서 실행되는 Service, BroadcastReceiver에서 startActivity()를 호출할 수 없도록 변경되었다고 한다.
변경했으면 예외처리라도 해주지 시*
어쨌든 위와 같은 사유로 Android SDK 12 이상에서는 기존의 방식을 사용할 수 없다.
이것 저것 다 해본 결과 오랜 시간 끝에 해결 방안을 찾았다.
해결 방안을 찾기 까지의 고생한 길은 맨 아래에 적어놓겠다.
결론부터 말하자면 PendingIntent를 사용하여 해결했다.
PendingIntent를 사용하려면 "SYSTEM_ALERT_WINDOW" 권한이 필요하다.
변경된 것을 살펴보자.
1. Manifest
<!--AutoStart-->
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
부팅 시점을 알아야 하기에 기존대로 RECEIVE_BOOT_COMPLETED가 필요하며,
SYSTEM_ALERT_WINDOW 권한을 선언했다.
Manifest.permission.SYSTEM_ALERT_WINDOW
다음으로 Permission을 받아야 한다.
Manifest에서 선언할 뿐만 아니라, 카메라/블루투스/위치 등 따로 Permission을 받아야 하는 것들 처럼 앱 초기 실행 시 Permission을 받아야 한다.
앱 전체 목록이 나오며 권한이 필요한 현재 앱의 권한을 직접 허용해주어야 한다.
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.widget.Toast;
public class BootCompletedReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Toast.makeText(context, context.getString(R.string.auto_start), Toast.LENGTH_SHORT).show();
if (!intent.getAction().equals("android.intent.action.BOOT_COMPLETED")) return;
if(Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
Intent i = new Intent(context, MainActivity.class);
i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(i);
return;
}
Intent i = new Intent(context, MainActivity.class);
i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, i, PendingIntent.FLAG_UPDATE_CURRENT);
try {
pendingIntent.send();
} catch (PendingIntent.CanceledException e) {
e.printStackTrace();
}
}
}
PendingIntent를 통해 getActivity()를 호출하도록 변경하였고, PendingIntent의 send()를 통해 실행했다.
대체로 Notification에서 사용되며 Notification 클릭 시 send()가 호출되며 Intent를 작동시킨다.
하지만 나의 경우 Notification을 클릭 한 경우가 아닌 부팅 시 바로 실행되도록 해야하기 때문에 PendingIntent에서 바로 send()를 호출하여 곧바로 실행할 수 있도록 했다.
위 방식을 통해 Q 이상 버전과 그 이하 버전에 대해 따로 처리를 해두었다.
작동이 아주 잘된다.
간단해 보이지만 꽤 오랜시간 고생했다😡
Android 12에서 Notification trampoline restrictions에 관해 이슈 발생
Android 12에서 앱 성능과 UX를 개선하기 위해 Service, Broadcast receiver에서 startActivity()를 호출할 수 없음
트램펄린 역할을 하는 서비스나 브로드캐스트 리시버에서 activity를 시작하는 경우 PendingIntent를 사용하여 해결하라고 나와있음
하지만 PendingIntent 사용하여 실행 시 작동하지 않음, 예외도 발생하지 않음
StackOverFlow에서의 대다수 의견은 Notification을 사용하여 해결하라는 위주였음
Notification을 사용 시 사용자의 상호작용이 있어야함, 현재 상황에 필요 없는 해결방안 (대체로 Notification으로 앱을 실행 유도하는 앱이 많았음)
Notification으로 알림을 띄우고, 자동으로 클릭 이벤트가 발생하는 쪽으로 생각해보았으나 방안을 찾지 못함
Manifest에서 receiver의 intent-filter에서 BOOT_COMPLETED 관련된 여러 옵션 추가하는 방식으로도 해결되지 않음(QUICKBOOT_POWERON, LOCKED_BOOT_COMPLETED 등)
Manifest에서 MainActivity를 singleTop 설정, intent의 flag를 Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK 두가지로 설정하는 방식은 해결되지 않음
BroadcastReceiver에서 Intent 생성 시 Settings.ACTION_MANAGE_OVERLAY_PERMISSION을 설정하여 오버레이 권한을 받고 activity를 실행하라는 의견도 있었으나 해결되지 않음 (어플 중 최상단에 표출시키는 권한)
앱 실행도중 종료 시 백그라운드에 이미 App이 남아있어서 안되는 것도 추측했으나 영향받지 않음
PendingIntent 사용 시 SYSTEM_ALERT_WINDOW 권한이 필요하다는 것을 발견했고, Manifest에 추가하였으나 실행되지 않음
PendingIntent 자체가 SYSTEM_ALERT와 관련 있는 것을 파악하고, SYSTEM_ALERT가 Manifest에 추가만 한다고 권한이 생기지 않다는것을 알고 앱 초기 실행 시 권한을 요청하여 직접 권한을 받게 함
권한을 제대로 받았을 때, PendingIntent가 정상적으로 실행되고 자동 실행이 됨
startActivity not working when calling BroadCastReceiver, Service
Can't startActivity from BroadCastReceiver on Android 10 / 12
Start app on startup(boot)