Android프로그래밍

[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를 사용하는 방법이 가장 간단하고 효과적이므로, 이 방법을 먼저 시도해 보시는 것을 추천드립니다.

error: Content is protected !!