安卓学习笔记之使用widget桌面小控件及源码分析
安卓学习笔记之使用widget桌面小控件
一、 使用步骤
1、创建所需要的Receiver,并在清单文件中配置
在AndroidManifest.xml中进行如下配置:
<receiver android:name="com.yu.receiver.SaferAppWidgetProvider" > <intent-filter> <action android:name="android.appwidget.action.APPWIDGET_UPDATE" /> intent-filter> <meta-data android:name="android.appwidget.provider" android:resource="@xml/safer_appwidget_info" /> receiver>
定义部件需要的AppWidgetProvider (本质上是一个Receiver)
package com.yu.receiver;import android.appwidget.AppWidgetManager;import android.appwidget.AppWidgetProvider;import android.content.Context;import android.content.Intent;import com.yu.service.KillProcesWidgetService;public class SaferAppWidgetProvider extends AppWidgetProvider { /** * 在每次操作的结束被调用 */ @Override public void onReceive(Context context, Intent intent) { super.onReceive(context, intent); } /** * 只要有新的桌面小控件创建时就会调用 */ @Override public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { super.onUpdate(context, appWidgetManager, appWidgetIds); // 开启更新小部件的服务 context.startService(new Intent(context,KillProcesWidgetService.class)); } /** * 每次删除桌面小控件时调用 */ @Override public void onDeleted(Context context, int[] appWidgetIds) { super.onDeleted(context, appWidgetIds); } /** * 第一次创建小控件时才会调用 */ @Override public void onEnabled(Context context) { super.onEnabled(context); } /** * 当所有的桌面小控件都删除后调用 */ @Override public void onDisabled(Context context) { super.onDisabled(context); // 关闭更新小部件的服务 context.startService(new Intent(context,KillProcesWidgetService.class)); } /** * Called in response to the AppWidgetManager.ACTION_APPWIDGET_RESTORED broadcast * when instances of this AppWidget provider have been restored from backup */ @Override public void onRestored(Context context, int[] oldWidgetIds, int[] newWidgetIds) { super.onRestored(context, oldWidgetIds, newWidgetIds); }}
2、在src/xml文件下创建safer_appwidget_info.xml,用以配置widget
<?xml version="1.0" encoding="utf-8"?><appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android" android:initialLayout="@layout/widget_process_safer" android:minHeight="75.0dip" -minHeight不宜过大,否则widget无法显示--> android:minWidth="294.0dip" android:updatePeriodMillis="0" /><!-设置为0,手动处理更新时间-->
3、在layout目录下创建widget的布局文件widget_process_safer
<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@drawable/widget_bg_portrait" android:orientation="horizontal" > <LinearLayout android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:background="@drawable/widget_bg_portrait_child" android:gravity="center" android:orientation="vertical" > <TextView android:id="@+id/tv_count_widget" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="运行的程序" /> <TextView android:id="@+id/tv_freeMem_widget" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="6dp" android:text="可用内存" /> LinearLayout> <LinearLayout android:layout_width="wrap_content" android:layout_height="match_parent" android:gravity="center" android:padding="5dp" android:orientation="vertical" > <TextView android:id="@+id/textView3" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:text="安全卫士" /> <Button android:id="@+id/bt_clean" style="?android:attr/buttonStyleSmall" android:layout_width="wrap_content" android:layout_height="match_parent" android:background="@drawable/bt_selector" android:layout_marginTop="6dp" android:paddingLeft="3dp" android:paddingRight="3dp" android:textColor="#000" android:text="一键清理" /> LinearLayout>LinearLayout>
4、配置完成,开始使用widget。新建一个服务,用于更新widget
1、 通过AppWidgetManger的getInstance方法获得widget管理器
2、 实例化Timer对象,并用TimerTask创建一个线程,通过timer的schedule方法开启一个定时任务
3、在TimerTask的run方法中创建widget所需的RemoteViews,并设置相应的控件内容和事件监听
4、创建Component组件,将RemoteViews和ComponentName 设置给widget,并更新widget
package com.yu.service;import java.util.Timer;import java.util.TimerTask;import com.yu.receiver.SaferAppWidgetProvider;import com.yu.safer.R;import com.yu.utils.SystemInfoUtils;import android.app.PendingIntent;import android.app.Service;import android.appwidget.AppWidgetManager;import android.content.ComponentName;import android.content.Intent;import android.os.IBinder;import android.text.format.Formatter;import android.widget.RemoteViews;/** * 进程清理小控件 * @author Administrator * */public class KillProcesWidgetService extends Service { AppWidgetManager awm; ComponentName appWidgetProvider; Timer timer; TimerTask task; @Override public IBinder onBind(Intent intent) { return null; } @Override public int onStartCommand(Intent intent, int flags, int startId) { // 获得widget管理者 awm = AppWidgetManager.getInstance(this); // 开启定时任务 每隔5秒更新widget timer = new Timer(); task = new TimerTask() { @Override public void run() { // 初始化一个远程的view(RemoteViews) RemoteViews views = new RemoteViews(getPackageName(), R.layout.widget_process_safer); // 获取正在运行的进程数 int count = SystemInfoUtils.getRunningAppCount(KillProcesWidgetService.this); // 获取可用的内存大小 String freeMem = Formatter.formatFileSize(KillProcesWidgetService.this, SystemInfoUtils.getFreeMemoryInfo(KillProcesWidgetService.this)); // 设置views内容 views.setTextViewText(R.id.tv_count_widget, "运行的程序:"+count+"个"); views.setTextViewText(R.id.tv_freeMem_widget, "可用内存:"+freeMem); Intent i = new Intent(); //设置一个隐式意图 i.setAction("com.yu.safer.widget"); // 通过PendingIntent 开启一个广播 用于清理进程 PendingIntent pendingIntent = PendingIntent.getBroadcast(getApplicationContext(), 0, i, 0); // 设置点击事件 views.setOnClickPendingIntent(R.id.bt_clean, pendingIntent ); appWidgetProvider = new ComponentName(getApplicationContext(), SaferAppWidgetProvider.class); awm.updateAppWidget(appWidgetProvider, views); } }; timer.schedule(task, 1000, 5000); return super.onStartCommand(intent, flags, startId); }}
二、 AppWidget更新流程分析
① AppWidgetManager$updateAppWidget
public void updateAppWidget(int[] appWidgetIds, RemoteViews views) { if (mService == null) { return; } try { // mService 即 AppWidgetServiceImpl mService.updateAppWidgetIds(mPackageName, appWidgetIds, views); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); }}
② 接着跨进程调用AppWidgetServiceImpl的updateAppWidgetIds方法,该方法内部调用重载方法如下
private void updateAppWidgetIds(String callingPackage, int[] appWidgetIds, RemoteViews views, boolean partially) { final int userId = UserHandle.getCallingUserId(); if (appWidgetIds == null || appWidgetIds.length == 0) { return; } // Make sure the package runs under the caller uid. mSecurityPolicy.enforceCallFromPackage(callingPackage); synchronized (mLock) { // 同步操作 ensureGroupStateLoadedLocked(userId); final int N = appWidgetIds.length; for (int i = 0; i < N; i++) { // 遍历更新可以查找到的Widget组件 final int appWidgetId = appWidgetIds[i]; // NOTE: The lookup is enforcing security across users by making // sure the caller can only access widgets it hosts or provides. Widget widget = lookupWidgetLocked(appWidgetId, Binder.getCallingUid(), callingPackage); if (widget != null) { updateAppWidgetInstanceLocked(widget, views, partially); } } }}
③ 接下来继续调用AppWidgetServiceImpl的updateAppWidgetInstanceLocked方法来更新
private void updateAppWidgetInstanceLocked(Widget widget, RemoteViews views, boolean isPartialUpdate) { if (widget != null && widget.provider != null && !widget.provider.zombie && !widget.host.zombie) { if (isPartialUpdate && widget.views != null) { // 部分更新还是更新全部 // For a partial update, we merge the new RemoteViews with the old. widget.views.mergeRemoteViews(views); } else { // For a full update we replace the RemoteViews completely. widget.views = views; } int memoryUsage; if ((UserHandle.getAppId(Binder.getCallingUid()) != Process.SYSTEM_UID) && (widget.views != null) && ((memoryUsage = widget.views.estimateMemoryUsage()) > mMaxWidgetBitmapMemory)) { widget.views = null; throw new IllegalArgumentException("RemoteViews for widget update exceeds" + " maximum bitmap memory usage (used: " + memoryUsage + ", max: " + mMaxWidgetBitmapMemory + ")"); } scheduleNotifyUpdateAppWidgetLocked(widget, widget.getEffectiveViewsLocked()); }}
④ 继续调用AppWidgetServiceImpl的scheduleNotifyUpdateAppWidgetLocked
private void scheduleNotifyUpdateAppWidgetLocked(Widget widget, RemoteViews updateViews) { long requestId = REQUEST_COUNTER.incrementAndGet(); if (widget != null) { widget.updateRequestIds.put(ID_VIEWS_UPDATE, requestId); } if (widget == null || widget.provider == null || widget.provider.zombie || widget.host.callbacks == null || widget.host.zombie) { return; } SomeArgs args = SomeArgs.obtain(); args.arg1 = widget.host; args.arg2 = widget.host.callbacks; args.arg3 = (updateViews != null) ? updateViews.clone() : null; args.arg4 = requestId; args.argi1 = widget.appWidgetId; mCallbackHandler.obtainMessage( CallbackHandler.MSG_NOTIFY_UPDATE_APP_WIDGET, args).sendToTarget(); // 发送通知来异步处理}
⑤ 在mCallbackHandler中处理消息,调用handleNotifyUpdateAppWidget方法
private final class CallbackHandler extends Handler { public static final int MSG_NOTIFY_UPDATE_APP_WIDGET = 1; public static final int MSG_NOTIFY_PROVIDER_CHANGED = 2; public static final int MSG_NOTIFY_PROVIDERS_CHANGED = 3; public static final int MSG_NOTIFY_VIEW_DATA_CHANGED = 4; public CallbackHandler(Looper looper) { super(looper, null, false); } @Override public void handleMessage(Message message) { switch (message.what) { case MSG_NOTIFY_UPDATE_APP_WIDGET: { SomeArgs args = (SomeArgs) message.obj; Host host = (Host) args.arg1; IAppWidgetHost callbacks = (IAppWidgetHost) args.arg2; RemoteViews views = (RemoteViews) args.arg3; long requestId = (Long) args.arg4; final int appWidgetId = args.argi1; args.recycle(); handleNotifyUpdateAppWidget(host, callbacks, appWidgetId, views, requestId); } break; ...
⑥ 在handleNotifyUpdateAppWidget方法中调用AppWidgetHost的updateAppWidget方法来更新
private void handleNotifyUpdateAppWidget(Host host, IAppWidgetHost callbacks, int appWidgetId, RemoteViews views, long requestId) { try { callbacks.updateAppWidget(appWidgetId, views); host.lastWidgetUpdateRequestId = requestId; } catch (RemoteException re) { synchronized (mLock) { Slog.e(TAG, "Widget host dead: " + host.id, re); host.callbacks = null; } }}
⑦ 接下来调用了AppWidgetHost$updateAppWidget,继续发送消息来更新
public void updateAppWidget(int appWidgetId, RemoteViews views) { if (isLocalBinder() && views != null) { views = views.clone(); } Handler handler = mWeakHandler.get(); if (handler == null) { return; } Message msg = handler.obtainMessage(HANDLE_UPDATE, appWidgetId, 0, views); msg.sendToTarget();}
⑧ 在AppWidgetHost的UpdateHandler中处理更新消息
class UpdateHandler extends Handler { public UpdateHandler(Looper looper) { super(looper); } public void handleMessage(Message msg) { switch (msg.what) { case HANDLE_UPDATE: { // 更新 updateAppWidgetView(msg.arg1, (RemoteViews)msg.obj); break; } ... } }}
⑨ 在UpdateHandler中调用updateAppWidgetView来更新,最终调用AppWidgetHostView的updateAppWidget更新view
void updateAppWidgetView(int appWidgetId, RemoteViews views) { AppWidgetHostView v; synchronized (mViews) { v = mViews.get(appWidgetId); } if (v != null) { v.updateAppWidget(views); }}
⑩ 接下来调用到了AppWidgetHostView的updateAppWidget方法,这个方法调用了applyRemoteViews方法
public void updateAppWidget(RemoteViews remoteViews) { applyRemoteViews(remoteViews);}
11. applyRemoteViews方法最终调用了RemoteViews的apply/reapply来更新,apply/reapply方法最终通过反射调用了view的属性来更新
protected void applyRemoteViews(RemoteViews remoteViews) { ... boolean recycled = false; View content = null; Exception exception = null; ... if (remoteViews == null) { if (mViewMode == VIEW_MODE_DEFAULT) { // We've already done this -- nothing to do. return; } content = getDefaultView(); mLayoutId = -1; mViewMode = VIEW_MODE_DEFAULT; } else { if (mAsyncExecutor != null) { inflateAsync(remoteViews); return; } // Prepare a local reference to the remote Context so we're ready to // inflate any requested LayoutParams. mRemoteContext = getRemoteContext(); int layoutId = remoteViews.getLayoutId(); // If our stale view has been prepared to match active, and the new // layout matches, try recycling it if (content == null && layoutId == mLayoutId) { // 已经加载过了则更新 try { remoteViews.reapply(mContext, mView, mOnClickHandler); // reapply只更新界面 content = mView; recycled = true; if (LOGD) Log.d(TAG, "was able to recycle existing layout"); } catch (RuntimeException e) { exception = e; } } // Try normal RemoteView inflation if (content == null) { try { content = remoteViews.apply(mContext, this, mOnClickHandler); // apply需要加载布局并更新界面 if (LOGD) Log.d(TAG, "had to inflate new layout"); } catch (RuntimeException e) { exception = e; } } mLayoutId = layoutId; mViewMode = VIEW_MODE_CONTENT; } applyContent(content, recycled, exception); updateContentDescription(mInfo);}
12 接下来调用RemoteViews的apply/performApply方法,这两个方法都会调用performApply来执行Action的apply
/** @hide */public View apply(Context context, ViewGroup parent, OnClickHandler handler) { RemoteViews rvToApply = getRemoteViewsToApply(context); View result = inflateView(context, rvToApply, parent); loadTransitionOverride(context, handler); rvToApply.performApply(result, parent, handler); return result;}
13 performApply方法遍历要执行的动作集合(每一次更新操作都对应一个Action),然后调用Action的apply方法来执行更新
private void performApply(View v, ViewGroup parent, OnClickHandler handler) { if (mActions != null) { handler = handler == null ? DEFAULT_ON_CLICK_HANDLER : handler; final int count = mActions.size(); for (int i = 0; i < count; i++) { Action a = mActions.get(i); a.apply(v, parent, handler); } }}
14 看Action的一个实现ReflectionAction的apply的执行,很明显通过反射来执行更新
@Override public void apply(View root, ViewGroup rootParent, OnClickHandler handler) { final View view = root.findViewById(viewId); if (view == null) return; Class<?> param = getParameterType(); // 获取Class类型,如int.class , Intent.class if (param == null) { throw new ActionException("bad type: " + this.type); } try { getMethod(view, this.methodName, param).invoke(view, wrapArg(this.value)); // 反射调用相关方法 } catch (ActionException e) { throw e; } catch (Exception ex) { throw new ActionException(ex); } }
总结:
更新流程为: AppWidgetManager$updateAppWidget --> AppWidgetServiceImpl$updateAppWidgetIds ..--> AppWidgetHost$updateAppWidget ..--> AppWidgetHostView$updateAppWidget ..--> remoteViews$apply/reapply ..--> Action$apply
RemoteViews相关内容可参看安卓学习笔记之RemoteViews
更多相关文章
- android 屏幕旋转
- android读取sd卡图片并进行缩放操作
- Android(安卓)vold核心篇(VolumeManager)
- Android中快速为Recyclerview添加头部
- mysql错误:Access denied for user 'root'@'172.19.100.123' to d
- Android事件总线框架设计:EventBus3.0源码详解与架构分析(中)
- android 判断手机是否是国内的手机的方法
- Android(安卓)ViewPager切换的N种动画
- Android(安卓)touch事件的派发流程