12.2.1. 实现YambaWidget类

优质
小牛编辑
130浏览
2023-12-01

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}

}

}

}

  1. 如前所说,我们的小部件是AppWidgetProvider的子类,而AppWidgetProvider又是BroadcastReceiver的子类。
  2. 此方法在小部件状态更新时触发,因此将主要功能的实现放在这里。在稍后将它注册到Manifest文件时,我们将为它的状态更新设置一个时间间隔,也就是30分钟。
  3. 终于可以用上我们的ContentProvider了。在前面我们编写StatusProvider时可以体会到,它的API与SQLite数据库的API十分相似,甚至返回值也是同样的Cursor对象。不过主要区别在于,在这里不是给出表名,而是给出URI。
  4. 在这里,我们只关心服务端最新的消息更新。因此Cursor指向第一个的元素若存在,那它就是最新的消息。
  5. 如下的几行代码读出Cursor对象中的数据,并储存到局部变量中。
  6. 用户可以挂载多个Yamba小部件,因此需要遍历并更新所有小部件。appWidgetId是某个小部件的标识符,在这里我们是更新所有小部件,因此不必关心某个appWidgetId具体的值。
  7. 小部件所在的View位于另一个进程中,因此使用RemoteViews。RemoteViews是专门为小部件设计的某种共享内存机制。
  8. 得到了另一个进程地址空间中View的引用,即可更新它们。
  9. 更新过RemoteViews对象,然后调用AppWidgetManager的updateAppWidget()方法,将发送一条消息,通知系统更新所有的小部件。这是个异步操作,不过实际执行会在onUpdate()之后。
  10. 不管得到新消息与否,都要记得释放从ContentProvider中得到的对象。这是个好习惯。
  11. 对一般的小部件来说,onReceive()并无必要。不过小部件既然是BroadcastReceiver,而UpdaterService在每获得一条消息时都会发一条广播。那我们可以利用这个性质,在onReceive()中触发onUpdate(),实现消息的更新。
  12. 如果得到新消息,获取当前上下文的AppWidgetManager对象。
  13. 触发onUpdate()。

到这里,Yamba小部件已经编写完毕。作为一个Broadcast Receiver,它可以定时更新,也可以在获取新消息时得到通知,然后遍历主屏幕上所有的Yamba小部件并更新它们。

接下来设计小部件的外观布局。