val constraints = Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build()
val request = OneTimeWorkRequestBuilder<MyWorker>()
.setConstraints(constraints)
.build()
のように RequiredNetworkType を指定すると、ConnectivityManager の registerDefaultNetworkCallback() が呼ばれます。
ここで TooManyRequestsException が起こったときに、Coil の使い方が原因になってることがありました。
registerDefaultNetworkCallback() の実装を見ると、同時に 100 を超える NetworkCallback を register すると exception が投げられると書いてあります。
/**
* ...
*
* To avoid performance issues due to apps leaking callbacks, the system will limit the
* number of outstanding requests to 100 per app (identified by their UID), ... If this limit is
* exceeded, an exception will be thrown. ...
*/
@RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
public void registerDefaultNetworkCallback(@NonNull NetworkCallback networkCallback) {
registerDefaultNetworkCallback(networkCallback, getDefaultHandler());
}
つまり、どこかで同時に大量に NetworkCallback を登録してるところがあるということです。
それがどこか探したところ、coil の ImageLoader.kt にいる
@JvmName("create")
fun ImageLoader(context: Context): ImageLoader {
return ImageLoader.Builder(context).build()
}
をリクエストごとに呼び出している箇所が原因でした。
coil では ImageLoader ごとに registerDefaultNetworkCallback() が呼ばれます。
ImageLoader.Builder の build() で RealImageLoader が作られ、NetworkObserver が作られ、 RealNetworkObserver() が作られます。
RealNetworkObserver の init で registerNetworkCallback() しています。
private class RealNetworkObserver(
private val connectivityManager: ConnectivityManager,
private val listener: Listener
) : NetworkObserver {
...
init {
val request = NetworkRequest.Builder()
.addCapability(NET_CAPABILITY_INTERNET)
.build()
connectivityManager.registerNetworkCallback(request, networkCallback)
}
...
}
RealNetworkObserver のインスタンスを作るところで try-catch しているので、ここでは問題に気づかず、WorkManager の方で発覚したということでした。
internal fun NetworkObserver(
context: Context,
listener: Listener,
logger: Logger?
): NetworkObserver {
val connectivityManager: ConnectivityManager? = context.getSystemService()
...
return try {
RealNetworkObserver(connectivityManager, listener)
} catch (e: Exception) {
logger?.log(TAG, RuntimeException("Failed to register network observer.", e))
EmptyNetworkObserver()
}
}
ImageLoader のインスタンスを取得するときは ImageLoader.kt にいる ImageLoader() ではなく、Coil.imageLoader() を使うようにしましょう!こちらは共通の ImageLoader インスタンスが返されるので registerNetworkCallback() を呼び過ぎることにはなりません。
参考 : https://issuetracker.google.com/issues/231499040#comment3