1. Broadcast Receiver


브로드캐스팅(Broadcasting)이란 여러 사람에게 동시에 데이터를 전달한다는 뜻이다.

안드로이드는 여러 애플리케이션 구성 요소에게 메시지를 전달하고 싶은 경우 브로드캐스팅을 사용한다.

앱에서 브로드캐스팅 메시지를 받고 싶다면 Broadcast Receiver를 만들어 등록하면 된다.


Broadcast Receiver는 Intent Filter를 포함하며, Manifest 파일에 등록함으로써 인텐트를 받을 준비를 한다.

Broadcast Receiver를 이용해서 처리하면 백그라운드에서 동작하기 때문에 사용자는 알 수 없다.(UI가 없다.)



2. 실행 결과 화면




3. AndroidManifest.xml


File - New - Other - Broadcast Receiver 클릭하면 매니페스트 파일에 <receiver> 태그가 자동 등록된다.


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
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.tistory.qlyh8.pracitice">
 
    <uses-permission android:name="android.permission.RECEIVE_SMS" />
 
    <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>
 
        <receiver
            android:name=".SmsReceiver"
            android:enabled="true"
            android:exported="true">
            <intent-filter>
                <action android:name="android.provider.Telephony.SMS_RECEIVED" />
            </intent-filter>
        </receiver>
    </application>
 
</manifest>
cs


브로드캐스팅 메시지는 인텐트 객체로 만들어져 전달된다.

<receiver> 태그 안에 <intent-filter> 태그를 이용해 어떤 브로드캐스팅 메시지를 받고 싶은지 지정할 수 있다.

<intent-filter> 태그 안에 들어있는 <action> 태그는 SMS_RECEIVED 라는 name 속성값을 가지고 있다.

SMS를 담고 있는 인텐트는 이와 동일한 액션 정보를 갖고 있어 브로드캐스트 수신자는 SMS를 담고 있는 인텐트를 받을 수 있게 된다.


SMS 수신 권한을 등록한다.



4. app/build.gradle


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
android {
    compileSdkVersion 26
    defaultConfig {
        applicationId "com.tistory.qlyh8.pracitice"
        minSdkVersion 15
        targetSdkVersion 22
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}
cs


SMS 수신 권한은 위험 권한이라서 앱 실행 시에 사용자에게 권한 승인을 받아야 한다.

실행 시에 권한 승인을 받는 코드를 넣기 힘들다면 build.gradle 파일에서 targetSdkVersion을 22이하로 낮추어준다.



5. SmsReceiver.java

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
package com.tistory.qlyh8.pracitice;
 
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.telephony.SmsMessage;
import android.util.Log;
 
import java.text.SimpleDateFormat;
import java.util.Date;
 
public class SmsReceiver extends BroadcastReceiver {
 
    private static final String TAG = "SmsReceiver";
    private static SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm");
 
    // 메시지를 수신하면 이 메소드가 자동으로 호출된다.
    @Override
    public void onReceive(Context context, Intent intent) {
        Log.d(TAG, "onReceive() 호출됨.");
 
        Bundle bundle = intent.getExtras(); // 번들 형태로 인텐트를 받아온다.
        SmsMessage smsMessage[] = ParseSms(bundle);
 
        if(smsMessage.length > 0){
            String sender = smsMessage[0].getDisplayOriginatingAddress();
            Log.d(TAG, "Sender: " +  sender); // 발신번호
            String contents = smsMessage[0].getMessageBody();
            Log.d(TAG, "Contents: " + contents);  // 메세지 내용
            Date receivedDate = new Date(smsMessage[0].getTimestampMillis());
            Log.d(TAG, "Received Date: " + receivedDate); // 발신시간
 
            SendToActivity(context, sender, contents, receivedDate);
        }
    }
 
    private SmsMessage[] ParseSms(Bundle bundle){
        Object object[] = (Object[]) bundle.get("pdus"); // pdus 안에 SMS 데이터와 관련된 내용이 들어가 있다.
        SmsMessage smsMessage[] = new SmsMessage[object.length];
 
        for(int i = 0 ; i < object.length ; i++){
            // 마시멜로우 버전 이상인지 확인
            if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
                smsMessage[i] = SmsMessage.createFromPdu((byte[]) object[i],  bundle.getString("format"));
            else
                smsMessage[i] = SmsMessage.createFromPdu((byte[]) object[i]);
        }
 
        return smsMessage;
    }
 
 
    private void SendToActivity(Context context, String sender, String contents, Date receivedDate) {
        Intent intent = new Intent(context, MainActivity.class);
        // 화면이 없는데서 화면을 띄워주기 위한 플래그 설정
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
                        | Intent.FLAG_ACTIVITY_SINGLE_TOP
                        | Intent.FLAG_ACTIVITY_CLEAR_TOP);
 
        intent.putExtra("sender", sender);
        intent.putExtra("contents", contents);
        intent.putExtra("receivedDate"format.format(receivedDate));
 
        context.startActivity(intent);
    }
}
 
cs



6. activity_main.xml


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
<?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"
    tools:context="com.tistory.qlyh8.pracitice.SmsActivity"
    android:orientation="vertical"
    android:padding="10dp"
    android:gravity="center">
 
   <EditText
       android:id="@+id/editText1"
       android:layout_width="250dp"
       android:layout_height="wrap_content"
       android:hint="수신번호"/>
 
   <EditText
       android:id="@+id/editText2"
       android:layout_width="250dp"
       android:layout_height="wrap_content"
       android:layout_marginTop="30dp"
       android:hint="메시지 내용"/>
 
   <EditText
       android:id="@+id/editText3"
       android:layout_width="250dp"
       android:layout_height="wrap_content"
       android:layout_marginTop="30dp"
       android:hint="수신시간"/>
 
   <Button
       android:id="@+id/button"
       android:layout_width="250dp"
       android:layout_height="wrap_content"
       android:layout_marginTop="50dp"
       android:text="확인"/>
 
</LinearLayout>
 
cs



7. MainActivity.java


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
package com.tistory.qlyh8.pracitice;
 
import android.content.Intent;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
 
public class MainActivity extends AppCompatActivity {
 
    EditText editText1, editText2, editText3;
    Button button;
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_sms);
 
        editText1 = findViewById(R.id.editText1);
        editText2 = findViewById(R.id.editText2);
        editText3 = findViewById(R.id.editText3);
        button = findViewById(R.id.button);
 
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                finish();   // 화면 닫기
            }
        });
 
        Intent intent = getIntent();
        processIntent(intent);
    }
 
    @Override
    protected void onNewIntent(Intent intent) {
        super.onNewIntent(intent);
        processIntent(intent);
    }
 
    private void processIntent(Intent intent){
        if(intent != null){
            editText1.setText(intent.getStringExtra("sender"));
            editText2.setText(intent.getStringExtra("contents"));
            editText3.setText(intent.getStringExtra("receivedDate"));
        }
    }
}
cs





출처: https://www.edwith.org/boostcourse-android/lecture/17069/

1. 서비스


화면이 없는 상태에서 백그라운드로 실행된다.

프로세스가 종료되어도 시스템에서 자동으로 재시작한다.



2. 서비스 만들기


App → New → Service → Service 메뉴 클릭

(이렇게 하면, 매니페스트에 자동으로 등록된다.)


AndroidManifest.xml

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
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.tistory.qlyh8.pracitice">
 
    <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>
        <activity android:name=".SubActivity" />
 
        <service
            android:name=".MyService"
            android:enabled="true"
            android:exported="true"></service>
    </application>
 
</manifest>
cs



서비스를 실행시키기 위해선 인텐트 객체를 파라미터로 전달해야 하며,

인텐트 객체는 시스템으로 전달된 후 시스템에서 지정한 서비스를 만들고 실행한다.



3. Activity → Service 로 인텐트 보내기


서비스 메서드에서,

onCreate(), onDestory() 메서드는 서비스의 생성과 소멸할 때 자동 호출되는 메서드이며 한번만 실행된다.

 
또한, 인텐트 안에 넣어 전달한 명령이나 데이터를 잘 처리할 수 있도록 onStartCommand 메소드를 사용할 수 있다.
onStartCommand 메소드가 호출되면 인텐트 객체를 파라미터로 전달받고 확인할 수 있다.


4. Service → Activity 로 인텐트 보내기


서비스 메서드에서,

화면이 없는 서비스에서 화면이 있는 액티비티를 띄울 때는 태스크(Task)를 새로 만들어 연결한다.

이 때, FLAG_ACTIVITY_NEW_TASK 플래그를 추가하는데 일반적인 경우 세 개의 플래그를 같이 사용한다.

1
2
3
  showIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
                      |Intent.FLAG_ACTIVITY_SINGLE_TOP
                      |Intent.FLAG_ACTIVITY_CLEAR_TOP);
cs

액티비티가 화면에 보인 상태에서 startActivity 를 호출하면, 액티비티는 새로 만들어지지 않고 기존 액티비티를 그대로 사용하게 된다.


메인 액티비티에서,

화면에 보이고 있는 액티비티를 그대로 사용하므로 onCreate 메소드가 호출되지 않고, onNewIntent 라는 메소드가 호출된다.

이 메소드가 별도로 호출되는 이유는 인텐트를 전달받기 위한 것이며, onNewIntent 메소드 안에서 인텐트 객체를 확인할 수 있다.



5. 실행 결과 화면


    


6. MyService.java


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
package com.tistory.qlyh8.pracitice;
 
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.util.Log;
 
public class MyService extends Service {
 
    private static final String TAG = "MyService";
 
    public MyService() {}
 
    @Override
    public void onCreate() {
        super.onCreate();
        Log.d(TAG, "onCreate() 호출됨");
    }
 
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.d(TAG, "onStartCommand() 호출됨");
 
        if(intent == null) {
            return Service.START_STICKY; // 서비스가 종료되었을 때 다시 자동으로 실행시킨다.
        }
        else{
            //////////////////// 메인 액티비티에 서비스로 인텐트 전달 ///////////////////////
            String command = intent.getStringExtra("command");
            String str = intent.getStringExtra("editStr");
            Log.d(TAG, "인텐트: " + command + " & " + str);
            ////////////////////////////////////////////////////////////////////////////////
 
            /////////////////////// 5초간 쉰 후, 다음 서비스 시작 ///////////////////////////
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            ////////////////////////////////////////////////////////////////////////////////
 
            /////////////////// 서비스에서 메인액티비티로 인텐트 보내기 ////////////////////
            Intent showIntent = new Intent(getApplicationContext(), MainActivity.class);
            showIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
                                |Intent.FLAG_ACTIVITY_SINGLE_TOP
                                |Intent.FLAG_ACTIVITY_CLEAR_TOP);
            showIntent.putExtra("command""show");
            showIntent.putExtra("editStr", str + "from Service");
            startActivity(showIntent); // 액티비티 시작
            ////////////////////////////////////////////////////////////////////////////////
        }
        return super.onStartCommand(intent, flags, startId);
    }
 
    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.d(TAG, "onDestroy() 호출됨");
    }
 
    @Override
    public IBinder onBind(Intent intent) {
        // TODO: Return the communication channel to the service.
        throw new UnsupportedOperationException("Not yet implemented");
    }
}
 
cs



7. MainActivity.java


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
package com.tistory.qlyh8.pracitice;
 
import android.content.Intent;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
 
public class MainActivity extends AppCompatActivity {
 
    EditText editText;
    Button button;
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
 
        editText = findViewById(R.id.editText);
        button = findViewById(R.id.button);
 
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                String editStr = editText.getText().toString();
 
               // 서비스한테 인텐트 전달
               Intent intent = new Intent(getApplicationContext(), MyService.class);
               intent.putExtra("command""show");
               intent.putExtra("editStr", editStr);
               // 서비스를 실행시키지만, 명령이나 데이터를 전달하기 위한 용도로 사용된다.
               startService(intent);
            }
        });
 
        // 서비스로부터 인텐트 받기
        Intent passedIntent = getIntent();
        processCommand(passedIntent);
    }
 
    // 서비스로부터 받은 인텐트 확인
    @Override
    protected void onNewIntent(Intent intent) {
        super.onNewIntent(intent);
        processCommand(intent);
    }
 
    // 서비스로부터 인텐트 전달받기
    public void processCommand(Intent intent){
        if(intent != null){
            String command = intent.getStringExtra("command");
            String str = intent.getStringExtra("editStr");
            Toast.makeText(this"전달받은 데이터: " + command + " & " + str, Toast.LENGTH_LONG).show();
        }
    }
}
 
cs



8. 서비스 확인하기


Tool → Android → Android Device Monitor 를 이용해

Device 탭에서, 실행되고 있는 프로세스를 알 수 있다. 





출처: https://www.edwith.org/boostcourse-android/lecture/17068/

0. 실행 결과 화면




1. MainActivity.java


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
package com.tistory.qlyh8.pracitice;
 
import android.app.Activity;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
 
public class MainActivity extends AppCompatActivity {
 
    Button button;
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
 
        Toast.makeText(getApplicationContext(), "onCreate() 호출됨: 1. 생성", Toast.LENGTH_SHORT).show();
 
        button = findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
               finish();
            }
        });
    }
 
    @Override
    protected void onStart() {
        super.onStart();
        Toast.makeText(getApplicationContext(), "onnStart() 호출됨: 2. 시작", Toast.LENGTH_SHORT).show();
    }
 
    @Override
    protected void onResume() {
        super.onResume();
        Toast.makeText(getApplicationContext(), "onResume() 호출됨: 3. 다시시작", Toast.LENGTH_SHORT).show();
 
        // 다시 시작할 때 데이터 가져오기
        SharedPreferences sharedPreferences = getSharedPreferences("pref", Activity.MODE_PRIVATE);
        if(sharedPreferences != null){
            String color = sharedPreferences.getString("color""");
            Toast.makeText(getApplicationContext(), "color: " + color, Toast.LENGTH_LONG).show();
        }
    }
 
    @Override
    protected void onPause() {
        super.onPause();
        Toast.makeText(getApplicationContext(), "onPause() 호출됨 : 4. 일시중지", Toast.LENGTH_SHORT).show();
 
        // 중지될 때 데이터 저장
        // MODE_PRIVATE : 자신의 어플리케이션 내에서만 사용한다. 기본값은 0
        SharedPreferences sharedPreferences = getSharedPreferences("pref", Activity.MODE_PRIVATE);
        SharedPreferences.Editor editor = sharedPreferences.edit();
        editor.putString("color""black");
        editor.apply();
    }
 
    @Override
    protected void onStop() {
        super.onStop();
        Toast.makeText(getApplicationContext(), "onStop() 호출됨: 5. 중지", Toast.LENGTH_SHORT).show();
    }
 
    @Override
    protected void onDestroy() {
        super.onDestroy();
        Toast.makeText(getApplicationContext(), "onDestroy() 호출됨: 6. 소멸", Toast.LENGTH_SHORT).show();
    }
 
    @Override
    protected void onRestart() {
        super.onRestart();
        Toast.makeText(getApplicationContext(), "onRestart() 호출됨: 7. 다시시작", Toast.LENGTH_SHORT).show();
    }
}
 
cs


SharedPreferences 이외에도 onSaveInstanceState 메소드와 onRestoreInstanceState 메소드를 사용할 수도 있다.

액티비티가 중지되기 전에 onSaveInstanceState 메소드가 호출되는데 이 안에서 임시로 데이터를 저장할 수 있다.


저장할 때는 Bundle 객체를 만들어 저장하는데, 

이 Bundle 객체는 액티비티가 다시 만들어질 때 호출되는 onCreate나 

화면에 다시 보일 때 호출되는 onRestoreInstanceState 메소드의 파라미터로 전달된다.


파라미터로 전달된 번들 객체의 데이터를 이용하면 화면에 원래의 데이터를 복구할 수 있다.





출처: https://www.edwith.org/boostcourse-android/lecture/17067/

+ Recent posts