12.2.1. 实现YambaWidget类
12.2.1.实现YambaWidget类
YambaWidget就是这个小部件所对应的类,它以AppWidgetProvider为基类。后者是Android框架为创建小部件所专门提供的一个类,它本身是BroadcastReceiver的子类,因此YambaWidget自然也算是一个Broadcast Receiver。在小部件更新、删除、启动、关闭时,我们都会相应地收到一条广播的Indent。同时,这个类也继承着onUpdate()、onDeleted()、onEnabled()、onDisabled()和onReceive()几个回调方法,我们可以随意覆盖它们。在这里,只覆盖onUpdate()和onReceive()两个方法即可。
现在对小部件的设计已有大致了解,下面是具体实现:
例 12.1. YambaWidget.java
package com.marakana.yamba7;
import android.app.PendingIntent;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.text.format.DateUtils;
import android.util.Log;
import android.widget.RemoteViews;
public class YambaWidget extends AppWidgetProvider { //
private static final String TAG = YambaWidget.class.getSimpleName();
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager,
int[] appWidgetIds) { //
Cursor c = context.getContentResolver().query(StatusProvider.CONTENT_URI,
null, null, null, null); //
try {
if (c.moveToFirst()) { {//#4}
CharSequence user = c.getString(c.getColumnIndex(StatusData.C_USER)); //
CharSequence createdAt = DateUtils.getRelativeTimeSpanString(context, c
.getLong(c.getColumnIndex(StatusData.C_CREATED_AT)));
CharSequence message = c.getString(c.getColumnIndex(StatusData.C_TEXT));
// Loop through all instances of this widget
for (int appWidgetId : appWidgetIds) { //
Log.d(TAG, "Updating widget " + appWidgetId);
RemoteViews views = new RemoteViews(context.getPackageName(),
R.layout.yamba_widget); //
views.setTextViewText(R.id.textUser, user); //
views.setTextViewText(R.id.textCreatedAt, createdAt);
views.setTextViewText(R.id.textText, message);
views.setOnClickPendingIntent(R.id.yamba_icon, PendingIntent
.getActivity(context, 0, new Intent(context,
TimelineActivity.class), 0));
appWidgetManager.updateAppWidget(appWidgetId, views); //
}
} else {
Log.d(TAG, "No data to update");
}
} finally {
c.close(); // {#10}
}
Log.d(TAG, "onUpdated");
}
@Override
public void onReceive(Context context, Intent intent) { // {#10}
super.onReceive(context, intent);
if (intent.getAction().equals(UpdaterService.NEW_STATUS_INTENT)) { // {#11}
Log.d(TAG, "onReceived detected new status update");
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context); // {#12}
this.onUpdate(context, appWidgetManager, appWidgetManager
.getAppWidgetIds(new ComponentName(context, YambaWidget.class))); // {#13}
}
}
}
- 如前所说,我们的小部件是AppWidgetProvider的子类,而AppWidgetProvider又是BroadcastReceiver的子类。
- 此方法在小部件状态更新时触发,因此将主要功能的实现放在这里。在稍后将它注册到Manifest文件时,我们将为它的状态更新设置一个时间间隔,也就是30分钟。
- 终于可以用上我们的ContentProvider了。在前面我们编写StatusProvider时可以体会到,它的API与SQLite数据库的API十分相似,甚至返回值也是同样的Cursor对象。不过主要区别在于,在这里不是给出表名,而是给出URI。
- 在这里,我们只关心服务端最新的消息更新。因此Cursor指向第一个的元素若存在,那它就是最新的消息。
- 如下的几行代码读出Cursor对象中的数据,并储存到局部变量中。
- 用户可以挂载多个Yamba小部件,因此需要遍历并更新所有小部件。appWidgetId是某个小部件的标识符,在这里我们是更新所有小部件,因此不必关心某个appWidgetId具体的值。
- 小部件所在的View位于另一个进程中,因此使用RemoteViews。RemoteViews是专门为小部件设计的某种共享内存机制。
- 得到了另一个进程地址空间中View的引用,即可更新它们。
- 更新过RemoteViews对象,然后调用AppWidgetManager的updateAppWidget()方法,将发送一条消息,通知系统更新所有的小部件。这是个异步操作,不过实际执行会在onUpdate()之后。
- 不管得到新消息与否,都要记得释放从ContentProvider中得到的对象。这是个好习惯。
- 对一般的小部件来说,onReceive()并无必要。不过小部件既然是BroadcastReceiver,而UpdaterService在每获得一条消息时都会发一条广播。那我们可以利用这个性质,在onReceive()中触发onUpdate(),实现消息的更新。
- 如果得到新消息,获取当前上下文的AppWidgetManager对象。
- 触发onUpdate()。
到这里,Yamba小部件已经编写完毕。作为一个Broadcast Receiver,它可以定时更新,也可以在获取新消息时得到通知,然后遍历主屏幕上所有的Yamba小部件并更新它们。
接下来设计小部件的外观布局。