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


<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


<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,widget,控件)