You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用WorkManager的Android AppWidget遭遇网络问题求助

排查思路与解决方案

1. 立即修复崩溃:添加异常捕获与缓存降级

当前RefreshWidgetWorker未处理网络异常,一旦请求失败直接崩溃,导致Widget无法加载。必须在doWork()中包裹网络请求逻辑,捕获所有IO相关异常,并降级使用缓存数据更新Widget:

override suspend fun doWork(): Result {
    return try {
        val d = dataService.getData()
        // 更新Widget并缓存新数据
        updateWidgetWithData(d)
        cache.put(d)
        Result.success()
    } catch (e: IOException) {
        // 网络异常时读取缓存更新Widget
        cache.get()?.let { cachedData ->
            updateWidgetWithData(cachedData)
        } ?: run {
            // 无缓存时显示默认占位内容
            showPlaceholderWidget()
        }
        // 根据异常类型决定是否重试:UnknownHostException可能是DNS解析问题,稍后重试;超时也可重试
        if (e is UnknownHostException || e is SocketTimeoutException) {
            Result.retry()
        } else {
            Result.failure()
        }
    }
}

// 提取更新Widget的逻辑复用
private fun updateWidgetWithData(data: Data) {
    val views = RemoteViews(context.packageName, R.layout.widget_layout).apply {
        setTextViewText(R.id.textView, data.item)
    }
    AppWidgetManager.getInstance(context).updateAppWidget(widgetId, views)
}

private fun showPlaceholderWidget() {
    val views = RemoteViews(context.packageName, R.layout.widget_layout).apply {
        setTextViewText(R.id.textView, context.getString(R.string.widget_placeholder))
    }
    AppWidgetManager.getInstance(context).updateAppWidget(widgetId, views)
}

2. 优化WorkManager调度策略

  • 添加网络约束:避免在无网络时发起请求,减少无效异常。创建WorkRequest时添加网络要求:
    val initialWorkRequest = OneTimeWorkRequest.Builder(RefreshWidgetWorker::class.java)
        .setInputData(/*requestData*/)
        .setRequiredNetworkType(NetworkType.CONNECTED) // 仅在有网络时执行
        .setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 10, TimeUnit.MINUTES) // 指数退避重试
        .build()
    
  • 替换GlobalScope:AppWidgetProvider是广播接收器,GlobalScope的协程生命周期不受控,容易导致内存泄漏或逻辑异常,改用临时协程:
    override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray) {
        CoroutineScope(Dispatchers.IO).launch {
            if (timeSinceLastRefresh >= refreshThreshold) {
                // 调度Worker逻辑
            }
        }
    }
    
  • 调整ExistingWorkPolicy:如果希望新任务替换旧任务(避免重复调度),改用ExistingWorkPolicy.REPLACE;如果要保留旧任务忽略新任务,KEEP没问题,但需确保任务不会堆积。

3. 优化网络请求配置

  • 延长超时时间:当前2秒超时过短,移动网络环境下容易触发超时,建议调整为5秒:
    OkHttpClient.Builder()
        .connectTimeout(5, TimeUnit.SECONDS)
        .readTimeout(5, TimeUnit.SECONDS)
        .writeTimeout(5, TimeUnit.SECONDS)
        .build()
    
  • 添加请求日志拦截器:收集请求细节(如网络类型、请求时间、响应状态),帮助定位问题:
    val loggingInterceptor = HttpLoggingInterceptor { message ->
        // 将日志上报到你的崩溃分析系统(如Firebase Crashlytics、Bugly)
        Crashlytics.log(message)
    }.setLevel(HttpLoggingInterceptor.Level.BASIC)
    
    OkHttpClient.Builder()
        .addInterceptor(loggingInterceptor)
        // 其他配置
        .build()
    
  • 统一协程适配:当前Retrofit使用RxJava2CallAdapterFactory,但Worker中用的是协程,建议替换为CoroutineCallAdapterFactory,避免调度冲突:
    Retrofit.Builder()
        .baseUrl(Constants.API_BASE_URL)
        .addConverterFactory(GsonConverterFactory.create())
        .addCallAdapterFactory(CoroutineCallAdapterFactory()) // 改用协程适配器
        .client(okHttpClient)
        .build()
        .create(DataService::class.java)
    
    确保DataService的getData()是suspend函数:
    interface DataService {
        @GET("data")
        suspend fun getData(): Data
    }
    

4. 系统与设备层面排查

  • 收集崩溃设备特征:统计崩溃日志中的设备品牌、Android版本、网络类型(移动/WiFi),看是否集中在特定机型(如某些国产ROM的后台限制)或系统版本(如Android 12+的后台调度限制)。
  • 检查Doze模式影响:WorkManager在Doze模式下会延迟执行,可通过setRequiresDeviceIdle(false)(默认就是false)保持调度灵活性,同时确保重试策略合理(指数退避比线性退避更友好)。
  • 验证后台权限:部分Android 13+设备需要POST_NOTIFICATIONS权限才能正常执行后台任务,检查是否已申请并引导用户授权。

5. 调整Widget更新流程

当前流程是请求API → 显示Widget → 缓存数据,改为先显示缓存 → 请求API → 更新缓存与Widget,确保用户始终能看到内容:

override suspend fun doWork(): Result {
    // 第一步:先显示缓存数据
    cache.get()?.let { updateWidgetWithData(it) } ?: showPlaceholderWidget()
    
    // 第二步:尝试请求新数据
    return try {
        val d = dataService.getData()
        updateWidgetWithData(d)
        cache.put(d)
        Result.success()
    } catch (e: IOException) {
        // 请求失败,已显示缓存,无需额外操作,仅记录异常
        Crashlytics.logException(e)
        if (e is UnknownHostException || e is SocketTimeoutException) {
            Result.retry()
        } else {
            Result.failure()
        }
    }
}

内容的提问来源于stack exchange,提问作者Waking7319

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.03 23:01:03