Android 10.0 锁屏壁纸 LockscreenWallpaper(三)

简介: Android 10.0 锁屏壁纸 LockscreenWallpaper(三)

学习笔记:

一、设置壁纸

通过系统设置进行锁屏壁纸和桌面壁纸的设置。

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 类来进行的。


二、锁屏壁纸的显示


锁屏壁纸显示流程图:


image.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开发
Android10.0 锁屏分析——KeyguardPINView PIN锁分析
Android10.0 锁屏分析——KeyguardPINView PIN锁分析
|
6月前
|
Java Android开发
Android 切换壁纸代码流程追踪
Android 切换壁纸代码流程追踪
51 0
|
存储 数据库 数据安全/隐私保护
Android11.0(R) 预留清空锁屏密码接口
Android11.0(R) 预留清空锁屏密码接口
304 0
|
Android开发
Android11.0(R) 手机锁屏炫酷充电动画————自定义View方案
Android11.0(R) 手机锁屏炫酷充电动画————自定义View方案
352 0
|
Java Android开发 开发者
Android9.0 无锁屏下连续锁屏-解屏2次,画面点击无响应 bug 解决办法
Android9.0 无锁屏下连续锁屏-解屏2次,画面点击无响应 bug 解决办法
133 0
|
开发工具 Android开发 开发者
Android 10.0 动态壁纸 LiveWallpaper (一)
Android 10.0 动态壁纸 LiveWallpaper (一)
1563 0
Android 10.0 动态壁纸 LiveWallpaper (一)
|
安全 算法 Java
Android10.0 锁屏分析——KeyguardPatternView图案锁分析
Android10.0 锁屏分析——KeyguardPatternView图案锁分析
Android10.0 锁屏分析——KeyguardPatternView图案锁分析
|
Java Android开发
Android 8.1 修改锁屏显示时间
Android 8.1 修改锁屏显示时间
233 0
|
Java Android开发
Android 8.1 系统锁屏显示流程整理
Android 8.1 系统锁屏显示流程整理
385 0
|
Android开发
android 4.2 修改默锁屏为无
android 4.2 修改默锁屏为无
150 0