서비스의 문제점이 있다.
ActivityA와 ServiceB는 보기에는 자바클래스와 같아보여도 서로 다른 프로세스에서 동작하기 때문에 데이터 통신을 제외하고는 두 클래스에서 정보를 주고받을 수 없다.
데이터야... 영속성을 포기하고 두 데이터를 함께 사용할 수 있지만 매서드는 그렇지 못하다. 같은 매서드를 복사해서 사용해도 A와 B의 상태가 동일하지 못한데, 이를 위해 사용해야 하는것이 마샬링 기법이다.
안드로이드에서는 이것을 비교적 쉽게 해결할 수 있는데, 바로 AIDL 서비스 바인딩이다.
https://developer.android.com/guide/components/aidl?hl=ko
먼저, 바인딩 되지 않을때의 한계를 알아보기 위해, 간단한 프로그램을 통해 바인딩 되지 않은 서비스의 결과물을 보자.
- 뷰
activitiy_main.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"
tools:context=".MainActivity"
android:orientation="vertical"
>
<TextView
android:id="@+id/tv_count"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="1"
android:textSize="100sp"
android:gravity="center"
/>
<Button
android:id="@+id/btn_start"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Start"
android:textSize="50sp"/>
<Button
android:id="@+id/btn_stop"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="stop"
android:textSize="50sp"/>
</LinearLayout>
- 자바 코드
MainActivity.java
package com.mary.counterapp;
import androidx.appcompat.app.AppCompatActivity;
import android.app.Service;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
private static final String TAG = "Main_Activity";
private TextView tvCount;
private Button btnStart, btnStop;
private ICounterService bind;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
init();
Listener();
}
private void init(){
tvCount=findViewById(R.id.tv_count);
btnStart=findViewById(R.id.btn_start);
btnStop=findViewById(R.id.btn_stop);
}
private void Listener(){
btnStart.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent=new Intent(MainActivity.this,CounterService.class);
startService(intent);
}
});
btnStop.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent=new Intent(MainActivity.this,CounterService.class);
stopService(intent);
}
});
}
}
CounterService.java
package com.mary.counterapp;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
public class CounterService extends Service {
private static final String TAG = "CounterService";
private int count;
private boolean isStop = false;
@Override
public void onCreate() {
super.onCreate();
}
@Override
public IBinder onBind(Intent intent) {
return binder;
}
class Counter implements Runnable {
@Override
public void run() {
for (count = 0; count < 20; count++) {
if (isStop) {
break;
}
try {
Thread.sleep(1000);
Log.d(TAG, "run: " + count);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
@Override
public void onDestroy() {
super.onDestroy();
isStop = true;
}
}
이 코드의 문제점은 다음과 같다.
1. 스레드가 돌아간다고 해서, 서비스에서 메인 액티비티의 뷰를 참조할 수 없기 때문에 뷰가 1에서 증가하지 않는다(앞서 말했지만 두 액티비티가 서로 다른 프로세스이기 때문에 일반적인 방법으로는 참조할 수 없다.).
2. stop를 누를 시 count가 저장되지 않기 때문에 다시 start를 누르게 될 경우 0으로 돌아간다.
이를 해결하기 위해 AIDL을 이용한다.
AIDL은 이렇게 만든다.
[우클릭] - [New] - [AIDL] - [AIDL File]
다음과 같이 들어가서, 인터페이스의 이름을 설정하고 finish를 누르면 aidl 파일이 생긴걸 확인할 수 있다.
여기서 끝나면 안되고, ctrl+F9 or [Build-Make Project]를 클릭해 프로젝트를 리빌드 하면, 다음과 같은 경로에 인터페이스가 생성된다.
이걸.. 이 인터페이스를 손대는게 아니라... aidl 파일 수정 - 리빌드를 통해 인터페이스를 수정해야한다.
내가 바꾸고 싶으면 aidl 파일 수정이다.. 저 인터페이스는 이미 만들어진 것이라 손대면 안된다.. ㅠㅠㅠ
- AIDL파일
// ICounterService.aidl
package com.mary.counterapp;
// Declare any non-default types here with import statements
interface ICounterService {
int getCount();
}
int getCount를 통해 서로 동기화 할 수 없었던 Count 값을 동기화한다.
이것을 참조하기 위해 서비스에 연결할 스텁을 생성한다.
-서비스
CounterService.java
--중략--
private int count;
private boolean isStop = false;
ICounterService.Stub binder = new ICounterService.Stub() {
@Override
public int getCount() throws RemoteException {
return count;
}
};
public CounterService() {
Thread counterThread = new Thread(new Counter());
--하략--
그리고, 바인딩 함수에 만든 스텁을 바인딩 하고, 언바인드 함수를 만들어 스레드를 멈추도록 한다.
--중략--
@Override
public IBinder onBind(Intent intent) {
return binder;
}
@Override
public boolean onUnbind(Intent intent) {
isStop=true;
return super.onUnbind(intent);
}
--하략--
그리고 스레드 내부에서 값이 바뀌므로, 메인스레드가 아닌 이상 UI를 건들 수 없으므로 Handler 함수를 통해 값을 건든다.
MainActivity.java의 btnStartListener 부분
private void Listener(){
btnStart.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.d(TAG, "onClick: 111");
final Intent intent=new Intent(MainActivity.this,CounterService.class);
bindService(intent, conn,BIND_AUTO_CREATE);
Log.d(TAG, "onClick: 222");
new Thread(new Runnable() {
@Override
public void run() {
while (running){
if(binder!=null){
handler.post(new Runnable() {
@Override
public void run() {
try {
tvCount.setText(binder.getCount()+"");
if(binder.getCount()==20){
running=false;
}
} catch (RemoteException e) {
e.printStackTrace();
}
}
});
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}).start();
}
});
이러면, 뷰는 계속 변화하지만 멈추고 재실행할 경우 카운트가 0으로 시작한다.
따라서, 값을 서비스로, intent로 보내고, 그 값을 서비스에서 참조하도록 한다.
MainActivity.java 의 stopButtonListener
btnStop.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
try {
startCount = binder.getCount();
} catch (RemoteException e) {
e.printStackTrace();
}
unbindService(connection);
running = false;
}
});
}
Service 부분
package com.mary.counterapp;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
public class CounterService extends Service {
private static final String TAG = "CounterService";
private int count;
private int startCount;
private boolean isStop = false;
ICounterService.Stub binder = new ICounterService.Stub() {
@Override
public int getCount() throws RemoteException {
return count;
}
};
public CounterService() {
Log.d(TAG, "CounterService: 생성자 실행됨");
}
@Override
public void onCreate() {
super.onCreate();
Log.d(TAG, "onCreate: 서비스 시작");
Thread counterThread = new Thread(new Counter());
counterThread.start();
}
@Override
public IBinder onBind(Intent intent) {
startCount = intent.getIntExtra("startCount", 0);
return binder;
}
@Override
public boolean onUnbind(Intent intent) {
Log.d(TAG, "onUnbind: count : "+count);
isStop = true;
return super.onUnbind(intent);
}
class Counter implements Runnable {
@Override
public void run() {
for (count=startCount; count<20; count++){
if(isStop){
break;
}
try {
Thread.sleep(1000);
Log.d(TAG, "run: count : "+count);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
@Override
public void onDestroy() {
super.onDestroy();
isStop = true;
Log.d(TAG, "onDestroy: 서비스 파괴");
}
}
'Android' 카테고리의 다른 글
Android Studio ] 리사이클러뷰의 어댑터, 뷰홀더를 다른 파일로 만들어보기 (0) | 2020.10.28 |
---|---|
[Android Studio, Java] (간단하게) SharedPreferenced가 무엇일까? (0) | 2020.10.20 |
Android Studio, JAVA] 파이어베이스 등록하기 (0) | 2020.08.13 |
Android Studio, JAVA] mvvc 모델과 LiveData (2) | 2020.08.12 |
Android Studio] MVVM 패턴 (0) | 2020.08.05 |