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

Android Studio中Java天气Widget停止工作问题求助

天气Widget闲置/重启后停止响应的解决方案

问题背景

用Android Studio+Java开发的天气Widget,通过网络API获取展示天气信息,但放置一段时间或手机重启后无响应,已确认网络和API正常。核心代码涉及widget_size_1.java(Widget逻辑)和WeatherApiClient.java(API请求)。

核心原因分析

  1. Widget生命周期限制:AppWidgetProvider作为广播接收器,进程极易被系统后台回收,重启后无自动触发更新的机制。
  2. 缺少定时更新逻辑:未设置周期性刷新,无法主动拉取最新数据。
  3. 全局变量不可靠:widget_size_1中的temp/city/desc是成员变量,进程回收后值丢失,onUpdate时会显示空内容。
  4. 位置获取无容错:直接调用gpsClass.getLocation2()可能返回null,导致后续API调用崩溃。
  5. 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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.29 06:45:54