Android Studio中Java天气Widget停止工作问题求助
天气Widget闲置/重启后停止响应的解决方案
问题背景
用Android Studio+Java开发的天气Widget,通过网络API获取展示天气信息,但放置一段时间或手机重启后无响应,已确认网络和API正常。核心代码涉及widget_size_1.java(Widget逻辑)和WeatherApiClient.java(API请求)。
核心原因分析
- Widget生命周期限制:AppWidgetProvider作为广播接收器,进程极易被系统后台回收,重启后无自动触发更新的机制。
- 缺少定时更新逻辑:未设置周期性刷新,无法主动拉取最新数据。
- 全局变量不可靠:
widget_size_1中的temp/city/desc是成员变量,进程回收后值丢失,onUpdate时会显示空内容。 - 位置获取无容错:直接调用
gpsClass.getLocation2()可能返回null,导致后续API调用崩溃。 - API客户端冗余:
WeatherApiClient每次创建新Retrofit实例,还附带无关的测试请求,浪费资源且易引发异常。
分步解决方案
1. 添加定时更新机制
在Widget的配置文件中设置自动更新间隔(系统最小允许30分钟):
- 修改
res/xml/widget_info.xml:
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android" android:updatePeriodMillis="1800000" <!-- 30分钟 --> android:minWidth="180dp" android:minHeight="180dp" android:initialLayout="@layout/widget_size_1" />
2. 修复Widget生命周期逻辑
重写onEnabled和onUpdate方法,确保首次添加和定时触发时主动拉取数据:
@Override public void onEnabled(Context context) { // 首次添加Widget时立即更新 performBtnSetAction(context); } @Override public void onUpdate(Context context, AppWidgetManager appWidgetManager, @NonNull int[] appWidgetIds) { for (int appWidgetId : appWidgetIds) { // 先显示加载状态,再触发数据获取 resetWidget(context, appWidgetId); } }
3. 移除不可靠的全局变量
删除widget_size_1中的temp/city/desc成员变量,改用局部变量传递数据:
public void currentLocTempReader(@NonNull Context context) { LocationManager locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE); GpsClass gpsClass = new GpsClass(locationManager, context); Location location = gpsClass.getLocation2(); // 位置为空时显示错误 if (location == null) { Log.e("MyWidgetLogs", "位置获取失败"); setErrorToWidget(context); return; } LinkCreator linkCreator = new LinkCreator(context); WeatherApiClient weatherApiClient = WeatherApiClient.getInstance("base url"); weatherApiClient.getCurrentWeatherData(location.getLatitude(),location.getLongitude(),linkCreator.GetOWMApiKey(), new WeatherApiClient.WeatherCallback() { @Override public void onSuccess(WeatherData weatherData) { String city = weatherData.getName(); DecimalFormat decimalFormat = new DecimalFormat("#,##0.00"); String temp = decimalFormat.format(weatherData.getMain().getTemp()); String desc = weatherData.getWeather()[0].getDescription(); // 直接传递数据更新Widget setDataToWidget(context, city, temp, desc); Log.i("MyWidgetLogs", "数据更新成功"); } @Override public void onFailure(Throwable throwable) { Log.e("MyWidgetLogs", throwable.toString()); setErrorToWidget(context); } }); } // 添加错误状态展示方法 private void setErrorToWidget(Context context) { AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context); int[] appWidgetIds = appWidgetManager.getAppWidgetIds(new ComponentName(context, widget_size_1.class)); for (int appWidgetId : appWidgetIds) { RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.widget_size_1); views.setTextViewText(R.id.widget_city, "获取失败"); views.setTextViewText(R.id.widget_desc, "点击刷新重试"); views.setTextViewText(R.id.widget_temp, "--"); // 保留刷新按钮事件 Intent intent = new Intent(context, widget_size_1.class); intent.setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE); intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, new int[]{appWidgetId}); PendingIntent pendingIntent = PendingIntent.getBroadcast(context, appWidgetId, intent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE); views.setOnClickPendingIntent(R.id.widget_refresh, pendingIntent); appWidgetManager.updateAppWidget(appWidgetId, views); } } // 修改数据更新方法,接收参数 private void setDataToWidget(Context context, String city, String temp, String desc) { AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context); int[] appWidgetIds = appWidgetManager.getAppWidgetIds(new ComponentName(context, widget_size_1.class)); for (int appWidgetId : appWidgetIds) { updateAppWidget(context, city, temp, desc, appWidgetId); } }
4. 优化WeatherApiClient
改为单例模式复用Retrofit实例,移除冗余测试代码,用注解构建请求:
import android.util.Log; import retrofit2.Call; import retrofit2.Callback; import retrofit2.Response; import retrofit2.Retrofit; import retrofit2.converter.gson.GsonConverterFactory; import retrofit2.http.GET; import retrofit2.http.Query; public class WeatherApiClient { private final WeatherApi weatherApi; private static WeatherApiClient instance; // 单例模式避免重复创建Retrofit public static WeatherApiClient getInstance(String baseURL) { if (instance == null) { synchronized (WeatherApiClient.class) { if (instance == null) { instance = new WeatherApiClient(baseURL); } } } return instance; } private WeatherApiClient(String baseURL) { Retrofit retrofit = new Retrofit.Builder() .baseUrl(baseURL) .addConverterFactory(GsonConverterFactory.create()) .build(); weatherApi = retrofit.create(WeatherApi.class); } // 用@Query注解构建请求,避免手动拼接URL public void getCurrentWeatherData( double lat, double lon, String apiKey, final WeatherCallback callback) { Call<WeatherData> call = weatherApi.getWeatherData(lat, lon, apiKey); call.enqueue(new Callback<WeatherData>() { @Override public void onResponse(Call<WeatherData> call, Response<WeatherData> response) { if (response.isSuccessful() && response.body() != null) { callback.onSuccess(response.body()); } else { callback.onFailure(new Exception("响应无效或数据为空")); } } @Override public void onFailure(Call<WeatherData> call, Throwable t) { callback.onFailure(t); } }); } public interface WeatherApi { @GET("weather") Call<WeatherData> getWeatherData( @Query("lat") double lat, @Query("lon") double lon, @Query("appid") String apiKey ); } public interface WeatherCallback { void onSuccess(WeatherData weatherData); void onFailure(Throwable throwable); } }
5. 处理位置权限问题
- 在
AndroidManifest.xml添加权限:
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <!-- Android 12+ 后台定位权限 --> <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" android:maxSdkVersion="33" />
- 在获取位置前检查权限,无权限时引导用户到设置页:
public void currentLocTempReader(@NonNull Context context) { // 检查位置权限 if (ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) { // Widget无法直接请求权限,跳转应用设置页 Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); Uri uri = Uri.fromParts("package", context.getPackageName(), null); intent.setData(uri); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); context.startActivity(intent); setErrorToWidget(context); return; } // 后续位置获取逻辑... }
6. 确保PendingIntent唯一性
创建PendingIntent时用appWidgetId作为requestCode,避免多Widget实例冲突:
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, appWidgetId, intent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
内容的提问来源于stack exchange,提问作者Teriteh Teriteh
相关产品推荐
相关产品推荐

