본문 바로가기
안드로이드 코딩일지

[Android] WorkManager 'Could not instantiate Worker' 해결법

by HAERO_KR 2021. 2. 24.

Android JetPack Component 에 'WorkManager' 라는 친구가 있다. 초기 안드로이드는 백그라운드 실행 정책이 느슨했기 때문에 여러 앱이 각자 백그라운드 태스크를 수행하면서 스마트폰의 자원을 엄청 소비하며 최악의 상황에 다다르기도 했다. 그래서 버전이 올라가면 올라갈 수록 앱의 백그라운드 실행 정책이 까다로워지게 되었다. 하지만 엄격해진 백그라운드 정책에도, 몇 가지 제약 조건에 부합하고 사용자의 기기 상태를 존중한다면 (잠자기 모드에 있는 스마트폰을 깨우지 않는 등) 손쉽게 백그라운드 태스크를 등록할 수 있게끔 도와주는 녀석이 바로 'WorkManager' 다. 2018년 구글 I/O 에서 처음 공개되었다.

 

WorkManager 를 활용하여 특정 태스크 (백그라운드에서 실행될 동작) 을 등록하게 된다면 해당 앱을 꺼도 (프로세스 킬 하여도), 스마트폰을 재부팅해도 해당 태스크의 수행이 보장된다. 게다가 체이닝(Chaining) 도 지원하여, 가령 작업 A 가 끝난 뒤 A 의 결과에 따라 작업 B, C 등 다른 태스크를 이어서 수행해야 한다면 A 의 작업 결과에 따라 손쉽게 동작 흐름을 이어갈 수 있도록 해준다.

 

WorkManager 에 대한 자세한 사항은 아래 링크를 클릭하면 참고할 수 있다.

 

WorkManager로 작업 예약  |  Android 개발자  |  Android Developers

WorkManager로 작업 예약  Android Jetpack의 일부 WorkManager는 지연 가능한 비동기 작업을 쉽게 예약할 수 있는 API로, 지연 가능한 비동기 작업은 앱이 종료되거나 기기가 다시 시작되더라도 실행될 것

developer.android.com


그러나, WorkManager 에 아직 이슈가 있는 것 같다.

필자도 WorkManager 를 통해 백그라운드에서 가벼운 태스크를 수행할 수 있도록 Worker 인스턴스를 생성하고, 

WorkRequestBuilder 를 활용하여 시스템에 WorkRequest 를 등록(제출) 하려고 했는데

 

WorkManager.getInstance(context).enqueue(myRequest) 를 하였더니 아래와 같은 오류가 로그에 찍혔다.

2018-10-04 22:25:47.004 28669-28885/app.package.com.debug E/DefaultWorkerFactory: Could not instantiate app.package.com.MyWorker
    java.lang.NoSuchMethodException: <init> []
        at java.lang.Class.getConstructor0(Class.java:2320)
        at java.lang.Class.getDeclaredConstructor(Class.java:2166)
        at androidx.work.DefaultWorkerFactory.createWorker(DefaultWorkerFactory.java:58)
        at androidx.work.impl.WorkerWrapper.runWorker(WorkerWrapper.java:180)
        at androidx.work.impl.WorkerWrapper.run(WorkerWrapper.java:117)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1162)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:636)
        at java.lang.Thread.run(Thread.java:764)
2018-10-04 22:25:47.005 28669-28885/app.package.com.debug E/WorkerWrapper: Could for create Worker app.package.com.MyWorker

공식 문서를 따라 제대로 Worker 클래스를 정의하였고, Context, WorkerParameters 이외에 개발자가 따로 정의한 파라미터도 만들면 안된다고 해서 공식 문서 그대로의 Worker 클래스 형태를 유지하며 동작을 정의했는데도 WorkRequest 를 등록하는 과정에서 위와 같은 오류를 뿜었다. 그렇다고 해서 WorkerRequestBuilder 가 잘못된 문법으로 쓰여져있던 것도 아니고, 100번 다시 봐도 오타 또한 없었다.

 

한동안 삽질을 하다가, StackOverFlow 에서 같은 문제를 겪고 있다는 사람의 글을 보게 되었다.

 

Android Work Manager: "Could not instantiate Worker"

I've followed the Android Developer's tutorial on using the Worker Manager structure to run my code in background but anytime I try to enqueue my worker it doesn't run and I get the following error:

stackoverflow.com

 

그리고 답변들을 읽어보며 모두 시도해보았는데, 결정적인 해결책을 찾을 수 있었다.

 

Tony 씨는 나의 생명의 은인이자 구세주다.

오류가 발생했던 원인은 바로, Activity 클래스 내부에 Inner Class 형태로 중첩하여 Worker 클래스를 정의하였기 때문이었다. Worker 클래스를 별도의 파일로 분리하였더니, 정상 동작하였다. 이번에도 역시나 허무하게 해결되었다.

 

공식 문서에 관련한 이야기가 없었기에 Nested Class 로 Worker 를 정의해두었다고 오류를 뿜을 줄은 상상도 못했다. 사실 해결해놓고도 이게 그렇게까지 문제가 될 부분인지 이해가 잘 되지 않는다.

 

물론, WorkerManager 관련 예제들을 살펴보면 모두 파일을 분리하여 Worker 를 정의해두었다. 하지만 필자가 이렇게 구현한 이유는, Worker 클래스를 정의할 때 정해진 파라미터 이외의 사용자 (개발자) 임의 파라미터는 지원하지 않는다는 글을 보았기 때문이다. 이는 사실이 맞았다. 아래와 같이 Context, WorkerParameters 이외의 파라미터는 만들어선 안된다. (공식 문서 예제 코드이다.)

class UploadWorker(appContext: Context, workerParams: WorkerParameters):
       Worker(appContext, workerParams) {
   override fun doWork(): Result {

       // Do the work here--in this case, upload the images.
       uploadImages()

       // Indicate whether the work finished successfully with the Result
       return Result.success()
   }
}

 

그래서 필자는 구현해야 하는 doWork() 메소드에 들어갈, 즉 백그라운드에서 수행할 태스크를 정의할 때 Activity 에서 핸들링되고 있는 특정 변수값이 필요했기 때문에 편의상 Activity 의 Class 내부에 Worker 클래스를 구현했던 것이다. 이렇게 구현하면 Activity 에서 사용중인 멤버 변수를 함께 사용할 수 있을거란 생각이었다.

 

하지만 알고보니 이렇게 하면 안되고, Worker 클래스에 데이터를 넘겨주는 기능이 내장되어 있었다.. (충격과 공포)

공식 문서 깊이 깊이 들어가면 아래와 같은 내용이 있었다.

 

WorkManager 고급 주체  |  Android 개발자  |  Android Developers

WorkManager를 사용하면 정교한 작업 요청을 쉽게 설정하고 예약할 수 있습니다. 다음과 같은 시나리오에 API를 사용할 수 있습니다. 지정된 순서로 실행되는 체인으로 연결된 시퀀스 작업 앱에서

developer.android.com

 

WorkerRequestBuilder 를 통해 사전에 정의해둔 Worker 클래스를 인스턴스화 시키고 인스턴스화 된 Worker 를 활용하여 WorkerRequest 객체를 빌드하기 전에, Worker 의 doWork() 안에서 활용할 수 있는 데이터를 삽입할 수 있다.

 

 

1. 아래와 같이 Worker 클래스에서 활용하게 될 데이터를 담은 Data 라는 객체를 만든 후,
   WorkRequestBuilder 에서 제공하는 메소드인 setInputData() 를 통해 삽입한다.

val myData: Data = workDataOf("KEY_X_ARG" to 42,
                       "KEY_Y_ARG" to 421,
                       "KEY_Z_ARG" to 8675309)

// ...then create and enqueue a OneTimeWorkRequest that uses those arguments
val mathWork = OneTimeWorkRequestBuilder<MathWorker>()
        .setInputData(myData)
        .build()
WorkManager.getInstance(myContext).enqueue(mathWork)

 

2. Worker 클래스의 doWork() 메소드 내에서 아래와 같이 inputData.getInt() 등의 메소드로 데이터를 꺼내어 활용한다.
    Result.success() 메소드에 Output Data 또한 생성하여 함께 내보낼 수 있다.

// Define the parameter keys:
const val KEY_X_ARG = "X"
const val KEY_Y_ARG = "Y"
const val KEY_Z_ARG = "Z"

// ...and the result key:
const val KEY_RESULT = "result"

// Define the Worker class:
class MathWorker(context : Context, params : WorkerParameters)
    : Worker(context, params)  {

    override fun doWork(): Result {
        val x = inputData.getInt(KEY_X_ARG, 0)
        val y = inputData.getInt(KEY_Y_ARG, 0)
        val z = inputData.getInt(KEY_Z_ARG, 0)

        // ...do the math...
        val result = myLongCalculation(x, y, z);

        //...set the output, and we're done!
        val output: Data = workDataOf(KEY_RESULT to result)

        return Result.success(output)
    }
}

위 코드는 모두 구글 공식문서의 예제 코드이다.

 

이러한 방식으로 데이터를 주고받을 수 있는 것이었다. 공식 문서 깊숙히 존재하는 내용이었어서, 처음엔 인지하지 못하고 비효율적으로 코드를 작성하여 괜한 오류를 겪게 되었던 것 같다. 물론, 공식 문서 앞부분에 'Nested Class 로 Worker 를 정의해서는 안됩니다.' 한 줄만 있었어도 Worker 클래스와 데이터를 주고받는 동작을 구현하기 위해 공식 문서를 더 자세히 찾아보게 되었을 것 같다. (어김없이 불친절하다)

 

아무튼, 이번에는 StackOverFlow 에게서 큰 도움을 받게 되었다. 해결하게 되어 다행이다. 만약 같은 오류가 발생하는 사람들이 구글링을 하던 중 이 글을 발견하게 되어 오류 해결에 도움이 되었으면 좋겠다.


추가적으로...

아직도 Nested Class 로 정의해선 안되는 이유를 잘 모르겠어서 구글링을 하다보니 다음과 같은 StackOverFlow 답변을 보았다. 

 

대충 해석해보면, WorkerFactory 자체가 Reflection 기법으로 Worker 클래스를 찾기 때문에 Worker 자체가 공적이어야 하고 Inner Class 등의 형태가 되어선 안된다는 뜻이다. 자바 (코틀린) 에서 기본적으로 제공하는 API 인 리플렉션이란, 클래스나 인터페이스 등을 찾아 객체를 생성하거나 메소드를 호출하는 등의 동작을 의미한다. Worker 를 생성하는 원리가 바로 이 Reflection 을 통한 것이기 때문에 Nested(Inner) Class 로 선언해선 안 되는 것이었다. 물론 공식문서에는 관련한 내용이 일절 없다. (이제 편안해졌다)