您的位置:首页 > 移动开发 > Android开发

实战:Android 时间、天气widget

2015-11-14 00:32 549 查看
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,初步实现了上述功能。如果有错误,欢迎指出!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: