Android 스레딩: 알아야 할 모든 것

게시 됨: 2022-03-11

모든 Android 개발자는 어느 시점에서 애플리케이션의 스레드를 처리해야 합니다.

애플리케이션이 Android에서 시작되면 "메인" 스레드라고 하는 첫 번째 실행 스레드가 생성됩니다. 기본 스레드는 적절한 사용자 인터페이스 위젯에 이벤트를 전달하고 Android UI 툴킷의 구성 요소와 통신하는 역할을 합니다.

애플리케이션의 응답성을 유지하려면 메인 스레드를 사용하여 차단 상태를 유지할 수 있는 작업을 수행하지 않는 것이 중요합니다.

네트워크 작업 및 데이터베이스 호출, 특정 구성 요소 로드는 기본 스레드에서 피해야 하는 작업의 일반적인 예입니다. 메인 스레드에서 호출될 때 동기적으로 호출됩니다. 즉, 작업이 완료될 때까지 UI가 완전히 응답하지 않는 상태로 유지됩니다. 이러한 이유로 일반적으로 별도의 스레드에서 수행되므로 수행되는 동안 UI 차단을 방지합니다(즉, UI에서 비동기적으로 수행됨).

Android는 스레드를 만들고 관리하는 다양한 방법을 제공하며 스레드 관리를 훨씬 더 즐겁게 해주는 많은 타사 라이브러리가 있습니다. 그러나 접근 방식이 매우 다양하기 때문에 올바른 접근 방식을 선택하는 것이 매우 혼란스러울 수 있습니다.

이 문서에서는 스레딩이 필수가 되는 Android 개발의 몇 가지 일반적인 시나리오와 이러한 시나리오에 적용할 수 있는 몇 가지 간단한 솔루션 등에 대해 알아봅니다.

안드로이드의 스레딩

Android에서는 모든 스레딩 구성 요소를 두 가지 기본 범주로 분류할 수 있습니다.

  1. 액티비티/프래그먼트 연결된 스레드: 이 스레드는 액티비티/프래그먼트의 수명 주기에 연결되어 있으며 액티비티/프래그먼트가 파괴되는 즉시 종료됩니다.
  2. 활동/조각에 연결 되지 않은 스레드: 이러한 스레드는 생성된 활동/조각(있는 경우)의 수명을 초과하여 계속 실행할 수 있습니다.

활동/프래그먼트에 연결하는 스레딩 구성 요소

비동기 작업

AsyncTask 는 스레딩을 위한 가장 기본적인 Android 구성 요소입니다. 사용이 간편하고 기본 시나리오에 적합합니다.

샘플 사용법:

 public class ExampleActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); new MyTask().execute(url); } private class MyTask extends AsyncTask<String, Void, String> { @Override protected String doInBackground(String... params) { String url = params[0]; return doSomeWork(url); } @Override protected void onPostExecute(String result) { super.onPostExecute(result); // do something with result } } }

그러나 AsyncTask 는 지연된 작업이 활동/조각의 수명을 넘어서 실행해야 하는 경우 부족합니다. 화면 회전과 같은 간단한 작업으로도 활동이 파괴될 수 있다는 점은 주목할 가치가 있습니다.

로더

로더는 위에서 언급한 문제에 대한 솔루션입니다. 로더는 활동이 소멸되면 자동으로 중지될 수 있으며 활동이 다시 생성된 후에도 스스로 다시 시작할 수 있습니다.

AsyncTaskLoaderCursorLoader 의 두 가지 유형의 로더가 있습니다. 이 기사 뒷부분에서 CursorLoader 에 대해 자세히 알아볼 것입니다.

AsyncTaskLoaderAsyncTask 와 비슷하지만 조금 더 복잡합니다.

샘플 사용법:

 public class ExampleActivity extends Activity{ @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); getLoaderManager().initLoader(1, null, new MyLoaderCallbacks()); } private class MyLoaderCallbacks implements LoaderManager.LoaderCallbacks { @Override public Loader onCreateLoader(int id, Bundle args) { return new MyLoader(ExampleActivity.this); } @Override public void onLoadFinished(Loader loader, Object data) { } @Override public void onLoaderReset(Loader loader) { } } private class MyLoader extends AsyncTaskLoader { public MyLoader(Context context) { super(context); } @Override public Object loadInBackground() { return someWorkToDo(); } } }

활동/프래그먼트에 연결하지 않는 스레딩 구성 요소

서비스

Service 는 UI 없이 긴(또는 잠재적으로 긴) 작업을 수행하는 데 유용한 구성 요소입니다.

Service 는 호스팅 프로세스의 메인 스레드에서 실행됩니다. 서비스는 자체 스레드를 생성하지 않으며 별도로 지정하지 않는 한 별도의 프로세스에서 실행되지 않습니다.

샘플 사용법:

 public class ExampleService extends Service { @Override public int onStartCommand(Intent intent, int flags, int startId) { doSomeLongProccesingWork(); stopSelf(); return START_NOT_STICKY; } @Nullable @Override public IBinder onBind(Intent intent) { return null; } }

Service 를 사용하면 stopSelf() 또는 stopService() 메서드를 호출하여 작업이 완료되면 서비스를 중지 책임이 있습니다.

인텐트 서비스

Service 와 마찬가지로 IntentService 는 별도의 스레드에서 실행되며 작업이 완료된 후 자동으로 중지됩니다.

IntentService 는 일반적으로 UI에 연결할 필요가 없는 짧은 작업에 사용됩니다.

샘플 사용법:

 public class ExampleService extends IntentService { public ExampleService() { super("ExampleService"); } @Override protected void onHandleIntent(Intent intent) { doSomeShortWork(); } }

Android의 7가지 스레딩 패턴

Use Case No. 1: 서버의 응답 없이 네트워크를 통해 요청하기

때로는 응답에 대해 걱정할 필요 없이 API 요청을 서버에 보내고 싶을 수 있습니다. 예를 들어 푸시 등록 토큰을 애플리케이션의 백엔드로 보낼 수 있습니다.

여기에는 네트워크를 통한 요청이 포함되므로 메인 스레드가 아닌 다른 스레드에서 요청해야 합니다.

옵션 1: AsyncTask 또는 로더

호출을 위해 AsyncTask 또는 로더를 사용할 수 있으며 작동합니다.

그러나 AsyncTask 와 로더는 모두 활동의 수명 주기에 따라 다릅니다. 즉, 호출이 실행될 때까지 기다렸다가 사용자가 활동을 떠나는 것을 방지하거나 활동이 파괴되기 전에 실행되기를 바라야 합니다.

옵션 2: 서비스

Service 는 어떤 활동에도 연결되어 있지 않으므로 이 사용 사례에 더 적합할 수 있습니다. 따라서 활동이 소멸된 후에도 네트워크 호출을 계속할 수 있습니다. 또한 서버의 응답이 필요하지 않기 때문에 서비스도 여기서 제한되지 않습니다.

그러나 서비스가 UI 스레드에서 실행되기 시작하므로 여전히 스레딩을 직접 관리해야 합니다. 또한 네트워크 호출이 완료되면 서비스가 중지되었는지 확인해야 합니다.

이렇게 하려면 그러한 간단한 작업에 필요한 것보다 더 많은 노력이 필요합니다.

옵션 3: IntentService

제 생각에는 이것이 최선의 선택이 될 것입니다.

IntentService 는 어떤 활동에도 연결되지 않고 UI가 아닌 스레드에서 실행되기 때문에 여기에서 우리의 요구를 완벽하게 충족합니다. 또한 IntentService 는 자동으로 중지되므로 수동으로 관리할 필요도 없습니다.

Use Case No. 2: 네트워크 호출 및 서버 응답 받기

이 사용 사례는 아마도 좀 더 일반적일 것입니다. 예를 들어 백엔드에서 API를 호출하고 해당 응답을 사용하여 화면의 필드를 채울 수 있습니다.

옵션 1: 서비스 또는 IntentService

Service 또는 IntentService 가 이전 사용 사례에서 잘 작동했지만 여기에서 사용하는 것은 좋은 생각이 아닙니다. Service 또는 IntentService 에서 기본 UI 스레드로 데이터를 가져오려고 하면 상황이 매우 복잡해집니다.

옵션 2: AsyncTask 또는 로더

처음에는 얼굴을 AsyncTask 또는 로더가 여기에서 확실한 솔루션으로 보일 것입니다. 사용하기 쉽고 간단합니다.

그러나 AsyncTask 또는 로더를 사용할 때 일부 상용구 코드를 작성할 필요가 있음을 알 수 있습니다. 또한 이러한 구성 요소에서는 오류 처리가 주요 작업이 됩니다. 간단한 네트워킹 호출로도 잠재적인 예외를 인식하고 포착하여 그에 따라 조치를 취해야 합니다. 이렇게 하면 가능한 오류 정보와 함께 데이터가 포함된 사용자 정의 클래스에 응답을 래핑해야 하며 플래그는 작업의 성공 여부를 나타냅니다.

모든 단일 호출에 대해 수행해야 하는 작업이 상당히 많습니다. 다행스럽게도 이제 훨씬 더 우수하고 간단한 솔루션인 RxJava를 사용할 수 있습니다.

옵션 3: RxJava

Netflix에서 개발한 라이브러리인 RxJava에 대해 들어본 적이 있을 것입니다. Java에서는 거의 마술과 같습니다.

RxAndroid를 사용하면 Android에서 RxJava를 사용할 수 있으며 비동기 작업을 쉽게 처리할 수 있습니다. 여기에서 Android의 RxJava에 대해 자세히 알아볼 수 있습니다.

RxJava는 ObserverSubscriber 라는 두 가지 구성 요소를 제공합니다.

관찰자 는 어떤 동작을 포함하는 구성 요소입니다. 해당 작업을 수행하고 성공하면 결과를 반환하고 실패하면 오류를 반환합니다.

반면에 구독자 는 구독을 통해 옵저버블에서 결과(또는 오류)를 수신할 수 있는 구성 요소입니다.

RxJava를 사용하여 먼저 관찰 가능 항목을 만듭니다.

 Observable.create((ObservableOnSubscribe<Data>) e -> { Data data = mRestApi.getData(); e.onNext(data); })

Observable이 생성되면 구독할 수 있습니다.

RxAndroid 라이브러리를 사용하면 옵저버블에서 작업을 실행하려는 스레드와 응답(예: 결과 또는 오류)을 얻으려는 스레드를 제어할 수 있습니다.

다음 두 가지 기능을 사용하여 관찰 가능 항목을 연결합니다.

 .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()

스케줄러는 특정 스레드에서 작업을 실행하는 구성 요소입니다. AndroidSchedulers.mainThread() 는 메인 스레드와 연결된 스케줄러입니다.

API 호출이 mRestApi.getData() 이고 Data 객체를 반환하는 경우 기본 호출은 다음과 같을 수 있습니다.

 Observable.create((ObservableOnSubscribe<Data>) e -> { try { Data data = mRestApi.getData(); e.onNext(data); } catch (Exception ex) { e.onError(ex); } }) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(match -> Log.i(“rest api, "success"), throwable -> Log.e(“rest api, "error: %s" + throwable.getMessage()));

RxJava 사용의 다른 이점을 살펴보지 않고도 RxJava를 사용하여 스레딩의 복잡성을 추상화하여 보다 성숙한 코드를 작성할 수 있는 방법을 이미 알 수 있습니다.

사용 사례 3: 네트워크 호출 연결

순서대로 수행되어야 하는 네트워크 호출의 경우(즉, 각 작업이 이전 작업의 응답/결과에 따라 달라지는 경우) 스파게티 코드 생성에 특히 주의해야 합니다.

예를 들어, 다른 API 호출을 통해 먼저 가져와야 하는 토큰으로 API 호출을 해야 할 수 있습니다.

옵션 1: AsyncTask 또는 로더

AsyncTask 또는 로더를 사용하면 거의 확실히 스파게티 코드가 생성됩니다. 전체 기능을 제대로 사용하기 어렵고 프로젝트 전체에 걸쳐 엄청난 양의 중복 상용구 코드가 필요합니다.

옵션 2: flatMap을 사용하는 RxJava

RxJava에서 flatMap 연산자는 소스 옵저버블에서 방출된 값을 가져오고 다른 옵저버블을 반환합니다. 옵저버블을 생성한 다음 첫 번째 옵저버블에서 방출된 값을 사용하여 다른 옵저버블을 생성하면 기본적으로 연결됩니다.

1단계. 토큰을 가져오는 옵저버블을 생성합니다.

 public Observable<String> getTokenObservable() { return Observable.create(subscriber -> { try { String token = mRestApi.getToken(); subscriber.onNext(token); } catch (IOException e) { subscriber.onError(e); } }); }

2단계. 토큰을 사용하여 데이터를 가져오는 옵저버블을 만듭니다.

 public Observable<String> getDataObservable(String token) { return Observable.create(subscriber -> { try { Data data = mRestApi.getData(token); subscriber.onNext(data); } catch (IOException e) { subscriber.onError(e); } }); }

3단계 . 두 개의 옵저버블을 함께 연결하고 구독합니다.

 getTokenObservable() .flatMap(new Function<String, Observable<Data>>() { @Override public Observable<Data> apply(String token) throws Exception { return getDataObservable(token); } }) .subscribe(data -> { doSomethingWithData(data) }, error -> handleError(e));

이 접근 방식의 사용은 네트워크 호출에만 국한되지 않습니다. 시퀀스로 실행해야 하지만 별도의 스레드에서 실행해야 하는 모든 작업 세트와 함께 작동할 수 있습니다.

위의 모든 사용 사례는 매우 간단합니다. 스레드 간 전환은 각 작업이 완료된 후에만 발생했습니다. 예를 들어 두 개 이상의 스레드가 서로 적극적으로 통신해야 하는 고급 시나리오도 이 접근 방식으로 지원할 수 있습니다.

사용 사례 4: 다른 스레드의 UI 스레드와 통신

파일을 업로드하고 완료되면 사용자 인터페이스를 업데이트하려는 시나리오를 고려하십시오.

파일을 업로드하는 데 시간이 오래 걸릴 수 있으므로 사용자를 기다리게 할 필요가 없습니다. 여기에서 기능을 구현하기 위해 서비스 및 아마도 IntentService 를 사용할 수 있습니다.

그러나 이 경우 더 큰 문제는 파일 업로드(별도의 스레드에서 수행됨)가 완료된 후 UI 스레드에서 메서드를 호출할 수 있다는 것입니다.

옵션 1: 서비스 내부의 RxJava

RxJava는 그 자체로 또는 IntentService 내부에서 이상적이지 않을 수 있습니다. Observable 을 구독할 때 콜백 기반 메커니즘을 사용해야 하며 IntentService 는 콜백이 아닌 간단한 동기 호출을 수행하도록 빌드되었습니다.

반면에 Service 를 사용하면 서비스를 수동으로 중지해야 하므로 더 많은 작업이 필요합니다.

옵션 2: BroadcastReceiver

Android는 글로벌 이벤트(예: 배터리 이벤트, 네트워크 이벤트 등)와 사용자 지정 이벤트를 수신할 수 있는 이 구성 요소를 제공합니다. 이 구성 요소를 사용하여 업로드가 완료될 때 트리거되는 사용자 지정 이벤트를 만들 수 있습니다.

이렇게 하려면 BroadcastReceiver 를 확장하는 사용자 지정 클래스를 만들고 매니페스트에 등록하고 IntentIntentFilter 를 사용하여 사용자 지정 이벤트를 만들어야 합니다. 이벤트를 트리거하려면 sendBroadcast 메서드가 필요합니다.

명백한:

 <receiver android:name="UploadReceiver"> <intent-filter> <action android:name="com.example.upload"> </action> </intent-filter> </receiver>

수화기:

 public class UploadReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { if (intent.getBoolean(“success”, false) { Activity activity = (Activity)context; activity.updateUI(); } }

보내는 사람:

 Intent intent = new Intent(); intent.setAction("com.example.upload"); sendBroadcast(intent);

이 접근 방식은 실행 가능한 옵션입니다. 그러나 눈치채셨겠지만 약간의 작업이 필요하고 브로드캐스트가 너무 많으면 속도가 느려질 수 있습니다.

옵션 3: 핸들러 사용

Handler 는 스레드에 연결한 다음 간단한 메시지나 Runnable 작업을 통해 해당 스레드에서 일부 작업을 수행하도록 만들 수 있는 구성 요소입니다. 특정 스레드에서 메시지 처리를 담당하는 다른 구성 요소인 Looper 와 함께 작동합니다.

Handler 가 생성되면 핸들러가 연결된 스레드를 나타내는 생성자에서 Looper 객체를 얻을 수 있습니다. 메인 스레드에 연결된 핸들러를 사용하려면 Looper.getMainLooper() 를 호출하여 메인 스레드와 연결된 루퍼를 사용해야 합니다.

이 경우 백그라운드 스레드에서 UI를 업데이트하려면 UI 스레드에 연결된 핸들러를 만든 다음 Runnable 로 작업을 게시할 수 있습니다.

 Handler handler = new Handler(Looper.getMainLooper()); handler.post(new Runnable() { @Override public void run() { // update the ui from here } });

이 방법은 첫 번째 방법보다 훨씬 낫지만 훨씬 더 간단한 방법이 있습니다.

옵션 3: EventBus 사용

GreenRobot의 인기 라이브러리인 EventBus 를 사용하면 구성 요소가 서로 안전하게 통신할 수 있습니다. 우리의 사용 사례는 UI만 업데이트하려는 경우이므로 이것이 가장 간단하고 안전한 선택이 될 수 있습니다.

1단계. 이벤트 클래스를 생성합니다. 예: UIEvent .

2단계. 이벤트를 구독하세요.

 @Subscribe(threadMode = ThreadMode.MAIN) public void onUIEvent(UIEvent event) {/* Do something */}; register and unregister eventbus : @Override public void onStart() { super.onStart(); EventBus.getDefault().register(this); } @Override public void onStop() { super.onStop(); EventBus.getDefault().unregister(this); }

3단계 . 이벤트 게시: EventBus.getDefault().post(new UIEvent());

주석의 ThreadMode 매개변수를 사용하여 이 이벤트를 구독하려는 스레드를 지정합니다. 여기 예제에서는 이벤트 수신자가 UI를 업데이트할 수 있기를 원하기 때문에 메인 스레드를 선택합니다.

필요에 따라 추가 정보를 포함하도록 UIEvent 클래스를 구성할 수 있습니다.

서비스에서:

 class UploadFileService extends IntentService { // … Boolean success = uploadFile(File file); EventBus.getDefault().post(new UIEvent(success)); // ... }

활동/조각에서:

 @Subscribe(threadMode = ThreadMode.MAIN) public void onUIEvent(UIEvent event) {//show message according to the action success};

EventBus library 를 사용하면 스레드 간의 통신이 훨씬 간단해집니다.

Use Case No. 5: 사용자 액션에 기반한 쓰레드 간 양방향 통신

미디어 플레이어를 구축 중이고 응용 프로그램 화면이 닫힌 경우에도 음악을 계속 재생할 수 있기를 원한다고 가정합니다. 이 시나리오에서는 UI가 미디어 스레드(예: 재생, 일시 중지 및 기타 작업)와 통신할 수 있기를 원하고 미디어 스레드가 특정 이벤트(예: 오류, 버퍼링 상태)를 기반으로 UI를 업데이트하기를 원할 것입니다. , 등).

전체 미디어 플레이어 예제는 이 문서의 범위를 벗어납니다. 그러나 여기와 여기에서 좋은 자습서를 찾을 수 있습니다.

옵션 1: EventBus 사용

여기에서 EventBus 를 사용할 수 있습니다. 그러나 UI 스레드에서 이벤트를 게시하고 서비스에서 수신하는 것은 일반적으로 안전하지 않습니다. 메시지를 보낼 때 서비스가 실행 중인지 알 수 있는 방법이 없기 때문입니다.

옵션 2: BoundService 사용

BoundService 는 액티비티/프래그먼트에 바인딩된 Service 입니다. 즉, 활동/조각은 서비스가 실행 중인지 여부를 항상 알고 있으며 서비스의 공개 메서드에 대한 액세스 권한도 얻습니다.

이를 구현하려면 서비스 내부에 사용자 정의 Binder 를 만들고 서비스를 반환하는 메서드를 만들어야 합니다.

 public class MediaService extends Service { private final IBinder mBinder = new MediaBinder(); public class MediaBinder extends Binder { MediaService getService() { // Return this instance of LocalService so clients can call public methods return MediaService.this; } } @Override public IBinder onBind(Intent intent) { return mBinder; } }

액티비티를 서비스에 바인딩하려면 서비스 상태를 모니터링하는 클래스인 ServiceConnection 을 구현하고 bindService 메서드를 사용하여 바인딩을 만들어야 합니다.

 // in the activity MediaService mService; // flag indicates the bound status boolean mBound; @Override protected void onStart() { super.onStart(); // Bind to LocalService Intent intent = new Intent(this, MediaService.class); bindService(intent, mConnection, Context.BIND_AUTO_CREATE); } private ServiceConnection mConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName className, IBinder service) { MediaBinder binder = (MediaBinder) service; mService = binder.getService(); mBound = true; } @Override public void onServiceDisconnected(ComponentName arg0) { mBound = false; } };

여기에서 전체 구현 예를 찾을 수 있습니다.

사용자가 재생 또는 일시 중지 버튼을 탭할 때 서비스와 통신하려면 서비스에 바인딩한 다음 서비스에서 관련 공용 메서드를 호출할 수 있습니다.

미디어 이벤트가 있고 이를 액티비티/프래그먼트에 다시 전달하려는 경우 이전 기술(예: BroadcastReceiver , Handler 또는 EventBus ) 중 하나를 사용할 수 있습니다.

사용 사례 6: 작업을 병렬로 실행하고 결과 얻기

관광 앱을 만들고 여러 소스(다른 데이터 공급자)에서 가져온 지도에 명소를 표시하려고 한다고 가정해 보겠습니다. 모든 소스가 신뢰할 수 있는 것은 아니므로 실패한 소스를 무시하고 어쨌든 맵을 계속 렌더링하는 것이 좋습니다.

프로세스를 병렬화하려면 각 API 호출이 다른 스레드에서 발생해야 합니다.

옵션 1: RxJava 사용

RxJava에서는 merge() 또는 concat() 연산자를 사용하여 여러 관찰 가능 항목을 하나로 결합할 수 있습니다. 그런 다음 "병합된" 옵저버블을 구독하고 모든 결과를 기다릴 수 있습니다.

그러나 이 접근 방식은 예상대로 작동하지 않습니다. 하나의 API 호출이 실패하면 병합된 관찰 가능 항목이 전체 실패를 보고합니다.

옵션 2: 기본 Java 구성 요소 사용

Java의 ExecutorService 는 고정된(구성 가능한) 스레드 수를 만들고 스레드에서 동시에 작업을 실행합니다. 서비스는 invokeAll() 메서드를 통해 결국 모든 결과를 반환하는 Future 객체를 반환합니다.

ExecutorService 에 보내는 각 작업은 예외를 던질 수 있는 작업을 생성하기 위한 인터페이스인 Callable 인터페이스에 포함되어야 합니다.

invokeAll() 에서 결과를 얻으면 모든 결과를 확인하고 그에 따라 진행할 수 있습니다.

예를 들어 세 가지 다른 끝점에서 들어오는 세 가지 매력 유형이 있고 세 개의 병렬 호출을 수행하려고 한다고 가정해 보겠습니다.

 ExecutorService pool = Executors.newFixedThreadPool(3); List<Callable<Object>> tasks = new ArrayList<>(); tasks.add(new Callable<Object>() { @Override public Integer call() throws Exception { return mRest.getAttractionType1(); } }); // ... try { List<Future<Object>> results = pool.invokeAll(tasks); for (Future result : results) { try { Object response = result.get(); if (response instance of AttractionType1... {} if (response instance of AttractionType2... {} ... } catch (ExecutionException e) { e.printStackTrace(); } } } catch (InterruptedException e) { e.printStackTrace(); }

이런 식으로 모든 작업을 병렬로 실행합니다. 따라서 각 작업의 오류를 개별적으로 확인하고 개별 오류를 적절하게 무시할 수 있습니다.

이 접근 방식은 RxJava를 사용하는 것보다 쉽습니다. 더 간단하고 짧으며 한 가지 예외로 인해 모든 작업이 실패하지 않습니다.

사용 사례 #7: 로컬 SQLite 데이터베이스 쿼리

로컬 SQLite 데이터베이스를 다룰 때는 데이터베이스 호출(특히 대규모 데이터베이스 또는 복잡한 쿼리)에 시간이 많이 소요되어 UI가 정지될 수 있으므로 백그라운드 스레드에서 데이터베이스를 사용하는 것이 좋습니다.

SQLite 데이터를 쿼리할 때 실제 데이터를 가져오는 데 사용할 수 있는 Cursor 개체를 얻습니다.

 Cursor cursor = getData(); String name = cursor.getString(<colum_number>);

옵션 1: RxJava 사용

백엔드에서 데이터를 가져오는 것처럼 RxJava를 사용하여 데이터베이스에서 데이터를 가져올 수 있습니다.

 public Observable<Cursor> getLocalDataObservable() { return Observable.create(subscriber -> { Cursor cursor = mDbHandler.getData(); subscriber.onNext(cursor); }); }

다음과 같이 getLocalDataObservable() 에서 반환된 관찰 가능 항목을 사용할 수 있습니다.

 getLocalDataObservable() .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(cursor -> String name = cursor.getString(0), throwable -> Log.e(“db, "error: %s" + throwable.getMessage()));

이것은 확실히 좋은 접근 방식이지만 바로 이 시나리오를 위해 구축된 구성 요소가 있기 때문에 훨씬 더 나은 접근 방식이 있습니다.

옵션 2: CursorLoader + ContentProvider 사용

Android는 SQLite 데이터를 로드하고 해당 스레드를 관리하기 위한 기본 구성 요소인 CursorLoader 를 제공합니다. getString() , getLong() 등과 같은 간단한 메서드를 호출하여 데이터를 가져오는 데 사용할 수 있는 Cursor 를 반환하는 Loader 입니다.

 public class SimpleCursorLoader extends FragmentActivity implements LoaderManager.LoaderCallbacks<Cursor> { public static final String TAG = SimpleCursorLoader.class.getSimpleName(); private static final int LOADER_ID = 0x01; private TextView textView; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.simple_cursor_loader); textView = (TextView) findViewById(R.id.text_view); getSupportLoaderManager().initLoader(LOADER_ID, null, this); } public Loader<Cursor> onCreateLoader(int i, Bundle bundle) { return new CursorLoader(this, Uri.parse("content://com.github.browep.cursorloader.data") , new String[]{"col1"}, null, null, null); } public void onLoadFinished(Loader<Cursor> cursorLoader, Cursor cursor) { if (cursor != null && cursor.moveToFirst()) { String text = textView.getText().toString(); while (cursor.moveToNext()) { text += "<br />" + cursor.getString(1); cursor.moveToNext(); } textView.setText(Html.fromHtml(text) ); } } public void onLoaderReset(Loader<Cursor> cursorLoader) { } }

CursorLoaderContentProvider 구성 요소와 함께 작동합니다. 이 구성 요소는 개발자가 더 나은 사용자 경험을 훨씬 더 쉽게 구현할 수 있도록 하는 과다한 실시간 데이터베이스 기능(예: 변경 알림, 트리거 등)을 제공합니다.

Android에서 스레딩에 대한 Silver Bullet 솔루션은 없습니다.

Android는 스레드를 처리하고 관리하는 다양한 방법을 제공하지만 그 중 어느 것도 은총알이 아닙니다.

사용 사례에 따라 올바른 스레딩 접근 방식을 선택하면 전체 솔루션을 쉽게 구현하고 이해하는 데 큰 차이를 만들 수 있습니다. 기본 구성 요소는 일부 경우에 적합하지만 모든 경우에는 적합하지 않습니다. 멋진 타사 솔루션에도 동일하게 적용됩니다.

다음 Android 프로젝트에서 작업할 때 이 기사가 유용하기를 바랍니다. Android에서의 스레딩 경험이나 위의 솔루션이 잘 작동하거나 그렇지 않은 사용 사례를 아래 의견에 공유해 주세요.