3
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

WorkManager に enqueue されている Worker クラスをアプリのアップデート時にリネームした場合の挙動

Last updated at Posted at 2018-06-22

WorkManager に enqueue されている Worker クラスをアプリのアップデート時にリネームした場合の挙動

結果

WorkerResult.FAILURE 扱いになり、二度と再実行されない
→ enqueue 状態の Worker クラスのリネーム(パッケージ移動を含む)には注意

背景

WorkManager の内部処理を追っていると、Room に Worker の各種情報( WorkSpec )を積んでいることがわかりました。
ふと、 「enqueue されている状態で、Worker の名前がアップデートにより変わったらどうなるんだろう?」 と思い、調査をしました。

アップデート前のコード(例:v1.0.0)

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        setSupportActionBar(toolbar)

        val workManager = WorkManager.getInstance()
        fab.setOnClickListener {
            workManager.enqueue(OneTimeWorkRequest.Builder(Worker1::class.java).build())
            workManager.enqueue(OneTimeWorkRequest.Builder(Worker2::class.java).build())
        }
    }

    class Worker1 : Worker() {
        override fun doWork(): WorkerResult {
            Log.d("★", javaClass.simpleName)
            return WorkerResult.RETRY
        }
    }

    class Worker2 : Worker() {
        override fun doWork(): WorkerResult {
            Log.d("★", javaClass.simpleName)
            return WorkerResult.RETRY
        }
    }
}

FAB をタップしたら、無限にリトライする Worker1Worker2 を enqueue します。

このときの DB の状態

スクリーンショット 2018-06-23 0.01.54.png `WorkSpec` テーブルに `Worker1`, `Worker2` とも `state == 0` で積まれています。 ※ state = {0: `ENQUEUED`, 1: `RUNNING`, 2: `SUCCEEDED`, 3: `FAILED`, 4: `BLOCKED`, 5: `CANCELLED`}

このときの Log

(略) D/★: Worker1
(略) D/★: Worker2
(略) D/★: Worker1
(略) D/★: Worker2
 :
 :

Worker1, Worker2 ともに無限にリトライされています。

アップデート時の差分(例:v1.0.1)

Worker2Worker3 にリネーム
(Typo してると、Rename したくなりますよね)

...
        fab.setOnClickListener {
            workManager.enqueue(OneTimeWorkRequest.Builder(Worker1::class.java).build())
-           workManager.enqueue(OneTimeWorkRequest.Builder(Worker2::class.java).build())
+           workManager.enqueue(OneTimeWorkRequest.Builder(Worker3::class.java).build())
        }
...

-    class Worker2 : Worker() {
+    class Worker3 : Worker() {
        override fun doWork(): WorkerResult {
...

アップデート後の Log(起動しただけ、新たに enqueue しない)

(略) D/★: Worker1
(略) D/★: Worker1
(略) D/★: Worker1
 :
 :

Worker1 しか実行されていません。。。

このときの DB の状態

スクリーンショット 2018-06-23 0.02.57.png いつの間にか、 `Worker2` が `state == 3(FAILED)` になっていますね… いつ失敗扱いになったのか見ていきます。

実装を見てみる

Worker#doWork の呼び出し元

WorkerWrapper.java
public void run() {
    ...
    if (mWorker == null) {
        mWorker = workerFromWorkSpec(mAppContext, mWorkSpec, extras); // ← mWorker の生成(この中を見る)
    }

    if (mWorker == null) {
        Log.e(TAG, String.format("Could for create Worker %s", mWorkSpec.workerClassName));
        setFailedAndNotify(); // ← mWorker の生成に失敗したら Failed にしている
        return;
    }
    ...
    Worker.WorkerResult result;
    try {
        result = mWorker.doWork(); // ← ここで doWork を呼び出し
    } catch (Exception | Error e) {
        result = Worker.WorkerResult.FAILURE;
    }
    ...
}

WorkerWrapper#workerFromWorkSpec の実装

WorkerWrapper.java
static Worker workerFromWorkSpec(...) {
    String workerClassName = workSpec.workerClassName;
    String workSpecId = workSpec.id;
    return workerFromClassName( // ← この中を見る
            context,
            workerClassName,
            workSpecId,
            extras);
    }

WorkerWrapper#workerFromClassName の実装

WorkerWrapper.java
public static Worker workerFromClassName(...) {
    Context appContext = context.getApplicationContext();
    try {
        Class<?> clazz = Class.forName(workerClassName);
        Worker worker = (Worker) clazz.newInstance();
            ...
        return worker; // 成功時
    } catch (Exception e) {
        Log.e(TAG, "Trouble instantiating " + workerClassName, e);
    }
    return null; // 失敗時
}

→ リフレクションによるインスタンス生成に失敗したら null を返していますね!
つまり、 DB 内の Worker2 のインスタンス化に失敗して、 setFailedAndNotify()FAILED 扱いになります。

まとめ

WorkManager の Worker クラスをアプリのアップデート時にリネームした場合の挙動

  • WorkerResult.FAILURE 扱いになり、二度と再実行されない
  • → enqueue 状態の Worker クラスのリネーム(パッケージ移動を含む)には注意

つぶやき

  • どうしてもリネーム(パッケージ移動を含む)必要があった場合、Room の マイグレーションを行ったらなんとかなる...?
  • Worker クラスは Proguard による難読化がかる...?そうすると、アップデート後も DB に格納されている値からリフレクションによるインスタンス生成は可能?
3
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?