安卓学习笔记之使用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

更多相关文章

  1. android 屏幕旋转
  2. android读取sd卡图片并进行缩放操作
  3. Android(安卓)vold核心篇(VolumeManager)
  4. Android中快速为Recyclerview添加头部
  5. mysql错误:Access denied for user 'root'@'172.19.100.123' to d
  6. Android事件总线框架设计:EventBus3.0源码详解与架构分析(中)
  7. android 判断手机是否是国内的手机的方法
  8. Android(安卓)ViewPager切换的N种动画
  9. Android(安卓)touch事件的派发流程

随机推荐

  1. Android设置重复文字水印背景
  2. 【Android】Android(安卓)permission 访
  3. android系统时间获取方式
  4. 有关Android中的java.lang.ClassCastExce
  5. android微信摇一摇功能
  6. Android(安卓)扫描SDCard上的音乐文件以
  7. android Toast工具BToast
  8. android之ListView和adapter配合显示图片
  9. Android(安卓)UDP广播包抓取
  10. android列表为空时提示语