Android Widget (小部件)刷新源码解析一非列表

791 阅读8分钟

本文是 Android Widget(小部件) 系列的第二篇,主要是从源码角度去分析非列表小部件刷新的流程。

本系列的目的是通过对Android 小部件的梳理,了解小部件刷新流程、恢复流程、以及系统发生变化时,小部件是如何适配的,解决在开发小部件过程中遇到的问题。系列文章大部份来自源码的解读,内容非常多,也非常容易遗忘,因此记录分享。

系列文章

Android Widget (小部件)基础介绍以及常见问题

Android Widget (小部件)刷新源码解析一非列表

Android Widget (小部件)刷新源码解析一列表

一、刷新流程

  1. system_process 发送广播
  2. 应用widget收到广播,准备数据构建RemoteView,并调用AppWidgetManager的updateAppWidget()方法
  3. AppWidgetManager 通过AIDL 通知 system_process更新,system_process收到回调后做一些列操作
  4. system_process 通过AIDL 回调Host 更新方法,Host 解析RemoteView,并生成相应的视图

二、详细刷新流程

widget 更新 -1-.png

1、system_process 发送广播
更新广播的action 为android.appwidget.action.APPWIDGET_UPDATE
2updateAppWidget()/partiallyUpdateAppWidget 局部更新
调用AppWidgetManager.updateAppWidget(),在此之前一般都会设置相应的RemoteView
3updateAppWidgetIds()
这里通过AIDL跨进程技术调用system_progress进程的AppWidgetServiceImpl对象。
3.1enforceCallFromPackage()
安全性校验,确定请求的包命和uid 是一致的
3.2、ensureGroupStateLoadedLocked
若已经加载过了则return,若没有加载过则根据uid 获取widgetProvider(过滤带刷新action 的广播),根据uid 获取相应的配置文件,根据配置文件设置widget,并绑定相应的host。放入mWidgets中。
3.3、lookupWidgetLocked
根据widgetId在mWidgets 找到对应的widget,通过uid验证权限
3.4、updateAppWidgetInstanceLocked
替换widget 的remoteView(局部刷新是合并两次的RemoteView),检查RemoteView的bitmap 是否超过最大限制。
3.4.1、scheduleNotifyUpdateAppWidgetLocked 
mCallbackHandler 发送更新message
3.5、handleNotifyUpdateAppWidget
host 的 updateAppWidget()方法
4、通过AIDL 回调Host 的updateAppWidget( ),send 更新 message
4.1、handler  处理更新message
4.1.1、updateAppWidgetView
在host的mViews中通过widgetId 找到对应的AppWidgetHostView,让后进行更新
4.1.1.1、applyRemoteViews将remoteView 中的action 设置在相应view 上

三、详细刷新流程代码分析

1、system_process 发送广播 ,更新广播的action 为android.appwidget.action.APPWIDGET_UPDATE。

// AppWidgetProvider 继承于广播,system_process发送广播是会回调onReceive方法
// 如果是更新广播的话会回调onUpdate()方法
public class AppWidgetProvider extends BroadcastReceiver {
   ...
   public void onReceive(Context context, Intent intent) {
    // Protect against rogue update broadcasts (not really a security issue,
    // just filter bad broacasts out so subclasses are less likely to crash).
    String action = intent.getAction();
    if (AppWidgetManager.ACTION_APPWIDGET_UPDATE.equals(action)) {
        Bundle extras = intent.getExtras();
        if (extras != null) {
            int[] appWidgetIds = extras.getIntArray(AppWidgetManager.EXTRA_APPWIDGET_IDS);
            if (appWidgetIds != null && appWidgetIds.length > 0) {
                this.onUpdate(context, AppWidgetManager.getInstance(context), appWidgetIds);
            }
        }
    }
   ...
}

2、应用widget收到广播,准备数据构建RemoteView,并调用AppWidgetManager的updateAppWidget()方法

public abstract class TestWidgetProvider extends AppWidgetProvider {
    ...
    /**
    *onReceive会回调该方法
    *
    **/
    @Override
    public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds{
    // AppWidgetProvider 这里通常会设置new RemoteView,并设置,可设置点击时间、文字、图片等最后调用
    // appWidgetManager.updateAppWidget()
        super.onUpdate(context, appWidgetManager, appWidgetIds);
        for (int widgetId : appWidgetIds) {
            RemoteViews remoteViews = new RemoteViews(context.getPackageName(),         
            R.layout.widget_test);
             ...
            appWidgetManager.updateAppWidget(widgetId, remoteViews);
    }
    }
  ...
}

3、updateAppWidgetIds()方法,这里通过AIDL跨进程技术调用system_progress进程的AppWidgetServiceImpl对象。

public class AppWidgetManager {
    ...
    public void updateAppWidget(int[] appWidgetIds, RemoteViews views) {
        mService 为 AppWidgetServiceImpl
        if (mService == null) {
        return;
        }
        try {
            // 通过AIDL 调用 AppWidgetServiceImpl 的 mService.updateAppWidgetIds 方法
            mService.updateAppWidgetIds(mPackageName, appWidgetIds, views);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }
   ...
 
}
 // partially 代表是否部分更新
 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.
    // AppWidgetServiceImpl 运行在system_process ,包名为字符串传入,
    // 安全性校验,确定请求的包命和uid 是一致的
    mSecurityPolicy.enforceCallFromPackage(callingPackage);
    synchronized (mLock) {
         // 是否解锁状态,处于解锁状态,若第一次加载则构建widget,后面会详细解析
        ensureGroupStateLoadedLocked(userId);
 
        final int N = appWidgetIds.length;
        for (int i = 0; i < N; i++) {
            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.
            // 根据widgetId在mWidgets 找到对应的widget,通过uid验证权限,后面会详细解析
            Widget widget = lookupWidgetLocked(appWidgetId,
                    Binder.getCallingUid(), callingPackage);
 
 
            if (widget != null) {
                // 更新widget
                updateAppWidgetInstanceLocked(widget, views, partially);
            }
        }
    }
}

3.1、enforceCallFromPackage()方法,安全性校验,确定请求的包命和uid 是一致的。

public void enforceCallFromPackage(String packageName) {
    mAppOpsManager.checkPackage(Binder.getCallingUid(), packageName);
}
@Deprecated
public void checkPackage(int uid, @NonNull String packageName) {
    try {
        // 检查请求的 uid 和 packageName  是否一致
        if (mService.checkPackage(uid, packageName) != MODE_ALLOWED) {
            throw new SecurityException(
                    "Package " + packageName + " does not belong to " + uid);
        }
    } catch (RemoteException e) {
        throw e.rethrowFromSystemServer();
    }
}

3.2、ensureGroupStateLoadedLocked()方法,若已经加载过了则return,若没有加载过则根据uid 获取widgetProvider(过滤带刷新action 的广播),根据uid 获取相应的配置文件,根据配置文件设置widget,并绑定相应的host。放入mWidgets中。

private void ensureGroupStateLoadedLocked(int userId, boolean enforceUserUnlockingOrUnlocked) {
    // 判断该应用是否处在解锁状态,设备锁
    if (enforceUserUnlockingOrUnlocked && !isUserRunningAndUnlocked(userId)) {
        throw new IllegalStateException(
                "User " + userId + " must be unlocked for widgets to be available");
    }
    // 判断该应用文件配置是否处在解锁状态
    if (enforceUserUnlockingOrUnlocked && isProfileWithLockedParent(userId)) {
        throw new IllegalStateException(
                "Profile " + userId + " must have unlocked parent");
    }
    // 获取能用的配置配置id
    final int[] profileIds = mSecurityPolicy.getEnabledGroupProfileIds(userId);
 
 
    // 查看是否有未加载的user https://www.jianshu.com/p/3ad2163f7d34
    //https://blog.csdn.net/qq_14978113/article/details/94654401
    // Careful lad, we may have already loaded the state for some
    // group members, so check before loading and read only the
    // state for the new member(s).
    int newMemberCount = 0;
    final int profileIdCount = profileIds.length;
    for (int i = 0; i < profileIdCount; i++) {
        final int profileId = profileIds[i];
        // >=0代表已经加载过,标记数组
        if (mLoadedUserIds.indexOfKey(profileId) >= 0) {
            profileIds[i] = LOADED_PROFILE_ID;
        } else {
            newMemberCount++;
        }
    }
    // 没有新加的 便会return
    if (newMemberCount <= 0) {
        return;
    }
     // 构建新增加的ProfileId 数组,后续通常在第一次加载的时候执行
    int newMemberIndex = 0;
    final int[] newProfileIds = new int[newMemberCount];
    for (int i = 0; i < profileIdCount; i++) {
        final int profileId = profileIds[i];
        if (profileId != LOADED_PROFILE_ID) {
            mLoadedUserIds.put(profileId, profileId);
            newProfileIds[newMemberIndex] = profileId;
            newMemberIndex++;
        }
    }
    // 清除provider 和 host 的tag 设置为 TAG_UNDEFINED
    clearProvidersAndHostsTagsLocked();
     // 根据加载ProfileId 获取系统 ResolveInfo 列表, 根据ResolveInfo 构建
     provider;
    loadGroupWidgetProvidersLocked(newProfileIds);
    // 从系统配置文件/data/system/users/0/appwidgets.xml 加载状态、
    loadGroupStateLocked(newProfileIds);
}

3.3、lookupWidgetLocked()方法,根据widgetId在mWidgets 找到对应的widget,通过uid验证权限。

private Widget lookupWidgetLocked(int appWidgetId, int uid, String packageName) {
    final int N = mWidgets.size();
    for (int i = 0; i < N; i++) {
        Widget widget = mWidgets.get(i);
        if (widget.appWidgetId == appWidgetId
                && mSecurityPolicy.canAccessAppWidget(widget, uid, packageName)) {
            return widget;
        }
    }
    return null;
}

3.4、updateAppWidgetInstanceLocked()方法,替换widget 的remoteView(局部刷新是合并两次的RemoteView),检查RemoteView的bitmap 是否超过最大限制。

private void updateAppWidgetInstanceLocked(Widget widget, RemoteViews views,
        boolean isPartialUpdate) {
    if (widget != null && widget.provider != null
            && !widget.provider.zombie && !widget.host.zombie) {
 
 
        // 如果是局部更新,合并remote view
        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;
        }
        // 如果非系统应用 bitmap 的大小超过规定大小,则抛出异常
        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());
    }
}
 
/***
**合并action 
**/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public void mergeRemoteViews(RemoteViews newRv) {
    if (newRv == null) return;
    // We first copy the new RemoteViews, as the process of merging modifies the way the actions
    // reference the bitmap cache. We don't want to modify the object as it may need to
    // be merged and applied multiple times.
    RemoteViews copy = new RemoteViews(newRv);
 
 
    HashMap<String, Action> map = new HashMap<String, Action>();
    if (mActions == null) {
        mActions = new ArrayList<Action>();
    }
 
    // 将老的action 放入map
    int count = mActions.size();
    for (int i = 0; i < count; i++) {
        Action a = mActions.get(i);
        map.put(a.getUniqueKey(), a);
    }
 
 
    ArrayList<Action> newActions = copy.mActions;
    if (newActions == null) return;
    count = newActions.size();
    for (int i = 0; i < count; i++) {
        Action a = newActions.get(i);
        //由viewId和action tag 组成
        String key = newActions.get(i).getUniqueKey();
        // R开始支持
        int mergeBehavior = newActions.get(i).mergeBehavior();
        // 相同则把旧的action 移除
        if (map.containsKey(key) && mergeBehavior == Action.MERGE_REPLACE) {
            mActions.remove(map.get(key));
            map.remove(key);
        }
 
 
        // If the merge behavior is ignore, we don't bother keeping the extra action
        if (mergeBehavior == Action.MERGE_REPLACE || mergeBehavior == Action.MERGE_APPEND) {
            mActions.add(a);
        }
    }
 
    存储设置的bitmap
    // Because pruning can remove the need for bitmaps, we reconstruct the bitmap cache
    mBitmapCache = new BitmapCache();
    setBitmapCache(mBitmapCache);
}

3.4.1、scheduleNotifyUpdateAppWidgetLocked()方法,mCallbackHandler 发送更新message。

private void scheduleNotifyUpdateAppWidgetLocked(Widget widget, RemoteViews updateViews) {
    long requestId = UPDATE_COUNTER.incrementAndGet();
    if (widget != null) {
        widget.updateSequenceNos.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();
}

3.5、handleNotifyUpdateAppWidget()方法,host 的 updateAppWidget()方法,通过通过AIDL 回调到host 中。

private void handleNotifyUpdateAppWidget(Host host, IAppWidgetHost callbacks,
        int appWidgetId, RemoteViews views, long requestId) {
    try {
        callbacks.updateAppWidget(appWidgetId, views);
        host.lastWidgetUpdateSequenceNo = requestId;
    } catch (RemoteException re) {
        synchronized (mLock) {
            Slog.e(TAG, "Widget host dead: " + host.id, re);
            host.callbacks = null;
        }
    }
}

4、通过AIDL 回调Host 的updateAppWidget( ),send 更新 message

public class AppWidgetHost {
    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();
    }
}

4.1、handler  处理更新message。

public void handleMessage(Message msg) {
    switch (msg.what) {
        case HANDLE_UPDATE: {
            updateAppWidgetView(msg.arg1, (RemoteViews)msg.obj);
            break;
        }
    }
}

4.1.1、updateAppWidgetView()方法,在host的mViews中通过widgetId 找到对应的AppWidgetHostView,让后进行更新。

void updateAppWidgetView(int appWidgetId, RemoteViews views) {
    AppWidgetHostView v;
    synchronized (mViews) {
        v = mViews.get(appWidgetId);
    }
    if (v != null) {
        v.updateAppWidget(views);
    }
}

4.1.1.1、applyRemoteViews()方法,将remoteView 中的action 设置在相应view 上。

protected void applyRemoteViews(@Nullable RemoteViews remoteViews, boolean useAsyncIfPossible) {
    boolean recycled = false;
    View content = null;
    Exception exception = null;
 
    // Block state restore until the end of the apply.
    mLastInflatedRemoteViewsId = -1;
 
    if (mLastExecutionSignal != null) {
        mLastExecutionSignal.cancel();
        mLastExecutionSignal = null;
    }
    // 如果remoteViews 为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 {
        // Select the remote view we are actually going to apply.
        // 查找适合的remoteView 找不到时返回最小的
        RemoteViews rvToApply = remoteViews.getRemoteViewsToApply(mContext, mCurrentSize);
        // 是否 设置了
        /if (mOnLightBackground) {
            // 如果深色浅色模式变了,使用相应的 layout id ,new remoteview
            rvToApply = rvToApply.getDarkTextViews();
        }
 
        if (mAsyncExecutor != null && useAsyncIfPossible) {
            // 异步加载
            inflateAsync(rvToApply);
            return;
        }
        int layoutId = rvToApply.getLayoutId();
        if (rvToApply.canRecycleView(mView)) {
            try {
                rvToApply.reapply(mContext, mView, mInteractionHandler, mCurrentSize,
                        mColorResources);
                content = mView;
                mLastInflatedRemoteViewsId = rvToApply.computeUniqueId(remoteViews);
                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 = rvToApply.apply(mContext, this, mInteractionHandler,
                        mCurrentSize, mColorResources);
                mLastInflatedRemoteViewsId = rvToApply.computeUniqueId(remoteViews);
                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);
}
 
 
/**
**将action 转成相应的设置
**/
private void reapply(Context context, View v, InteractionHandler handler, SizeF size,
        ColorResources colorResources, boolean topLevel) {
 
 
    RemoteViews rvToApply = getRemoteViewsToApply(context, size);
 
 
    // In the case that a view has this RemoteViews applied in one orientation or size, is
    // persisted across change, and has the RemoteViews re-applied in a different situation
    // (orientation or size), we throw an exception, since the layouts may be completely
    // unrelated.
    if (hasMultipleLayouts()) {
        if (!rvToApply.canRecycleView(v)) {
            throw new RuntimeException("Attempting to re-apply RemoteViews to a view that" +
                    " that does not share the same root layout id.");
        }
    }
    // 将action 转成相应行为
    rvToApply.performApply(v, (ViewGroup) v.getParent(), handler, colorResources);
    
    // If the parent of the view is has is a root, resolve the recycling.
    if (topLevel && v instanceof ViewGroup) {
        finalizeViewRecycling((ViewGroup) v);
    }
}
 
/**
**将生成的view 添加到host 中
**/
private void applyContent(View content, boolean recycled, Exception exception) {
    if (content == null) {
        if (mViewMode == VIEW_MODE_ERROR) {
            // We've already done this -- nothing to do.
            return ;
        }
        if (exception != null) {
            Log.w(TAG, "Error inflating RemoteViews", exception);
        }
        content = getErrorView();
        mViewMode = VIEW_MODE_ERROR;
    }
    // 复用的场景直接在view上进行了设置,而view 之前已经添加到host,所以复用的case 这里就不用处理。
    if (!recycled) {
        prepareView(content);
        addView(content);
    }
 
 
    if (mView != content) {
        removeView(mView);
        mView = content;
    }
}

到这里,非列表小部件的刷新就梳理完了。里面涉及到很多东西,比如跨进程通信、安全性校验、多用户、分身等,但如果这些不是你关注的,可以选择忽略,因为并耽误你去理解刷新的整个过程。最后提醒一下刷新是一个跨进程的操作,因此最好在子线程中调用。