美文网首页Android 源码浅析
Android 10.0 锁屏壁纸 LockscreenWall

Android 10.0 锁屏壁纸 LockscreenWall

作者: 孤街酒客0911 | 来源:发表于2022-10-19 18:09 被阅读0次

    学习笔记:

    一、设置壁纸

    通过系统设置进行锁屏壁纸和桌面壁纸的设置。
    Setting 部分的代码:

    // DefaultWallpaperPersister.java
        private int setStreamToWallpaperManagerCompat(InputStream inputStream, boolean allowBackup,
                int whichWallpaper) {
            try {
                // whichWallpaper  // 壁纸类型
                return mWallpaperManagerCompat.setStream(inputStream, null, allowBackup,
                        whichWallpaper);
            } catch (IOException e) {
                return 0;
            }
        }
    
    
      ...
    //    int whichWallpaper;    // 壁纸类型
    //    if (mDestination == DEST_HOME_SCREEN) {    // 桌面壁纸
    //        whichWallpaper = WallpaperManagerCompat.FLAG_SYSTEM;
    //    } else if (mDestination == DEST_LOCK_SCREEN) {  // 锁屏壁纸
    //        whichWallpaper = WallpaperManagerCompat.FLAG_LOCK;
    //    } else { // DEST_BOTH    // 桌面壁纸 和 锁屏壁纸
     //       whichWallpaper = WallpaperManagerCompat.FLAG_SYSTEM
    //                | WallpaperManagerCompat.FLAG_LOCK;
    //    }
      ...
    
    

    mWallpaperManagerCompat 其实就是 WallpaperManagerCompatV16 的对象。

    // WallpaperManagerCompatV16.java
        @Override
        public int setStream(InputStream data, Rect visibleCropHint, boolean allowBackup,
                             int whichWallpaper) throws IOException {
            mWallpaperManager.setStream(data);
            // Return a value greater than zero to indicate success.
            return 1;
        }
    

    由此可知,壁纸的设置是通过 WallpaperManager 类来进行的。

    二、锁屏壁纸的显示
    锁屏壁纸显示流程图: 壁纸显示流程.png

    上面应用程序设置完成了,下面就该进行壁纸显示了。
    WallpaperManager#setStream()

    // WallpaperManager.java 
        public int setStream(InputStream bitmapData, Rect visibleCropHint,
                boolean allowBackup, @SetWallpaperFlags int which)
                        throws IOException {
    
            // 省略部分代码......
    
            try {
                //sGlobals.mService即 WallpaperManagerService。
                // WallpaperManager 在 SystemServiceRegistry 实例化,
                // 过程中传入 WallpaperManagerService 的 binder 对象。   
                ParcelFileDescriptor fd = sGlobals.mService.setWallpaper(null,
                        mContext.getOpPackageName(), visibleCropHint, allowBackup,
                        result, which, completion, mContext.getUserId());
                if (fd != null) {
                    FileOutputStream fos = null;
                    try {
                        // 将壁纸copy一份并存储到对应目录,
                        // 默认是/data/system/users/0/wallpaper(或wallpaper_lock),
                        // 其中0是主用户的userId,支持多用户
                        fos = new ParcelFileDescriptor.AutoCloseOutputStream(fd);
                        copyStreamToWallpaperFile(bitmapData, fos);
                        fos.close();
                        completion.waitForCompletion();
                    } finally {
                        IoUtils.closeQuietly(fos);
                    }
                }
            } catch (RemoteException e) {
                throw e.rethrowFromSystemServer();
            }
            return result.getInt(EXTRA_NEW_WALLPAPER_ID, 0);
        }
    

    这里注意两个方法:sGlobals.mService.setWallpaper()fos.close()

    先看第一个WallpaperManagerService#setWallpaper()方法:

    // WallpaperManagerService.java
        @Override
        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);
            
             //调用setStream方法的时候参数which必须是正确的
            if ((which & (FLAG_LOCK|FLAG_SYSTEM)) == 0) {
                final String msg = "Must specify a valid wallpaper category to set";
                Slog.e(TAG, msg);
                throw new IllegalArgumentException(msg);
            }
    
            // 省略部分代码......
    
            synchronized (mLock) {
                if (DEBUG) Slog.v(TAG, "setWallpaper which=0x" + Integer.toHexString(which));
                WallpaperData wallpaper;
      
                //如果当前没有锁屏壁纸的话,并且是设置桌面壁纸即which == FLAG_SYSTEM,那么同时设置为锁屏壁纸
                if (which == FLAG_SYSTEM && mLockWallpaperMap.get(userId) == null) {
                    migrateSystemToLockWallpaperLocked(userId);
                }
    
                wallpaper = getWallpaperSafeLocked(userId, which);
                final long ident = Binder.clearCallingIdentity();
                try {
                    // updateWallpaperBitmapLocked() 将创建一个文件描述符
                    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()方法:

    // WallpaperManagerService.java
        ParcelFileDescriptor updateWallpaperBitmapLocked(String name, WallpaperData wallpaper,
                Bundle extras) {
            if (name == null) name = "";
            try {
                // 通过getWallpaperDir() 获取文件路径;这个方法值得注意:后面会讲到。
                File dir = getWallpaperDir(wallpaper.userId);
                if (!dir.exists()) {
                    dir.mkdir();
                    FileUtils.setPermissions(
                            dir.getPath(),
                            FileUtils.S_IRWXU|FileUtils.S_IRWXG|FileUtils.S_IXOTH,
                            -1, -1);
                }
                // 创建一个文件描述符,并返回。
                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;
        }
    

    这里再看fos.close(),这个方法本身没什么可以看的,就是 FileOutputStream 文件字节输出流结束。但是这里涉及到了 WallpaperManagerService 的一个内部类 WallpaperObserver,通过名字我们就能知道它是一个观察者。

    WallpaperObserver 初始化:在 WallpaperManagerService 初始化时,会调用 systemReady() 通过getWallpaperSafeLocked()方法初始化 WallpaperData,而这个 WallpaperData 中有个变量 wallpaperObserver ,也在开机时服务初始化, systemReady() 中调用 switchUser() 执行了 wallpaperObserver.startWatching()。

    WallpaperObserver 这个内部类的作用:观察壁纸的变化并通知所有 IWallpaperServiceCallbacks 壁纸已经改变。 CREATE 在没有设置壁纸时触发,并且是第一次创建。每次更改壁纸时都会触发 CLOSE_WRITE,这也是关注fos.close()的原因。

    所以文件的变化触发 WallpaperObserver 的 onEvent() :

    // WallpaperManagerService.java
            @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 事件的文件路径
                final File changedFile = new File(mWallpaperDir, path);
    
                // System and system+lock changes happen on the system wallpaper input file;
                // lock-only changes happen on the dedicated lock wallpaper input file
                // 用于判断事件是不是这个事件发生的。
                final boolean sysWallpaperChanged = (mWallpaperFile.equals(changedFile));
                final boolean lockWallpaperChanged = (mWallpaperLockFile.equals(changedFile));
                int notifyColorsWhich = 0;
                WallpaperData wallpaper = dataForEvent(sysWallpaperChanged, lockWallpaperChanged);
    
    
                // 如果是锁屏壁纸更新
                if (moved && lockWallpaperChanged) {
                    SELinux.restorecon(changedFile);
                    notifyLockWallpaperChanged();
                    notifyWallpaperColorsChanged(wallpaper, FLAG_LOCK);
                    return;
                }
    
                synchronized (mLock) {
                    if (sysWallpaperChanged || lockWallpaperChanged) {
                        notifyCallbacksLocked(wallpaper);
                        if (wallpaper.wallpaperComponent == null
                                || event != CLOSE_WRITE // includes the MOVED_TO case
                                || wallpaper.imageWallpaperPending) {
                            if (written) {
                      
                                SELinux.restorecon(changedFile);
                                if (moved) {
                                    loadSettingsLocked(wallpaper.userId, true);
                                }
                                generateCrop(wallpaper);
                              
                                wallpaper.imageWallpaperPending = false;
                                if (sysWallpaperChanged) {
                                    // 桌面壁纸变化,那么bind ImageWallpaper,ImageWallpaper是负责显示静态桌面壁纸的
                                   bindWallpaperComponentLocked(mImageWallpaper, true,
                                            false, wallpaper, null);
                                    notifyColorsWhich |= FLAG_SYSTEM;
                                }
                                if (lockWallpaperChanged
                                        || (wallpaper.whichPending & FLAG_LOCK) != 0) {
                                    if (DEBUG) {
                                        Slog.i(TAG, "Lock-relevant wallpaper changed");
                                    }
                                
                                    if (!lockWallpaperChanged) {
                                        //如果参数which是system+lock,也就是同时设置锁屏和桌面壁纸,那么remove锁屏壁纸,因为已经是同一张壁纸了
                                        mLockWallpaperMap.remove(wallpaper.userId);
                                    }
                                    // and in any case, tell keyguard about it
                                    notifyLockWallpaperChanged();
                                    notifyColorsWhich |= FLAG_LOCK;
                                }
    
                                saveSettingsLocked(wallpaper.userId);
    
                                // Publish completion *after* we've persisted the changes
                                if (wallpaper.setComplete != null) {
                                    try {
                                        wallpaper.setComplete.onWallpaperChanged();
                                    } catch (RemoteException e) {
                                        // if this fails we don't really care; the setting app may just
                                        // have crashed and that sort of thing is a fact of life.
                                    }
                                }
                            }
                        }
                    }
                }
    
                // Outside of the lock since it will synchronize itself
                if (notifyColorsWhich != 0) {
                    notifyWallpaperColorsChanged(wallpaper, notifyColorsWhich);
                }
            }
    

    先看锁屏壁纸更新这一部分notifyLockWallpaperChanged()

    // WallpaperManagerService.java
        private void notifyLockWallpaperChanged() {
            final IWallpaperManagerCallback cb = mKeyguardListener;
            if (cb != null) {
                try {
                    cb.onWallpaperChanged();
                } catch (RemoteException e) {
                    // Oh well it went away; no big deal
                }
            }
        }
        @Override
        public boolean setLockWallpaperCallback(IWallpaperManagerCallback cb) {
            checkPermission(android.Manifest.permission.INTERNAL_SYSTEM_WINDOW);
            synchronized (mLock) {
                mKeyguardListener = cb;
            }
            return true;
        }
    

    notifyLockWallpaperChanged 中执行 cb.onWallpaperChanged();这里的 cb = mKeyguardListener,而 mKeyguardListener 在 setLockWallpaperCallback() 方法中得到。 跟进我们发现 cb 其实就是 LockscreenWallpaper 引用,在 LockscreenWallpaper 的构造方法里赋值调用:

    // LockscreenWallpaper.java
        @Inject
        public LockscreenWallpaper(WallpaperManager wallpaperManager,
                @Nullable IWallpaperManager iWallpaperManager,
                KeyguardUpdateMonitor keyguardUpdateMonitor,
                DumpManager dumpManager,
                NotificationMediaManager mediaManager,
                @Main Handler mainHandler) {
    
            // 省略部分代码......
    
            if (iWallpaperManager != null) {
                // Service is disabled on some devices like Automotive
                try {
                    // iWallpaperManager 是 WallpaperManagerService 的 binder对象,
                    // 通过 dagger 在 SystemServicesModule 实例化。
                    iWallpaperManager.setLockWallpaperCallback(this);
                } catch (RemoteException e) {
                    Log.e(TAG, "System dead?" + e);
                }
            }
        }
    

    所以当锁屏壁纸更新时,就会回调到 LockscreenWallpaper#onWallpaperChanged()

    // LockscreenWallpaper.java 
        @Override
        public void onWallpaperChanged() {
            // Called on Binder thread.
            postUpdateWallpaper();
        }
    
        private void postUpdateWallpaper() {
            if (mH == null) {
                Log.wtfStack(TAG, "Trying to use LockscreenWallpaper before initialization.");
                return;
            }
            mH.removeCallbacks(this);
            mH.post(this);
        }
    

    而 LockscreenWallpaper 类实现了 Runnable 接口的,所以看下它的 run() 方法;LockscreenWallpaper#run()

    // LockscreenWallpaper.java
        @Override
        public void run() {
            // Called in response to onWallpaperChanged on the main thread.
    
            if (mLoader != null) {
                mLoader.cancel(false /* interrupt */);
            }
    
            final int currentUser = mCurrentUserId;
            final UserHandle selectedUser = mSelectedUser;
            mLoader = new AsyncTask<Void, Void, LoaderResult>() {
                @Override
                protected LoaderResult doInBackground(Void... params) {
                    return loadBitmap(currentUser, selectedUser);
                }
    
                @Override
                protected void onPostExecute(LoaderResult result) {
                    super.onPostExecute(result);
                    if (isCancelled()) {
                        return;
                    }
                    if (result.success) {
                        mCached = true;
                        mCache = result.bitmap;
                        mUpdateMonitor.setHasLockscreenWallpaper(result.bitmap != null);
                        // 通知StatusBar更新壁纸
                        mMediaManager.updateMediaMetaData(
                                true /* metaDataChanged */, true /* allowEnterAnimation */);
                    }
                    mLoader = null;
                }
            }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
        }
    

    异步获取壁纸,并通知StatusBar去更新壁纸。
    NotificationMediaManager#updateMediaMetaData()

    // NotificationMediaManager.java
        public void updateMediaMetaData(boolean metaDataChanged, boolean allowEnterAnimation) {
            Trace.beginSection("StatusBar#updateMediaMetaData");
      
            // 省略部分代码......
    
            Bitmap artworkBitmap = null;
            if (mediaMetadata != null && !mKeyguardBypassController.getBypassEnabled()) {
                artworkBitmap = mediaMetadata.getBitmap(MediaMetadata.METADATA_KEY_ART);
                if (artworkBitmap == null) {
                    artworkBitmap = mediaMetadata.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART);
                }
            }
     
            //在后台线程上处理图稿并将生成的位图发送到finishUpdateMediaMetaData。
            if (metaDataChanged) {
                for (AsyncTask<?, ?, ?> task : mProcessArtworkTasks) {
                    task.cancel(true);
                }
                mProcessArtworkTasks.clear();
            }
            if (artworkBitmap != null && !Utils.useQsMediaPlayer(mContext)) {
                mProcessArtworkTasks.add(new ProcessArtworkTask(this, metaDataChanged,
                        allowEnterAnimation).execute(artworkBitmap));
            } else {
                finishUpdateMediaMetaData(metaDataChanged, allowEnterAnimation, null);
            }
    
            Trace.endSection();
        }
    

    对锁屏壁纸所在 view 做 setImageBitmap。

    // NotificationMediaManager.java
        private void finishUpdateMediaMetaData(boolean metaDataChanged, boolean allowEnterAnimation,
                @Nullable Bitmap bmp) {
            Drawable artworkDrawable = null;
            if (bmp != null) {
                artworkDrawable = new BitmapDrawable(mBackdropBack.getResources(), bmp);
            }
    
            // 省略部分代码......
    
            if ((hasArtwork || DEBUG_MEDIA_FAKE_ARTWORK)
                    && (mStatusBarStateController.getState() != StatusBarState.SHADE || allowWhenShade)
                    &&  mBiometricUnlockController != null && mBiometricUnlockController.getMode()
                            != BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING
                    && !hideBecauseOccluded) {
    
                 // 省略部分代码......
    
                if (metaDataChanged) {
                    if (mBackdropBack.getDrawable() != null) {
                        Drawable drawable =
                                mBackdropBack.getDrawable().getConstantState()
                                        .newDrawable(mBackdropFront.getResources()).mutate();
    
                        // 设置壁纸 setImageDrawable()
                        mBackdropFront.setImageDrawable(drawable);
                        mBackdropFront.setAlpha(1f);
                        mBackdropFront.setVisibility(View.VISIBLE);
                    } else {
                        mBackdropFront.setVisibility(View.INVISIBLE);
                    }
     
                    if (DEBUG_MEDIA_FAKE_ARTWORK) {
                        final int c = 0xFF000000 | (int)(Math.random() * 0xFFFFFF);
                        Log.v(TAG, String.format("DEBUG_MEDIA: setting new color: 0x%08x", c));
                        mBackdropBack.setBackgroundColor(0xFFFFFFFF);
                        mBackdropBack.setImageDrawable(new ColorDrawable(c));
                    } else {
                        mBackdropBack.setImageDrawable(artworkDrawable);
                    }
     
                    if (mBackdropFront.getVisibility() == View.VISIBLE) {
                        if (DEBUG_MEDIA) {
                            Log.v(TAG, "DEBUG_MEDIA: Crossfading album artwork from "
                                    + mBackdropFront.getDrawable()
                                    + " to "
                                    + mBackdropBack.getDrawable());
                        }
                        mBackdropFront.animate()
                                .setDuration(250)
                                .alpha(0f).withEndAction(mHideBackdropFront);
                    }
                }
            } else {
                // 省略部分代码......
            }
        }
    

    通过 mBackdropFront.setImageDrawable(drawable) 方法将图片设置进去,完成锁屏壁纸的更新

    mBackdropFront 在 NotificationMediaManager的setup() 方法被赋值,而 setup() 方法在 StatusBar 的 makeStatusBarView() 中被调用初始化。
    StatusBar#makeStatusBarView()

    // StatusBar.java
        protected void makeStatusBarView(@Nullable RegisterStatusBarResult result) {
    
            // 省略部分代码......
    
            mMediaManager.setup(backdrop, backdrop.findViewById(R.id.backdrop_front),
                    backdrop.findViewById(R.id.backdrop_back), mScrimController, mLockscreenWallpaper);
    
            // 省略部分代码......
        }
    

    相关文章

      网友评论

        本文标题:Android 10.0 锁屏壁纸 LockscreenWall

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