天天看點

安卓學習筆記之使用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(), , i, );
                // 設定點選事件
                views.setOnClickPendingIntent(R.id.bt_clean, pendingIntent );

                appWidgetProvider = new ComponentName(getApplicationContext(), SaferAppWidgetProvider.class);
                awm.updateAppWidget(appWidgetProvider, views);

            }
        };
        timer.schedule(task, , );
        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