设置壁纸流程分析

设置壁纸流程分析

标签(空格分隔): Android开发


设置静态壁纸

设置静态壁纸有很多途径,但归根结底都是一下三种方法:

使用WallpaperManager的setResource(int ResourceID)方法
使用WallpaperManager的setBitmap(Bitmap bitmap)方法
使用WallpaperManager的setStream(InputStream data)方法

举个栗子,就选第一个:

WallpaperManager wallpaperManager = WallpaperManager.getInstance(this);
try {
    wallpaperManager.setResource(R.drawable.picture);
} catch (IOException e) {
    e.printStackTrace();
}

其他两个和第一个一样,只不过将要设为壁纸的图片参数换成了Bitmap和InputStream。然后就是不要忘了加上以下权限:

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

时序图镇楼:
此处输入图片的描述

Step 1. setResource()

WallpaperManager.java->setResource()

public void setResource(int resid) throws IOException {
    if (sGlobals.mService == null) {
        Log.w(TAG, "WallpaperService not running");
        return;
    }
    try {
        Resources resources = mContext.getResources();
        /* Set the wallpaper to the default values */
        // (1) 
        ParcelFileDescriptor fd = sGlobals.mService.setWallpaper(
                "res:" + resources.getResourceName(resid));
        if (fd != null) {
            FileOutputStream fos = null;
            try {
                fos = new ParcelFileDescriptor.AutoCloseOutputStream(fd);
                setWallpaper(resources.openRawResource(resid), fos);        
            } finally {
                if (fos != null) {
                    fos.close();
                }
            }
        }
    } catch (RemoteException e) {
    }
}

(1)
代码里牵涉到sGlobals.mService,这个mService是WallpaperManagerService的实例对象,它是在Globals的构造函数中初始化的。

static class Globals extends IWallpaperManagerCallback.Stub {
    private IWallpaperManager mService;

    Globals(Looper looper) {
        IBinder b = ServiceManager.getService(Context.WALLPAPER_SERVICE);
        mService = IWallpaperManager.Stub.asInterface(b);
    }
}        

可以去查看WallpaperManagerService的代码,发现WallpaperManagerService正是实现了IWallpaperManager.Stub,而ServiceManager中正是以关键字Context.WALLPAPER_SERVICE保存的WallpaperManagerService实例,由以上两点可以得知mService正是WallpaperManagerService的实例对象。

Step 2. setWallpaper()

WallpaperManagerService.java->setWallpaper()

public ParcelFileDescriptor setWallpaper(String name, String callingPackage) {
    // 检查权限
    checkPermission(android.Manifest.permission.SET_WALLPAPER);
    if (!isWallpaperSupported(callingPackage)) {
        return null;
    }
    synchronized (mLock) {
        if (DEBUG) Slog.v(TAG, "setWallpaper");
        // 获取应用的uid
        int userId = UserHandle.getCallingUserId();
        WallpaperData wallpaper = getWallpaperSafeLocked(userId);
        final long ident = Binder.clearCallingIdentity();
        try {
            ParcelFileDescriptor pfd = updateWallpaperBitmapLocked(name, wallpaper);
            if (pfd != null) {
                wallpaper.imageWallpaperPending = true;
            }
            return pfd;
        } finally {
            Binder.restoreCallingIdentity(ident);
        }
    }
}

WallpaperManagerService的setWallpaper其实主要作用是取得一个ParcelFileDescriptor对象,这个对象指向了/data/system/user/0/wallpaper这个文件.

这里Binder.getCallingUid()获得应用的uid,我们知道系统的uid是1000,之后应用都是从1000往后的。一些重要的system app,比如com.android.phone电话是1001,com.android.bluetooth蓝牙是1002等等;第三方应用则是从10000开始的,多装一个应用分配的uid就会+1.但是如果应用在清单文件中配置了android:sharedUserId=”android.uid.system”属性,那么这个应用的uid也是1000,不过一般需要打系统签名。
如果要查看应用的uid,可以查看android机器的/data/system/packages.list文件,上面都罗列了每个应用的包名和对应uid。

Step 3. getWallpaperSafeLocked()

WallpaperManagerService.java->getWallpaperSafeLocked()

private WallpaperData getWallpaperSafeLocked(int userId) {
    WallpaperData wallpaper = mWallpaperMap.get(userId);
    if (wallpaper == null) {
        loadSettingsLocked(userId);
        wallpaper = mWallpaperMap.get(userId);
    }
    return wallpaper;
}

首先根据userID从mWallpaperMap对象中获取WallpaperData实例,如果为空则调用loadSettingsLocked方法,再重新获取。那么mWallpaperMap什么时候保存的WallpaperData呢?

从WallpaperManagerService的构造方法中发现:

public WallpaperManagerService(Context context) {
    // ......
    //生成壁纸相关目录/data/system/users/0
    getWallpaperDir(UserHandle.USER_OWNER).mkdirs();
    //载入系统保存的配置,UserHandle.USER_OWNER为0
    loadSettingsLocked(UserHandle.USER_OWNER);
}
/**
 * 返回 /data/system/users/{userId}
 * @param userId
 * @return /data/system/users/{userId}
 */
private static File getWallpaperDir(int userId) {
    return Environment.getUserSystemDirectory(userId);
}

Step 4. loadSettingsLocked

private void loadSettingsLocked(int userId) {

    JournaledFile journal = makeJournaledFile(userId);
    FileInputStream stream = null;
    File file = journal.chooseForRead();
    if (!file.exists()) {
        // This should only happen one time, when upgrading from a legacy system
        migrateFromOld();
    }
    WallpaperData wallpaper = mWallpaperMap.get(userId);
    if (wallpaper == null) {
        wallpaper = new WallpaperData(userId);
        mWallpaperMap.put(userId, wallpaper);
    }
    boolean success = false;
    // 解析/data/system/users/0/wallpaper_info.xml文件
    // 省略

}

载入系统保存的配置,这里也是分三步:

  1. 封装一下/data/system/users/0/wallpaper_info.xml文件,用JournaledFile这个工具类,这个工具类会包装两个文件,一个是wallpaper_info.xml正式文件,另一个是wallpaper_info.xml.tmp临时文件。如果正式文件存在就选出正式文件,并删除临时文件;如果正式文件不存在就将临时文件重名为正式文件。
  2. 创建一个WallpaperData并存入mWallpaperMap ,我们可以看看WallpaperData 的构造,这是一个内部类:
static final String WALLPAPER = "wallpaper";
WallpaperData(int userId) {//0
  this.userId = userId;
  //位于/data/system/users/0/wallpaper,是存放壁纸的文件
  wallpaperFile = new File(getWallpaperDir(userId), WALLPAPER);
}

WallpaperData 的构造方法主要是创建了一个/data/system/users/0/wallpaper的File对象。
3. 最后就是解析/data/system/users/0/wallpaper_info.xml文件

Step 5. updateWallpaperBitmapLocked()

再回到WallpaperManagerService.java->setWallpaper中,查看updateWallpaperBitmapLocked方法。

ParcelFileDescriptor updateWallpaperBitmapLocked(String name, WallpaperData wallpaper) {
    if (name == null) name = "";
    try {
        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);
        }
        //  /data/system/users/0/wallpaper文件
        File file = new File(dir, WALLPAPER);
        //返回这个文件的fd
        ParcelFileDescriptor fd = ParcelFileDescriptor.open(file,
                MODE_CREATE|MODE_READ_WRITE|MODE_TRUNCATE);
        if (!SELinux.restorecon(file)) {
            return null;
        }
        wallpaper.name = name;
        return fd;
    } catch (FileNotFoundException e) {
        Slog.w(TAG, "Error setting wallpaper", e);
    }
    return null;
}

可以看到,这个方法最终返回了/data/system/users/0/wallpaper文件的fd。

Step 6. WallpaperManager->setWallpaper

接着根据ParcelFileDescriptor生成文件输出流fos,再调用resources.openRawResource(resid)获得源壁纸的文件输入流,传入WallpaperManager的下一个方法,setWallpaper(InputStream,FileOutputStream)。
而在这个方法中主要代码为

while ((amt=data.read(buffer)) > 0) {
    fos.write(buffer, 0, amt);
    if (mSaveBakFlag && null != fos_bak) {
        fos_bak.write(buffer, 0, amt);
    }
}

WallpaperManager.setResource方法到最后就是进行文件复制而已,把源壁纸图片复制到之前曾经提到过的
/data/system/user/0/wallpaper这个文件中。

设置壁纸的流程走到这里就结束了。。。


那壁纸是如何被加载和绘制的呢?

此过程可以参考:《开机默认壁纸加载流程分析》
https://www.zybuluo.com/guhuizaifeiyang/note/866798

动态壁纸

// TODO

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值