实战:Android 时间、天气widget

本文介绍了如何在Android中创建和使用Widget,包括基本配置如在Android Studio中添加Widget文件,设置AppWidgetProviderInfo属性,以及实现时间每分钟刷新的广播监听。对于显示天气,文章探讨了三种方案,最终选择了使用Yahoo API通过经纬度获取天气信息,因为它无需申请key且支持免费查询(有次数限制)。项目源码已上传至GitHub。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Android widget可以在用户不进入APP的情况下进行操作,同时也可呈现给用户更多的信息。所以今天就来看看widget的基本使用(建议阅读官方文档)。

widget基本使用

Android studio 环境下可选择

file => new => widget=> App Widget

即可添加一系列相关文件。

主要有:

  • res/xml/new_app_widget_info
<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
    android:initialKeyguardLayout="@layout/new_app_widget"
    android:initialLayout="@layout/new_app_widget"
    android:minHeight="40dp"
    android:minWidth="250dp"
    android:previewImage="@drawable/example_appwidget_preview"
    android:resizeMode="horizontal|vertical"
    android:updatePeriodMillis="108005"
    android:widgetCategory="home_screen"></appwidget-provider>

作用:官方的解释是 The AppWidgetProviderInfo defines the essential qualities of an App Widget。其中主要的几个属性:
android:initialKeyguardLayout:锁屏界面对应的布局
android:initialLayout:桌面对应布局
android:previewImage:选择小部件时的预览图
android:updatePeriodMillis:widget更新周期,单位:ms。注意:规定不能小于30min,如果小于30min,则系统会设置为30min。

  • res/layout/new_app_widget

作用:对应widget的布局

  • 同时也要在AndroidManifest.xml中声明
<receiver android:name=".NewAppWidget">
            <intent-filter>
                <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
            </intent-filter>

            <meta-data
                android:name="android.appwidget.provider"
                android:resource="@xml/new_app_widget_info" />
        </receiver>
  • NewAppWidget
    该类继承于AppWidgetProvider,是控制widget生命周期的一个类。
    其中主要的方法有:
    • onEnabled:当widget被添加到桌面上时被调用
    • onUpdate:当widget更新时调用
    • onDisabled:当widget被移除时调用
      通过系统自动添加的文件,运行后即可在桌面widget中找到了!

接下来就是实现显示时间和天气的功能了。

时间

每分钟刷新界面只需要注册 android.intent.action.TIME_TICK 广播即可。

 private BroadcastReceiver minBroadcast = new BroadcastReceiver() {

        @Override
        public void onReceive(Context context, Intent intent) {
            //update time every minute
            updateTime(context);
        }
    };
//...

IntentFilter updateIntent = new IntentFilter();
        updateIntent.addAction("android.intent.action.TIME_TICK");
        context.getApplicationContext().registerReceiver(minBroadcast, updateIntent);

//...

天气

获取天气信息肯定是要通过网络请求的,于是开始找API,能找到free use 的是最好。
方案一:使用大公司提供的天气查询接口,比如百度。
优点:准确,方便。
缺点:需要申请key,好像还要用百度的包什么的。

方案二:使用民间抓包的接口。
优点:可以直接用,返回数据信息足够详细。
缺点:找到的大多接口,比如中国气象网的接口,会用到cityid这个参数,也有很多类似都接口都用到了这个参数,这里给出各个城市对应的cityid。如果是使用这个方案的话,就有两条路:

  • 获取经纬度-》再找一个能通过经纬度找到地名的API-》根据地名找到最接近的cityid-》最后传cityid、调用天气API、获得天气情况。
  • 获取IP-》再找一个通过ip获得地址的API-》根据地名找到最接近的cityid-》最后传cityid、调用天气API、获得天气情况。

方案三:使用Yahoo的API
鉴于方案二分析,如果能找到直接通过经纬度就能得到天气的API那时极好的。而Yahoo的API刚好符合这点。而且是免费使用(有次数限制),不需要申请key,添加包。
缺点:返回数据类型有限,比如大家最关心的PM值没有、需要学习Yahoo的数据可查询语句YQL。

综上,最后决定使用Yahoo的API。

使用可参见这两个链接:
https://developer.yahoo.com/weather/
https://developer.yahoo.com/weather/documentation.html

import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.location.Criteria;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.AsyncTask;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.widget.RemoteViews;

import org.json.JSONObject;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
import java.net.URLConnection;
import java.text.SimpleDateFormat;

public class NewAppWidget extends AppWidgetProvider {

    private static boolean isFirst = true;

    enum weatherType {
        condition, sunset, weather;
    }

    private LocationListener locationListener = null;


    private final String INFO = "AppWidget_INFO";
    private final String IS_LOCATING = "locating", FRESH_FAIL = "fresh fail", LOCATION_FAIL = "location fail ";


    private LocationManager locationManager;
    private Location location;

    private BroadcastReceiver minBroadcast = new BroadcastReceiver() {

        @Override
        public void onReceive(Context context, Intent intent) {
            //update time every minute
            updateTime(context);
        }
    };

    private URL weatherUrl;


    @Override
    public void onEnabled(Context context) {
        //when attach to screen

        Log.i(INFO, "onEnabled");
        acquireLocation(context);
        updateTime(context);


        // detected minute change
        IntentFilter updateIntent = new IntentFilter();
        updateIntent.addAction("android.intent.action.TIME_TICK");
        context.getApplicationContext().registerReceiver(minBroadcast, updateIntent);

        RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.new_app_widget);
        views.setTextViewText(R.id.appwidget_temp, IS_LOCATING);
        views.setViewVisibility(R.id.appwidget_weather, View.INVISIBLE);
        views.setViewVisibility(R.id.appwidget_date, View.INVISIBLE);
        AppWidgetManager.getInstance(context.getApplicationContext()).updateAppWidget(new ComponentName(context.getApplicationContext(), NewAppWidget.class), views);
    }

    @Override
    public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
        // There may be multiple widgets active, so update all of them
        Log.i(INFO, "onUpdate");

        if (location != null) {
            new freshWeatherTask(context).execute(acquireUrl(weatherType.condition, location));
        } else if (!isFirst) {
            RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.new_app_widget);
            views.setTextViewText(R.id.appwidget_temp, LOCATION_FAIL);
            views.setViewVisibility(R.id.appwidget_weather, View.INVISIBLE);
            views.setViewVisibility(R.id.appwidget_date, View.INVISIBLE);
            AppWidgetManager.getInstance(context.getApplicationContext()).updateAppWidget(new ComponentName(context.getApplicationContext(), NewAppWidget.class), views);
        }
        isFirst = false;
    }


    @Override
    public void onDisabled(Context context) {
        // Enter relevant functionality for when the last widget is disabled
        if (minBroadcast.isOrderedBroadcast())
            context.getApplicationContext().unregisterReceiver(minBroadcast);

        if (locationManager != null && locationListener != null)
            locationManager.removeUpdates(locationListener);
    }


    /**
     * get location by GPS or NetWork
     *
     * @param context
     */
    private void acquireLocation(final Context context) {
        if (locationManager == null) {
            locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
        }

        if (locationListener == null) {
            locationListener = new LocationListener() {
                @Override
                public void onLocationChanged(Location loc) {
                    if (loc != null) {
                        Log.i(INFO, "get new location");
                        location = loc;
                        new freshWeatherTask(context).execute(acquireUrl(weatherType.condition, location));
                    }
                }

                @Override
                public void onStatusChanged(String provider, int status, Bundle extras) {

                }

                @Override
                public void onProviderEnabled(String provider) {

                }

                @Override
                public void onProviderDisabled(String provider) {

                }
            };
        }


        Criteria criteria = new Criteria();
        criteria.setAccuracy(Criteria.ACCURACY_COARSE);
        criteria.setAltitudeRequired(false);
        criteria.setBearingRequired(false);
        criteria.setCostAllowed(true);
        criteria.setPowerRequirement(Criteria.POWER_LOW);

        locationManager.requestLocationUpdates(locationManager.getBestProvider(criteria, true), 1000 * 600, 10, locationListener);

    }

    /**
     * get the url by different type.
     * in the example ,the type I used is condition. if you use different type , the json construct may change. see https://developer.yahoo.com/weather/
     *
     * @param type
     * @param l
     * @return
     */
    private String acquireUrl(weatherType type, Location l) {
        if (l != null) {
            switch (type) {
                case condition:
                    return "https://query.yahooapis.com/v1/public/yql?q=select%20item.condition%20from%20weather.forecast%20where%20woeid%20in%20(SELECT%20woeid%20FROM%20geo.placefinder%20WHERE%20text%3D%22" + l.getLatitude() + "%2C" + l.getLongitude() + "%22%20and%20gflags%3D%22R%22)&format=json&env=store%3A%2F%2Fdatatables.org%2Falltableswithkeys";
                case weather:
                    return "https://query.yahooapis.com/v1/public/yql?q=select%20*%20from%20weather.forecast%20where%20woeid%20in%20(SELECT%20woeid%20FROM%20geo.placefinder%20WHERE%20text%3D%22" + l.getLatitude() + "%2C" + l.getLongitude() + "%22%20and%20gflags%3D%22R%22)&format=json&env=store%3A%2F%2Fdatatables.org%2Falltableswithkeys";
                case sunset:
                    return "https://query.yahooapis.com/v1/public/yql?q=select%20astronomy.sunset%20from%20weather.forecast%20where%20woeid%20in%20(SELECT%20woeid%20FROM%20geo.placefinder%20WHERE%20text%3D%22" + l.getLatitude() + "%2C" + l.getLongitude() + "%22%20and%20gflags%3D%22R%22)&format=json&env=store%3A%2F%2Fdatatables.org%2Falltableswithkeys";
                default:
                    return null;
            }
        } else {
            return null;
        }
    }

    private void updateTime(Context context) {
        RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.new_app_widget);
        views.setTextViewText(R.id.appwidget_text, new SimpleDateFormat("hh:mm").format(System.currentTimeMillis()));

        AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context.getApplicationContext());
        ComponentName componentName = new ComponentName(context.getApplicationContext(), NewAppWidget.class);
        appWidgetManager.updateAppWidget(componentName, views);
    }

    class freshWeatherTask extends AsyncTask<String, String, String> {

        private Context context;
        private AppWidgetManager manager;
        private ComponentName componentName;

        freshWeatherTask(Context c) {
            context = c;
            manager = AppWidgetManager.getInstance(context.getApplicationContext());
            componentName = new ComponentName(context.getApplicationContext(), NewAppWidget.class);

        }


        @Override
        protected String doInBackground(String... params) {
            BufferedReader in = null;
            String rst = "";
            if (params.length > 0) {
                try {
                    weatherUrl = new URL(params[0]);
                    URLConnection connection = weatherUrl.openConnection();
                    connection.connect();
                    in = new BufferedReader(new InputStreamReader(connection.getInputStream()));

                    String tmpRst;
                    while ((tmpRst = in.readLine()) != null) {
                        rst += tmpRst;
                    }
                    Log.i(INFO, rst);
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    try {
                        if (in != null) {
                            in.close();
                        }
                    } catch (IOException ioE) {
                        ioE.printStackTrace();
                    }
                }
            }
            return rst;
        }

        @Override
        protected void onPostExecute(String s) {
            super.onPostExecute(s);
            Log.i("Async", s);
            if (!TextUtils.isEmpty(s)) {
                try {
                    JSONObject mainJson = new JSONObject(s).getJSONObject("query");
                    JSONObject subJson;
                    if (mainJson.getInt("count") == 1) {
                        if (mainJson.has("results") && mainJson.getJSONObject("results").has("channel")) {
                            subJson = mainJson.getJSONObject("results").getJSONObject("channel");
                            if (subJson.has("item") && subJson.getJSONObject("item").has("condition")) {
                                subJson = subJson.getJSONObject("item").getJSONObject("condition");

                                RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.new_app_widget);
                                if (subJson.has("code")) {
//                                    views.setTextViewText(R.id.appwidget_temp, subJson.getString("temp"));
                                }

                                if (subJson.has("date")) {
                                    views.setViewVisibility(R.id.appwidget_date, View.VISIBLE);
                                    views.setTextViewText(R.id.appwidget_date, subJson.getString("date"));
                                }

                                if (subJson.has("temp")) {
                                    views.setViewVisibility(R.id.appwidget_temp, View.VISIBLE);
                                    float temp = (subJson.getInt("temp") - 32) / 1.8f;
                                    views.setTextViewText(R.id.appwidget_temp, (Math.round(temp * 100)) / 100f + "ºC");
                                }

                                if (subJson.has("text")) {
                                    views.setViewVisibility(R.id.appwidget_weather, View.VISIBLE);
                                    views.setTextViewText(R.id.appwidget_weather, subJson.getString("text"));
                                }


                                manager.updateAppWidget(componentName, views);
                            }
                        }
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            } else {
                RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.new_app_widget);
                views.setTextViewText(R.id.appwidget_temp, FRESH_FAIL);
                views.setViewVisibility(R.id.appwidget_weather, View.INVISIBLE);
                views.setViewVisibility(R.id.appwidget_date, View.INVISIBLE);
                manager.updateAppWidget(componentName, views);
            }
        }
    }
}

project已传到github,初步实现了上述功能。如果有错误,欢迎指出!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值