美文网首页OpenDreamer
安卓小部件刷新源码解析一非列表

安卓小部件刷新源码解析一非列表

作者: OpenDreamer | 来源:发表于2022-06-10 10:50 被阅读0次

    一、刷新流程

    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
    2、updateAppWidget()/partiallyUpdateAppWidget 局部更新
    调用AppWidgetManager.updateAppWidget(),在此之前一般都会设置相应的RemoteView
    3、updateAppWidgetIds()
    这里通过AIDL跨进程技术调用system_progress进程的AppWidgetServiceImpl对象。
    3.1、enforceCallFromPackage
    安全性校验,确定请求的包命和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;
        }
    }
    

    相关文章

      网友评论

        本文标题:安卓小部件刷新源码解析一非列表

        本文链接:https://www.haomeiwen.com/subject/vonvmrtx.html