systemui

SystemUI深浅色主题随壁纸改变的流程探析

2018-10-20  本文已影响0人  Soetsu

前言

在Android8.1上有一个新增的特性,当你修改了自己的壁纸时(壁纸的主色调必须有差别,比如从白色壁纸更改为黑色壁纸),你的Android设备的下拉QS、音量调节框、关机对话框等的背景色都会随之改变。


深色主题的Quicksettings

并且在9.0系统上,Google还为这个功能添加了一项开关,用户可以强制设置主题背景色:


设置 - 显示 - 设备主题背景

废话不多说,从源码里来看看根据壁纸的这整套流程是怎么实现的。

设置系统壁纸

首先简单介绍下Android如何修改系统壁纸,如果是第三方应用首先要申请系统权限:

   <uses-permission android:name="android.permission.SET_WALLPAPER"/>

然后需要通过WallpaperManager相关的接口进行静态壁纸的设置。
WallpaperManager提供了setBitmap(bitmap)、setResource(resid)、setStream(inputstream)这三个方法来设置静态壁纸,下面以setBitmap的源码为例来说明一下。

    public int setBitmap(Bitmap fullImage, Rect visibleCropHint,
            boolean allowBackup, @SetWallpaperFlags int which, int userId)
            throws IOException {
        ...
        final Bundle result = new Bundle();
        final WallpaperSetCompletion completion = new WallpaperSetCompletion();
        try {
            // 通过WallpaperManagerService的setWallpaper方法获得一个文件描述符
            ParcelFileDescriptor fd = sGlobals.mService.setWallpaper(null,
                    mContext.getOpPackageName(), visibleCropHint, allowBackup,
                    result, which, completion, userId);
            if (fd != null) {
                FileOutputStream fos = null;
                try {
                    fos = new ParcelFileDescriptor.AutoCloseOutputStream(fd);
                    // 将bitmap写入描述符对应的文件里
                    fullImage.compress(Bitmap.CompressFormat.PNG, 90, fos);
                    fos.close();
                    completion.waitForCompletion();
                } finally {
                    IoUtils.closeQuietly(fos);
                }
            }
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
        return result.getInt(EXTRA_NEW_WALLPAPER_ID, 0);
    }

从以上方法来看,设置壁纸的过程有进行写文件操作,我们知道SystemUI的静态壁纸是ImageWallpaper,那么上述过程是如何通知到ImageWallpaper的呢。下面就来介绍WallpaperManagerService里的一个重要组件:WallpaperObserver。

壁纸改变的监听者

WallpaperObserver继承自FileObserver,而FileObserver类是一个用于监听文件访问、创建、修改、删除、移动等操作的监听器,可以监听一个文件或者一个文件夹,并在上述动作发生时回调onEvent()方法。

回到上面讲的setBitmap方法,会通过WallpaperManagerService的setWallpaper()方法获得一个文件描述符,

    public ParcelFileDescriptor setWallpaper(String name, String callingPackage,
            Rect cropHint, boolean allowBackup, Bundle extras, int which,
            IWallpaperManagerCallback completion, int userId) {
        userId = ActivityManager.handleIncomingUser(getCallingPid(), getCallingUid(), userId,
                false /* all */, true /* full */, "changing wallpaper", null /* pkg */);
        checkPermission(android.Manifest.permission.SET_WALLPAPER);
        ...
        synchronized (mLock) {
            ...
            wallpaper = getWallpaperSafeLocked(userId, which);
            final long ident = Binder.clearCallingIdentity();
            try {
                // 创建一个文件描述符
                ParcelFileDescriptor pfd = updateWallpaperBitmapLocked(name, wallpaper, extras);
                if (pfd != null) {
                    wallpaper.imageWallpaperPending = true;
                    wallpaper.whichPending = which;
                    wallpaper.setComplete = completion;
                    wallpaper.cropHint.set(cropHint);
                    wallpaper.allowBackup = allowBackup;
                }
                return pfd;
            } finally {
                Binder.restoreCallingIdentity(ident);
            }
        }
    }

再看updateWallpaperBitmapLocked()的实现:

    ParcelFileDescriptor updateWallpaperBitmapLocked(String name, WallpaperData wallpaper,
            Bundle extras) {
        if (name == null) name = "";
        try {
            File dir = getWallpaperDir(wallpaper.userId);
            ...
            ParcelFileDescriptor fd = ParcelFileDescriptor.open(wallpaper.wallpaperFile,
                    MODE_CREATE|MODE_READ_WRITE|MODE_TRUNCATE);
            ...
            return fd;
        } catch (FileNotFoundException e) {
            Slog.w(TAG, "Error setting wallpaper", e);
        }
        return null;
    }

从以上实现可知,文件路径是用getWallpaperDir()方法获得的,了解了这点,就方便介绍WallpaperObserver了。在它的构造方法里,会将getWallpaperDir()取得的路径传回给父类的构造方法,从而开始监听wallpaper的CLOSE_WRITE事件。

        public WallpaperObserver(WallpaperData wallpaper) {
            super(getWallpaperDir(wallpaper.userId).getAbsolutePath(),
                    CLOSE_WRITE | MOVED_TO | DELETE | DELETE_SELF);
            ...
        }

下面是用于回调的onEvent()方法的实现:

        @Override
        public void onEvent(int event, String path) {
            if (path == null) {
                return;
            }
            final boolean moved = (event == MOVED_TO);
            final boolean written = (event == CLOSE_WRITE || moved);
            ...
            // 监听CLOSE_WRITE 通知ImageWallpaper进行壁纸更新
            ...
            // Outside of the lock since it will synchronize itself
            if (notifyColorsWhich != 0) {
                // Android 8.1才新增的通知壁纸颜色变化的方法
                notifyWallpaperColorsChanged(wallpaper, notifyColorsWhich);
            }
        }

notifyColorsWhich 是一个int值,初始时为0,当通过Wallpaper path取到的新壁纸File与之前保存的全局变量不一致时,即有壁纸更新,notifyColorsWhich 会与FLAG_SYSTEM进行按位或运算;
notifyWallpaperColorsChanged()方法又会将参数传给notifyColorListeners()方法去处理,这里就直接跳到notifyColorListeners()的实现:

    private void notifyColorListeners(@NonNull WallpaperColors wallpaperColors, int which,
            int userId) {
        final IWallpaperManagerCallback keyguardListener;
        final ArrayList<IWallpaperManagerCallback> colorListeners = new ArrayList<>();
        synchronized (mLock) {
            final RemoteCallbackList<IWallpaperManagerCallback> currentUserColorListeners =
                    mColorsChangedListeners.get(userId);
            ...
            keyguardListener = mKeyguardListener;

            if (currentUserColorListeners != null) {
                final int count = currentUserColorListeners.beginBroadcast();
                for (int i = 0; i < count; i++) {
                    colorListeners.add(currentUserColorListeners.getBroadcastItem(i));
                }
                currentUserColorListeners.finishBroadcast();
            }
            ...
        }

        final int count = colorListeners.size();
        for (int i = 0; i < count; i++) {
            try {
                // 通知系统壁纸颜色改变
                colorListeners.get(i).onWallpaperColorsChanged(wallpaperColors, which, userId);
            } catch (RemoteException e) {
                ...
            }
        }
        ...
    }

notifyWallpaperColorsChanged()方法会分别通知系统壁纸和锁屏壁纸颜色改变的监听者,我们暂时只先分析系统壁纸相关的流程。在这个方法里,首先会通过mColorsChangedListeners取得当前userId的一个RemoteCallbackList,这个类是一个系统层的容器类,容纳的对象是一些接口mCallbacks ,用于执行列表中对象的回调函数,里面有几个比较重要的方法:

看的出WallPaperObserver的currentUserColorListeners里存放的是一些IWallpaperManagerCallback对象,之后通过getBroadcastItem()遍历出所有的callbacks,并分别调用每个callback的onWallpaperColorsChanged()。

SystemUI的注册与响应过程

StatusBar的onStart()方法中有这么一步:

    mColorExtractor = Dependency.get(SysuiColorExtractor.class);
    mColorExtractor.addOnColorsChangedListener(this);

SysuiColorExtractor继承于ColorExtractor,后者又实现了WallpaperManager.OnColorsChangedListener

public SysuiColorExtractor(Context context, ExtractionType type, boolean registerVisibility) {
        super(context, type);
        ...
        WallpaperManager wallpaperManager = context.getSystemService(WallpaperManager.class);
        if (wallpaperManager != null) {
            // Listen to all users instead of only the current one.
            wallpaperManager.removeOnColorsChangedListener(this);
            wallpaperManager.addOnColorsChangedListener(this, null /* handler */,
                    UserHandle.USER_ALL);
        }
    }

在SysuiColorExtractor的构造函数里,通过WallpaperManager的addOnColorsChangedListener()方法,把自身这个OnColorsChangedListener给注册过去,最终会调用到WallpaperManager.sGlobals的addOnColorsChangedListener():

    private static class Globals extends IWallpaperManagerCallback.Stub {
        private final IWallpaperManager mService;
        ...
        public void addOnColorsChangedListener(@NonNull OnColorsChangedListener callback,
                @Nullable Handler handler, int userId) {
            synchronized (this) {
                if (!mColorCallbackRegistered) {
                    try {
                        mService.registerWallpaperColorsCallback(this, userId);
                        ...
                    } catch (RemoteException e) {
                        ...
                    }
                }
                // 把callback保存下来,这个callback是ColorExtractor
                mColorListeners.add(new Pair<>(callback, handler));
            }
        }
    }

然后会把自己这个IWallpaperManagerCallback以及userId,通过WallpaperManagerService的registerWallpaperColorsCallback()方法传递过去:

    public void registerWallpaperColorsCallback(IWallpaperManagerCallback cb, int userId) {
        userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
                userId, true, true, "registerWallpaperColorsCallback", null);
        synchronized (mLock) {
            RemoteCallbackList<IWallpaperManagerCallback> userColorsChangedListeners =
                    mColorsChangedListeners.get(userId);
            if (userColorsChangedListeners == null) {
                userColorsChangedListeners = new RemoteCallbackList<>();
                mColorsChangedListeners.put(userId, userColorsChangedListeners);
            }
            userColorsChangedListeners.register(cb);
        }
    }

到这一步就和上一节的通知过程对应上了,给mColorsChangedListeners添加新的RemoteCallbackList对象,并注册相应的callback进去(这里的callback是一个Globals对象),方便后续通知。

好了,注册过程讲完了,最后再来看看SystemUI是怎么响应壁纸颜色改变的。回到上一节的最末尾,我们说到 “通过getBroadcastItem()遍历出所有的callbacks,并分别调用每个callback的onWallpaperColorsChanged()”,已经知道这一处的callback是WallpaperManager里的Globals类的对象了,找到它的实现:

        @Override
        public void onWallpaperColorsChanged(WallpaperColors colors, int which, int userId) {
            synchronized (this) {
                for (Pair<OnColorsChangedListener, Handler> listener : mColorListeners) {
                    Handler handler = listener.second;
                    if (listener.second == null) {
                        handler = mMainLooperHandler;
                    }
                    handler.post(() -> {
                        // Dealing with race conditions between posting a callback and
                        // removeOnColorsChangedListener being called.
                        boolean stillExists;
                        synchronized (sGlobals) {
                            stillExists = mColorListeners.contains(listener);
                        }
                        if (stillExists) {
                            listener.first.onColorsChanged(colors, which, userId);
                        }
                    });
                }
            }
        }

在前面的注释中已经介绍过,存在listener这个Pair里的first是一个ColorExtractor,回到SystemUI,

    @Override
    public void onColorsChanged(WallpaperColors colors, int which) {
        ...
        if ((which & WallpaperManager.FLAG_SYSTEM) != 0) {
            mSystemColors = colors;
            GradientColors[] systemColors = mGradientColors.get(WallpaperManager.FLAG_SYSTEM);
            extractInto(colors, systemColors[TYPE_NORMAL], systemColors[TYPE_DARK],
                    systemColors[TYPE_EXTRA_DARK]);
            changed = true;
        }
        if (changed) {
            triggerColorsChanged(which);
        }
    }

    protected void triggerColorsChanged(int which) {
        ArrayList<WeakReference<OnColorsChangedListener>> references =
                new ArrayList<>(mOnColorsChangedListeners);
        final int size = references.size();
        for (int i = 0; i < size; i++) {
            final WeakReference<OnColorsChangedListener> weakReference = references.get(i);
            final OnColorsChangedListener listener = weakReference.get();
            if (listener == null) {
                mOnColorsChangedListeners.remove(weakReference);
            } else {
                listener.onColorsChanged(this, which);
            }
        }
    }

    public interface OnColorsChangedListener {
        void onColorsChanged(ColorExtractor colorExtractor, int which);
    }

遍历mOnColorsChangedListeners里的所有listener并调用其onColorsChanged()方法,在这一节的最开始我们有提到StatusBar把自己作为参数通过addOnColorsChangedListener()传给了ColorExtractor,之后便被加入到mOnColorsChangedListeners里面,过程很简单,这里就不再赘述了。之后自然是调用StatusBar的onColorsChanged()了:

    @Override
    public void onColorsChanged(ColorExtractor extractor, int which) {
        updateTheme();
    }

    /**
     * Switches theme from light to dark and vice-versa.
     */
    protected void updateTheme() {
        final boolean inflated = mStackScroller != null;

        // The system wallpaper defines if QS should be light or dark.
        WallpaperColors systemColors = mColorExtractor
                .getWallpaperColors(WallpaperManager.FLAG_SYSTEM);
        final boolean useDarkTheme = systemColors != null
                && (systemColors.getColorHints() & WallpaperColors.HINT_SUPPORTS_DARK_THEME) != 0;
        if (isUsingDarkTheme() != useDarkTheme) {
            mUiOffloadThread.submit(() -> {
                try {
                    mOverlayManager.setEnabled("com.android.systemui.theme.dark",
                            useDarkTheme, mLockscreenUserManager.getCurrentUserId());
                } catch (RemoteException e) {
                    Log.w(TAG, "Can't change theme", e);
                }
            });
        }
        ...

        if (inflated) {
            int which;
            if (mState == StatusBarState.KEYGUARD || mState == StatusBarState.SHADE_LOCKED) {
                which = WallpaperManager.FLAG_LOCK;
            } else {
                which = WallpaperManager.FLAG_SYSTEM;
            }
            final boolean useDarkText = mColorExtractor.getColors(which,
                    true /* ignoreVisibility */).supportsDarkText();
            mStackScroller.updateDecorViews(useDarkText);

            // Make sure we have the correct navbar/statusbar colors.
            mStatusBarWindowManager.setKeyguardDark(useDarkText);
        }
    }

最后在updateTheme()这个方法中修改SystemUI相关的深色和浅色主题。


以上就是整个注册以及响应的流程了,至于到了updateTheme()之后SystemUI是以怎样的方式去具体修改那些控件的主题的,后续有时间会继续再研究一下。

上一篇下一篇

猜你喜欢

热点阅读