[Android] 재부팅 시 앱 자동 실행

👻·2023년 4월 4일
0

Android

목록 보기
11/11
post-thumbnail

📌 개요

OS 부팅 시 프로그램이 자동실행 되도록 할 수 있도록 사용중이였다.

Android 10 버전 이후의 기기에선 동작하지 않았다.

이유를 알아보자.


📌 원인파악

부팅 시 자동 실행에 관하여 검색해보면 대부분의 개발자들이 아래의 방식을 사용하고 있다.
하지만, 아래 방식은 Android Q 이상부터 적용되지 않는다.
기존에 사용하던 코드를 한번 살펴보자.

  1. Manifest XML
<?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>
  1. BroadcastReceiver
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 권한을 선언했다.

  1. Permission
Manifest.permission.SYSTEM_ALERT_WINDOW

다음으로 Permission을 받아야 한다.
Manifest에서 선언할 뿐만 아니라, 카메라/블루투스/위치 등 따로 Permission을 받아야 하는 것들 처럼 앱 초기 실행 시 Permission을 받아야 한다.
앱 전체 목록이 나오며 권한이 필요한 현재 앱의 권한을 직접 허용해주어야 한다.

  1. BroadcastReceiver
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")) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
                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();
                }
            } else {
                Intent i = new Intent(context, MainActivity.class);
                i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                context.startActivity(i);
            }
        }
    }
}

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)

profile
Software Developer

0개의 댓글