本文剖析Android自带widget-Music,其涉及到众多事件的处理,虽然麻烦,但可看出是如何和服务进行交互的。 首先我们看下music程序中AndroidManifest.xml中有关widgets的定义。 下面是xml/appwidget_info的内容,里面包含了这个widget程序的基本定义。 android:minWidth="294dip" //最小宽度 android:minHeight="72dip" //最小高度 android:updatePeriodMillis="0" //更新频率 android:initialLayout="@layout/album_appwidget"> //widget界面布局文件 整个设计还是十分清晰,这里我们就不再做过多的赘述。 public class MediaAppWidgetProvider extends AppWidgetProvider { public static final String CMDAPPWIDGETUPDATE = "appwidgetupdate"; static final ComponentName THIS_APPWIDGET = new ComponentName("com.android.music", "com.android.music.MediaAppWidgetProvider"); private static MediaAppWidgetProvider sInstance; static synchronized MediaAppWidgetProvider getInstance() { if (sInstance == null) { sInstance = new MediaAppWidgetProvider(); } return sInstance; } @Override public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { defaultAppWidget(context, appWidgetIds); // 发送一个Intent广播给MediaPlaybackService以便立即更新 Intent updateIntent = new Intent(MediaPlaybackService.SERVICECMD); updateIntent.putExtra(MediaPlaybackService.CMDNAME,MediaAppWidgetProvider.CMDAPPWIDGETUPDATE); updateIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds); updateIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); context.sendBroadcast(updateIntent); } /* * 初始化widget默认状态,如果服务没有运行,我们启动music的时候默认单击隐藏 */ private void defaultAppWidget(Context context, int[] appWidgetIds) { final Resources res = context.getResources(); final RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.album_appwidget); views.setViewVisibility(R.id.title, View.GONE); views.setTextViewText(R.id.artist, res.getText(R.string.emptyplaylist)); linkButtons(context, views, false /* 没有播放*/); pushUpdate(context, appWidgetIds, views); } private void pushUpdate(Context context, int[] appWidgetIds, RemoteViews views) { // 更新指定的列表 final AppWidgetManager gm = AppWidgetManager.getInstance(context); if (appWidgetIds != null) { gm.updateAppWidget(appWidgetIds, views); } else { gm.updateAppWidget(THIS_APPWIDGET, views); } } /** * Check against {@link AppWidgetManager} if there are any instances of this widget. */ private boolean hasInstances(Context context) { AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context); int[] appWidgetIds = appWidgetManager.getAppWidgetIds(THIS_APPWIDGET); return (appWidgetIds.length > 0); } /** * Handle a change notification coming over from {@link MediaPlaybackService} */ void notifyChange(MediaPlaybackService service, String what) { if (hasInstances(service)) { if (MediaPlaybackService.PLAYBACK_COMPLETE.equals(what) || MediaPlaybackService.META_CHANGED.equals(what) || MediaPlaybackService.PLAYSTATE_CHANGED.equals(what)) { performUpdate(service, null); } } } /** * Update all active widget instances by pushing changes */ void performUpdate(MediaPlaybackService service, int[] appWidgetIds) { final Resources res = service.getResources(); final RemoteViews views = new RemoteViews(service.getPackageName(), R.layout.album_appwidget); final int track = service.getQueuePosition() + 1; CharSequence titleName = service.getTrackName(); CharSequence artistName = service.getArtistName(); CharSequence errorState = null; // Format title string with track number, or show SD card message String status = Environment.getExternalStorageState(); if (status.equals(Environment.MEDIA_SHARED) || status.equals(Environment.MEDIA_UNMOUNTED)) { errorState = res.getText(R.string.sdcard_busy_title); } else if (status.equals(Environment.MEDIA_REMOVED)) { errorState = res.getText(R.string.sdcard_missing_title); } else if (titleName == null) { errorState = res.getText(R.string.emptyplaylist); } if (errorState != null) { // Show error state to user views.setViewVisibility(R.id.title, View.GONE); views.setTextViewText(R.id.artist, errorState); } else { // No error, so show normal titles views.setViewVisibility(R.id.title, View.VISIBLE); views.setTextViewText(R.id.title, titleName); views.setTextViewText(R.id.artist, artistName); } // Set correct drawable for pause state final boolean playing = service.isPlaying(); if (playing) { views.setImageViewResource(R.id.control_play, R.drawable.appwidget_pause); } else { views.setImageViewResource(R.id.control_play, R.drawable.appwidget_play); } // Link actions buttons to intents linkButtons(service, views, playing); pushUpdate(service, appWidgetIds, views); } /** * Link up various button actions using {@link PendingIntents}. * * @param playerActive True if player is active in background, which means * widget click will launch {@link MediaPlaybackActivity}, * otherwise we launch {@link MusicBrowserActivity}. */ private void linkButtons(Context context, RemoteViews views, boolean playerActive) { // Connect up various buttons and touch events Intent intent; PendingIntent pendingIntent; final ComponentName serviceName = new ComponentName(context, MediaPlaybackService.class); if (playerActive) { intent = new Intent(context, MediaPlaybackActivity.class); pendingIntent = PendingIntent.getActivity(context, 0 /* no requestCode */, intent, 0 /* no flags */); views.setOnClickPendingIntent(R.id.album_appwidget, pendingIntent); } else { intent = new Intent(context, MusicBrowserActivity.class); pendingIntent = PendingIntent.getActivity(context, 0 /* no requestCode */, intent, 0 /* no flags */); views.setOnClickPendingIntent(R.id.album_appwidget, pendingIntent); } intent = new Intent(MediaPlaybackService.TOGGLEPAUSE_ACTION); intent.setComponent(serviceName); pendingIntent = PendingIntent.getService(context, 0 /* no requestCode */, intent, 0 /* no flags */); views.setOnClickPendingIntent(R.id.control_play, pendingIntent); intent = new Intent(MediaPlaybackService.NEXT_ACTION); intent.setComponent(serviceName); pendingIntent = PendingIntent.getService(context, 0 /* no requestCode */, intent, 0 /* no flags */); views.setOnClickPendingIntent(R.id.control_next, pendingIntent); } }