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 をタップしたら、無限にリトライする Worker1
と Worker2
を enqueue します。
このときの DB の状態

このときの Log
(略) D/★: Worker1
(略) D/★: Worker2
(略) D/★: Worker1
(略) D/★: Worker2
:
:
Worker1
, Worker2
ともに無限にリトライされています。
アップデート時の差分(例:v1.0.1)
Worker2
を Worker3
にリネーム
(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 の状態

実装を見てみる
Worker#doWork の呼び出し元
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 の実装
static Worker workerFromWorkSpec(...) {
String workerClassName = workSpec.workerClassName;
String workSpecId = workSpec.id;
return workerFromClassName( // ← この中を見る
context,
workerClassName,
workSpecId,
extras);
}
WorkerWrapper#workerFromClassName の実装
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 に格納されている値からリフレクションによるインスタンス生成は可能?