투케이2K

128. (AndroidStudio/android/java) 포그라운드 서비스 (Foreground Service) 사용해 이모탈 서비스 만들기 - 좀비 서비스 본문

Android

128. (AndroidStudio/android/java) 포그라운드 서비스 (Foreground Service) 사용해 이모탈 서비스 만들기 - 좀비 서비스

투케이2K 2021. 5. 3. 13:23

/* =========================== */

[ 개발 환경 설정 ]

개발 툴 : AndroidStudio

개발 언어 : java

/* =========================== */

/* =========================== */

[소스 코드]

[AndroidManifest.xml 파일]

<퍼미션 등록 부분>
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.BOOT_COMPLETED" />
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />



<액티비티 등록 부분>
<activity
            android:name=".A_ImmotalService"
            android:screenOrientation="portrait"
            android:windowSoftInputMode="adjustPan"/>



<서비스 등록 부분>
<service
            android:name=".A_ImmotalServiceReceiver"
            android:enabled="true"
            android:exported="true"
            android:stopWithTask="false" />

[JAVA 파일 : A_ImmotalService]

package kr.co.two2k.manager;

import androidx.appcompat.app.AppCompatActivity;

import android.content.Context;

import android.content.Intent;
import android.os.Build;
import android.os.Bundle;

import android.os.Handler;
import android.util.Log;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;

import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;

public class A_ImmotalService extends AppCompatActivity {

    /**
     * [좀비 서비스]
     *  1. 앱이 실행되는 동안 주기적 작업을 반복합니다
     *  2. 서비스를 실행한 액티비티가 종료되어도 주기적 작업을 반복합니다
     *  3. 애플리케이션 killed 상태가 되면 다시 서비스를 재실행합니다
     * */

    //TODO [클래스 컴포넌트 선언]
    Button start_button;
    Button stop_button;

    //TODO [액티비티 시작 메소드]
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_a_immotal_service);
        Log.d("---","---");
        Log.d("//===========//","================================================");
        Log.d("","\n"+"[A_ImmotalService > onCreate() 메소드 : 액티비티 시작 실시]");
        Log.d("//===========//","================================================");
        Log.d("---","---");

        //TODO [컴포넌트 매칭 실시] 
        start_button = (Button)findViewById(R.id.start_button);
        stop_button = (Button)findViewById(R.id.stop_button);

        //TODO [화면 접속 시 즉시 서비스 종료]
        try {
            setServiceStop();
        }
        catch (Exception e){
            e.printStackTrace();
        }

        //TODO [버튼 클릭 이벤트]
        start_button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                try {
                    Toast.makeText(getApplication(), "서비스를 시작합니다 ... ",Toast.LENGTH_SHORT).show();
                    setServiceStart();
                }
                catch (Exception e){
                    e.printStackTrace();
                }
            }
        });

        //TODO [버튼 클릭 이벤트] 
        stop_button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                try {
                    Toast.makeText(getApplication(), "서비스를 종료합니다 ... ",Toast.LENGTH_SHORT).show();
                    setServiceStop();
                }
                catch (Exception e){
                    e.printStackTrace();
                }
            }
        });

    }//TODO 메인 종료

    //TODO [서비스 등록 부분] 
    public void setServiceStart(){
        Log.d("---","---");
        Log.w("//===========//","================================================");
        Log.d("","\n"+"[A_ImmotalService > setServiceStart() 메소드 : 서비스 시작 수행]");
        Log.w("//===========//","================================================");
        Log.d("---","---");
        try {            
            //TODO [서비스 시작 실시]
            startService(new Intent(getApplicationContext(), A_ImmotalServiceReceiver.class));
        }
        catch (Exception e){
            e.printStackTrace();
        }
    }

    //TODO [서비스 해제 부분] 
    public void setServiceStop(){
        Log.d("---","---");
        Log.e("//===========//","================================================");
        Log.d("","\n"+"[A_ImmotalService > setServiceStop() 메소드 : 서비스 종료 수행]");
        Log.e("//===========//","================================================");
        Log.d("---","---");
        try {
            //TODO [서비스 종료 실시]
            stopService(new Intent(getApplicationContext(), A_ImmotalServiceReceiver.class));
        }
        catch (Exception e){
            e.printStackTrace();
        }
    }

    //TODO [백버튼 터치시 뒤로 가기] 
    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        // 디바이스의 키 이벤트가 발생했는데, 뒤로가기 이벤트일때
        if (keyCode == KeyEvent.KEYCODE_BACK) {
            Log.d("---","---");
            Log.d("//===========//","================================================");
            Log.d("","\n"+"[A_ImmotalService > onKeyDown() 메소드 : 백버튼 터치시 뒤로 가기 이벤트 실시]");
            Log.d("//===========//","================================================");
            Log.d("---","---");
            try {
                //TODO [액티비티 종료 실시]
                finish();
                overridePendingTransition(0,0);
            }
            catch (Exception e){
                e.printStackTrace();
            }
        }
        return true;
    }

    //TODO [액티비티 종료 메소드] 
    @Override
    public void onDestroy(){
        super.onDestroy();
        Log.d("---","---");
        Log.d("//===========//","================================================");
        Log.d("","\n"+"[A_ImmotalService > onDestroy() 메소드 : 액티비티 종료 확인]");
        Log.d("//===========//","================================================");
        Log.d("---","---");       
    }

}//TODO 클래스 종료

 

[JAVA 파일 : A_ImmotalServiceReceiver]

    //TODO 노티피케이션 알림 표시 부분
    NotificationManager notificationManager; //매니저
    NotificationChannel notificationChannel; //채널
    NotificationCompat.Builder builder; //빌더
    String serviceTittle = "Service";
    String serviceContent = "Start";
    public void setNotificationShow(){
        try {
            if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){ //TODO 오레오 버전 이상
                //오레오 버전 이상부터는 노티피케이션을 사용하기 위해서 채널이 필요하다
                int importance = NotificationManager.IMPORTANCE_HIGH; //TODO 알림 우선순위를 최상으로 설정 (타이틀 및 소리)
                String Noti_Channel_ID = "DefaultNoti";
                String Noti_Channel_Group_ID = "DefaultNoti_Group";
                notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
                notificationChannel = new NotificationChannel(Noti_Channel_ID,Noti_Channel_Group_ID,importance);
                //TODO 뱃지 카운트 실시 (디폴트 true)
                //notificationChannel.setShowBadge(true);
                if(notificationManager.getNotificationChannel(Noti_Channel_ID) != null){ //TODO 채널이 존재할 경우
                    Log.d("---","---");
                    Log.w("//===========//","================================================");
                    Log.d("","\n"+"[A_ImmotalServiceReceiver > setNotificationShow() 메소드 : 채널이 이미 존재합니다]");
                    Log.w("//===========//","================================================");
                    Log.d("---","---");
                }
                else{
                    Log.d("---","---");
                    Log.e("//===========//","================================================");
                    Log.d("","\n"+"[A_ImmotalServiceReceiver > setNotificationShow() 메소드 : 채널이 없어서 만듭니다]");
                    Log.e("//===========//","================================================");
                    Log.d("---","---");
                    notificationManager.createNotificationChannel(notificationChannel);
                }
                notificationManager.createNotificationChannel(notificationChannel);
                builder = new NotificationCompat.Builder(getApplicationContext(),Noti_Channel_ID)
                        .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.drawable.tk_app_icon))
                        .setSmallIcon(R.drawable.ic_logo_white)
                        .setColor(ContextCompat.getColor(this, R.color.customColor))
                        .setWhen(System.currentTimeMillis())
                        .setShowWhen(true)
                        .setAutoCancel(true)
                        .setPriority(NotificationCompat.PRIORITY_MAX)
                        .setContentTitle(serviceTittle) //TODO 제목
                        .setChannelId(Noti_Channel_ID)
                        .setContentText(serviceContent); //TODO 내용

                //builder.setContentIntent(pendingIntent); //TODO 개별 인텐트 적용

                builder.getNotification().flags |= Notification.FLAG_AUTO_CANCEL; //TODO 노티 알림 삭제 시 자동으로 푸시 뱃지 표시 지움

                notificationManager.notify(1,builder.build()); //TODO 노티피케이션이 표시

                startForeground(1, builder.build()); //TODO 포그라운드 서비스 실행               
            }
            else {
                builder = new NotificationCompat.Builder(getApplicationContext())
                        .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.drawable.tk_app_icon))
                        .setSmallIcon(R.drawable.ic_logo_white)
                        .setColor(ContextCompat.getColor(this, R.color.customColor))
                        .setWhen(System.currentTimeMillis())
                        .setShowWhen(true)
                        .setAutoCancel(true)
                        .setPriority(NotificationCompat.PRIORITY_MAX) //TODO Priority와 Vibrator가 있어야 알림바 표시됨
                        //.setDefaults(Notification.DEFAULT_VIBRATE)
                        .setContentTitle(serviceTittle) //TODO 제목
                        .setContentText(serviceContent); //TODO 내용

                //builder.setContentIntent(pendingIntent); //TODO 개별 인텐트 적용

                builder.getNotification().flags |= Notification.FLAG_AUTO_CANCEL; //TODO 노티 알림 삭제 시 자동으로 푸시 뱃지 표시 지움

                notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);

                notificationManager.notify(1,builder.build()); //TODO 노티피케이션이 표시

                startForeground(1, builder.build()); //TODO 포그라운드 서비스 실행               
            }
        }
        catch (Exception e){
            e.printStackTrace();
        }
    }

[XML 파일 : activity_a_immotal_service.xml]

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"
    android:orientation="vertical"
    android:focusable="true"
    android:focusableInTouchMode="true"
    android:id="@+id/parent">
    <!--    [editText 사용시 자동으로 포커스활성 방지]-->
    <!--    android:focusable="true"-->
    <!--    android:focusableInTouchMode="true"-->
    <!--    android:id="@+id/parent"-->

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <!-- ====== [타이틀 레이아웃] ====== -->
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="60dp"
            android:orientation="horizontal"
            android:background="#343d46"
            android:layout_marginBottom="10dp">
            <TextView
                android:id="@+id/title_text"
                android:layout_width="0dp"
                android:layout_height="match_parent"
                android:layout_weight="1"
                android:text="[ 이모탈 서비스 ]"
                android:textColor="#ffffff"
                android:textSize="23dp"
                android:textStyle="bold"
                android:gravity="center"/>
        </LinearLayout>

        <!-- ====== [카드 레이아웃] ====== -->
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical"
            android:background="#343d46"
            android:layout_margin="15dp">

            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="0dp"
                android:layout_weight="6.5"
                android:orientation="horizontal"
                android:layout_marginTop="20dp"
                android:layout_marginLeft="30dp"
                android:layout_marginRight="30dp"
                android:layout_marginBottom="0dp">
                <ImageView
                    android:layout_width="match_parent"
                    android:layout_height="match_parent"
                    android:src="@drawable/url_icon"/>
            </LinearLayout>

            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="0dp"
                android:layout_weight="1"
                android:orientation="horizontal"
                android:layout_marginTop="15dp"
                android:layout_marginLeft="30dp"
                android:layout_marginRight="30dp"
                android:layout_marginBottom="20dp">

                <Button
                    android:id="@+id/start_button"
                    android:layout_width="0dp"
                    android:layout_height="match_parent"
                    android:layout_weight="1"
                    android:text="서비스 시작"
                    android:textStyle="bold"
                    android:textSize="13dp"
                    android:gravity="center"
                    android:textColor="#000000"
                    android:background="@drawable/white_button_bg"
                    android:layout_marginTop="0dp"
                    android:layout_marginLeft="0dp"
                    android:layout_marginRight="5dp"
                    android:layout_marginBottom="0dp" />

                <Button
                    android:id="@+id/stop_button"
                    android:layout_width="0dp"
                    android:layout_height="match_parent"
                    android:layout_weight="1"
                    android:text="서비스 종료"
                    android:textStyle="bold"
                    android:textSize="13dp"
                    android:gravity="center"
                    android:textColor="#000000"
                    android:background="@drawable/white_button_bg"
                    android:layout_marginTop="0dp"
                    android:layout_marginLeft="5dp"
                    android:layout_marginRight="0dp"
                    android:layout_marginBottom="0dp" />

            </LinearLayout>

        </LinearLayout>

    </LinearLayout>

</LinearLayout>

/* =========================== */

/* =========================== */

[결과 출력]

/* =========================== */

/* =========================== */

[첨부 파일 - 전체 소스코드]

소스코드.txt
0.03MB

/* =========================== */

반응형
Comments