본문 바로가기

개발 코딩 정보 공유/안드로이드 자바 코틀린

안드로이드 API 26 (8.0 오레오) 대비 하기 - 백그라운드 제한

 

 

 

 

 

 

안드로이드 API 26

- 강제 업데이트 대비하기

 

 

 

 

 

 

 

 

 

안녕하세요. 지난번에도 소개 했었던 안드로이드 API 26(안드로이드 8.0 오레오) 강제 업데이트를 위한 대비 관련하여 상당히 많은 부분을 수정하고 추가해야 하는데요. 하...(벌써 현기증이)

 

구글은 이미 한참전 부터 공지를 했었죠. 2018년 8월까지는 새로운 앱에 대해서, 2018년 11월 까지는 기존의 앱까지 통틀어서 강제로! 무조건 API level 26 으로 올려야 한다고 권고 하고 있습니다. 이는 이번 일회성에 그치지 않고 이후에 판올림이 일어날때 마다 (거의 매년) 이번일과 같은 강제적 업데이트를 해야한다고 하고 있습니다. 하...(왜 그러냐 너네들) 큰 틀에서는 64bit 지원을 위한 초석 이며 보안성을 높이기 위한 조치 라고 합니다.

 

64비트 지원을 위한 준비는 2019년 8월까지 준비하도록 권고하고 있습니다. 어찌됐든 8.0 (오레오) 기준으로 제 입장에서 ? 크게 손봐야 할 것들을 골라내 봤습니다.

 

- 백그라운드 서비스

- 위치 정보 관련

- Notification 관련

- GCM 관련

 

사실 GCM은 한참전부터 FCM으로 변경 했어야 하는 부분 입니다만... 우선 오늘은 백그라운드 제한 에 대해 알아보려고 합니다. 

 

구글은 불필요한 배터리의 소모를 줄이고 유저에게 이를 알릴수 있는 장치를 두고자 몇가지 조치를 취했습니다. 

그 중에 백그라운드에 대한 제한을 걸었습니다. 

 

백그라운드 서비스 제한 : 앱이 유휴 상태인 경우 백그라운드 서비스의 사용이 제한됩니다. 이 기능은 사용자에게 잘 보이는 포그라운드 서비스에는 적용되지 않습니다.

 

브로드캐스트 제한 : 제한된 예외의 경우, 앱이 암시적 브로드캐스트에 등록하기 위해 자체 매니페스트를 사용할 수 없습니다. 그렇지만 여전히 앱이 런타임에 브로드캐스트에 등록할 수 있으며, 특정 앱을 대상으로 하는 명시적 브로드캐스트에 등록하기 위해 매니페스트를 사용할 수 있습니다.

 

이는 안드로이드 8.0을 타겟팅 하는 경우 적용되는 기준입니다. 우리는 구글님의 API 26 강제 업데이트를 진행 해야 하기 때문에 어쩔수 없이 이 기준을 맞춰야 합니다.

 

" 앱이 포그라운드에 있는 동안에는 이 앱이 포그라운드 및 백그라운드 서비스를 자유롭게 생성하고 실행할 수 있습니다. 앱이 백그라운드로 이동하더라도 몇 분 정도의 기간 동안은 앱이 서비스를 생성하고 사용하는 것이 여전히 허용됩니다. 이 기간이 끝나면 앱이 유휴 상태로 간주됩니다. 이때 마치 앱이 서비스의 Service.stopSelf() 메서드를 호출한 것처럼 시스템이 앱의 백그라운드 서비스를 중지시킵니다. "

 

구글님 의 말을 요약해보자면 "서비스를 마음껏 생성하고 사용해라. 그러나 백그라운드서비스는 곧 죽을 것이다." 라는 뜻으로 해석이 되는데요. 믿을 수 없습니다. 그래서 실제로 예제를 돌려 봤는데 결과는 정말 충격적 이었습니다. 

백그라운드로 들어간(유휴상태) 서비스는 수초가 지나서 destory 되어 버렸습니다.

우리는 선택권이 없습니다. 자 다들 수정합시다. 

 

그렇다면 백그라운드 서비스를 살리기 위해서는 어떻게 해야 할까요?

 

바로 서비스를 포그라운드 서비스로 만드는 것입니다. 

포그라운드 상태가 되기 위해서는 앱이 화면상에 올라와 동작하고 있거나, 포그라운드로 셋팅된 서비스가 돌아 가고 있거나 입니다. 우리가 원하는 바는 아마도 후자 일텐데요. 구글에 명에 따라 아래와 같이 적용해 보았습니다.

 

" 앱이 백그라운드에 있는 동안 포그라운드 서비스를 생성해야 하는 경우, 백그라운드 서비스를 생성하고 이 서비스를 포그라운드로 승격시키려고 시도하는 대신 새 NotificationManager.startServiceInForeground() 메서드를 사용합니다. "

 

그러나. 속았습니다.  NotificationManager.startServiceInForeground() 는 이미 삭제된 메서드 였습니다. Context.startForegroundService()를 사용하여 start 시키고 반드시 수초 내에 Service.startForeground()를 통해 Notification과 연결해야 합니다. Notification 의 경우 조금 새로운 코드가 보이실 텐데요. Notification Channel 을 만들어줘야 합니다. 길어질수 있으니 다음 섹션에서 더 알아보겠습니다.

 

 

 

*** 파트너스 활동을 통해 일정액의 수수료를 제공받을 수 있음

 

 

 

 

소스코드를 같이 올려봅니다. 

API 26 (안드로이드 8.0 오레오) 잘 대비해 봅시다~~~!

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package com.example.test.androidbackgroundtest;
 
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.content.Context;
import android.content.Intent;
import android.graphics.Color;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
 
public class MainActivity extends AppCompatActivity {
    public static boolean TFLAG = false;
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
 
        NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
        NotificationChannel notificationChannel = new NotificationChannel("channel1""1번채널", NotificationManager.IMPORTANCE_DEFAULT);
        notificationChannel.setDescription("1번채널입니다");
        notificationChannel.enableLights(true);
        notificationChannel.setLightColor(Color.GREEN);
        notificationChannel.enableVibration(true);
        notificationChannel.setVibrationPattern(new long[]{100200100200});
        notificationChannel.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE);
        notificationManager.createNotificationChannel(notificationChannel);
 
        TFLAG = true;
        Intent backStartIntent = new Intent(MainActivity.this, TestBackgroundService.class);
        backStartIntent.setAction("Action1");
        startForegroundService(backStartIntent);
 
    }
}
 
cs

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">
 
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
 
</android.support.constraint.ConstraintLayout>
cs

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
package com.example.test.androidbackgroundtest;
 
import android.app.Notification;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.IBinder;
import android.support.annotation.Nullable;
import android.support.v4.app.NotificationCompat;
import android.util.Log;
 
public class TestBackgroundService extends Service {
    private int a = 0;
 
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }
 
    @Override
    public void onCreate() {
        super.onCreate();
 
    }
 
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
 
        if (intent.getAction().equals("Action1")) {
            Log.i("onStartCommand-Action1""---서비스 스타트--- ");
 
            Intent notificationIntent = new Intent(this, MainActivity.class);
            notificationIntent.setAction("Action2");
 
            PendingIntent pendingIntent = PendingIntent.getActivity(this0,
                    notificationIntent, 0);
 
            Bitmap icon = BitmapFactory.decodeResource(getResources(),
                    R.mipmap.ic_launcher_round);
 
            Notification notification = new NotificationCompat.Builder(this"channel1")
                    .setContentTitle("background machine")
                    .setTicker("측정중... ")
                    .setContentText("백그라운드 동작중")
                    .setSmallIcon(R.mipmap.ic_launcher_round)
                    .setContentIntent(pendingIntent)
                    .setOngoing(true).build();
 
            startForeground(111, notification);
 
            Thread bt = new Thread(new Runnable() {
                @Override
                public void run() {
                    while(true){
                        try {
                            Thread.sleep(2000);
                        }catch(InterruptedException e){
                            e.printStackTrace();
                        }
                        if(!MainActivity.TFLAG){
                            Log.i("background-counter","---백스레드 중지---");
                            break;
                        }
 
                        Log.i("background-counter",String.valueOf(++a));
                    }
                }
            });
            bt.setName("백그라운드스레드");
            bt.start();
 
        }else if(intent.getAction().equals("Action2")){
        }
 
 
 
        return START_STICKY;
    }
 
    @Override
    public void onDestroy() {
        super.onDestroy();
 
        MainActivity.TFLAG = false;
        Log.i("SERVICE TAG ""==== 서비스 destroyed ===");
 
    }
}
 
cs

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.test.androidbackgroundtest">
 
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
 
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
 
        <service android:name=".TestBackgroundService"/>
    </application>
 
</manifest>
cs

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
apply plugin: 'com.android.application'
 
android {
    compileSdkVersion 26
    defaultConfig {
        applicationId "com.example.test.androidbackgroundtest"
        minSdkVersion 26
        targetSdkVersion 26
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}
 
dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.android.support:appcompat-v7:26+'
    implementation 'com.android.support.constraint:constraint-layout:1.1.3'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.2'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
}
 
cs