[Android 백그라운드서비스] 여기의 앱 코드로 위의 I/O 호출이 발생합니다. I/O 작업을 트리거하는 코드를 기본 스레드 외부로 이동해야 합니다. android.app.SharedPreferencesImpl.enqueueDiskWrite (SharedPreferencesImpl.java:691) ANR 오류 해결방법
백그라운드 서비스가 동작할 때 특정값들을 SharedPreferences를 사용하여 저장하고 있는데, 일부 스마트폰에서 ANR 문제가 발생되어 해결책을 찾고 있었다. ANR 오류 내용은 다음과 같다.
기본 스레드의 I/O
통계가 있는 이벤트 1개 표시
기본 스레드가 I/O 작업을 수행하는 중입니다. 이 문제는 네트워크 또는 파일 액세스(특히 압축된 파일) 때문일 수 있지만 'ClassLoader.loadClass'와 같은 작업이 원인일 수도 있습니다.
I/O 작업은 예측이 불가능하며 스레드를 오랫동안 차단할 수 있습니다. 기본 스레드에서는 I/O 작업을 피하고 I/O 작업이 기본 스레드가 아닌 별도의 스레드에서 실행되도록 코드를 리팩터링하세요.
그리고 일부 ANR에 대해 파이어베이스에서 가이드라인을 제공해준다. 그럼에도 불구하고 어떻게 해야할지 모르겠을 경우가 발생된다.
libc.so
syscall + 28
android.app.SharedPreferencesImpl$2.run (SharedPreferencesImpl.java:672)
android.app.SharedPreferencesImpl.enqueueDiskWrite (SharedPreferencesImpl.java:691)
여기의 앱 코드로 위의 I/O 호출이 발생합니다. I/O 작업을 트리거하는 코드를 기본 스레드 외부로 이동해야 합니다.
android.app.SharedPreferencesImpl.-$$Nest$menqueueDiskWrite (unavailable)
android.app.SharedPreferencesImpl$EditorImpl.commit (SharedPreferencesImpl.java:604)
com.test.common.SharedPrefProperty.saveSharedPref (SharedPrefProperty.java:1)
이 함수 호출이 타이밍을 예측할 수 없는 I/O 작업을 수행하고 있습니다.
com.test.AndroidManageService$2.onReceive (AndroidManageService.java:676)
android.app.LoadedApk$ReceiverDispatcher$Args.lambda$getRunnable$0$android-app-LoadedApk$ReceiverDispatcher$Args (LoadedApk.java:1809)
com.android.internal.os.ZygoteInit.main (ZygoteInit.java:1019)
기존에는 AsyncTask를 사용중이었는데 안드로이드 11부터 deprecated 되었다고 하여 다른 방법을 찾고 있다.
그래서 혹시나 해서 ChatGpt에게 물어보니 3가지 가이드 라인을 제공해준다.
나의 경우 1번 방법으로 해결하였다.
기본 스레드(Main Thread)에서 I/O 작업을 수행하는 것은 애플리케이션의 성능을 저하시킬 수 있으며, 경우에 따라서는 애플리케이션이 응답하지 않게 될 수도 있습니다. 이를 피하기 위해서는 I/O 작업을 별도의 스레드에서 수행해야 합니다. 이 문제를 해결하기 위해 AsyncTask
또는 ExecutorService
를 사용할 수 있습니다. 여기서는 ExecutorService
를 사용하여 코드를 변경하는 방법을 설명하겠습니다.
다음은 saveSharedPrefPro
메서드를 비동기로 실행하도록 변경하는 예제입니다.
1. ExecutorService
사용
먼저, ExecutorService
를 사용하여 saveSharedPrefPro
메서드를 백그라운드 스레드에서 실행하도록 변경합니다.
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class SharedPrefProperty {
private static final String MY_PM_PREF = "pref";
private static final ExecutorService executor = Executors.newSingleThreadExecutor();
public static void saveSharedPrefPro(Context newBase, String SaveType, String val) {
executor.execute(new Runnable() {
@Override
public void run() {
SharedPreferences sp = newBase.getSharedPreferences(MY_PM_PREF, Activity.MODE_PRIVATE);
SharedPreferences.Editor editor = sp.edit();
editor.putString("fullStartDt", val);
editor.commit();
}
});
}
}
2. AsyncTask
사용 (Deprecated in Android 11)
AsyncTask
는 이제 더 이상 사용되지 않지만, 여전히 많은 기존 코드베이스에서 사용되고 있습니다. 참고로, 새로운 프로젝트에서는 ExecutorService
또는 WorkManager
를 사용하는 것이 좋습니다.
import android.os.AsyncTask;
public class SharedPrefProperty {
private static final String MY_PM_PREF = "my_pm_pref";
public static void saveSharedPrefPro(final Context newBase, final String SaveType, final String val) {
new SaveSharedPrefTask(newBase, SaveType, val).execute();
}
private static class SaveSharedPrefTask extends AsyncTask<Void, Void, Void> {
private Context context;
private String saveType;
private String val;
SaveSharedPrefTask(Context context, String saveType, String val) {
this.context = context;
this.saveType = saveType;
this.val = val;
}
@Override
protected Void doInBackground(Void... voids) {
SharedPreferences sp = context.getSharedPreferences(MY_PM_PREF, Activity.MODE_PRIVATE);
SharedPreferences.Editor editor = sp.edit();
editor.putString("fullStartDt", val);
editor.commit();
return null;
}
}
}
3. WorkManager
사용
WorkManager
는 작업 스케줄링을 위한 Android의 최신 라이브러리입니다. 장기적으로 지속될 작업을 백그라운드에서 실행할 때 특히 유용합니다.
import android.content.Context;
import androidx.work.OneTimeWorkRequest;
import androidx.work.WorkManager;
import androidx.work.Worker;
import androidx.work.WorkerParameters;
public class SharedPrefProperty {
private static final String MY_PM_PREF = "my_pm_pref";
public static void saveSharedPrefPro(Context newBase, String SaveType, String val) {
OneTimeWorkRequest saveRequest = new OneTimeWorkRequest.Builder(SaveSharedPrefWorker.class)
.setInputData(new Data.Builder()
.putString("SaveType", SaveType)
.putString("val", val)
.build())
.build();
WorkManager.getInstance(newBase).enqueue(saveRequest);
}
public static class SaveSharedPrefWorker extends Worker {
public SaveSharedPrefWorker(Context context, WorkerParameters params) {
super(context, params);
}
@Override
public Result doWork() {
Context context = getApplicationContext();
String saveType = getInputData().getString("SaveType");
String val = getInputData().getString("val");
SharedPreferences sp = context.getSharedPreferences(MY_PM_PREF, Activity.MODE_PRIVATE);
SharedPreferences.Editor editor = sp.edit();
editor.putString("fullStartDt", val);
editor.commit();
return Result.success();
}
}
}
이렇게 수정하면 기본 스레드에서 I/O 작업을 수행하지 않게 되어 애플리케이션의 성능 저하를 방지할 수 있습니다. ExecutorService
를 사용하는 방법이 가장 간단하고 효과적이므로, 이 방법을 먼저 시도해 보시는 것을 추천드립니다.