@guhuizaifeiyang
2017-08-31T08:44:11.000000Z
字数 5850
阅读 1860
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"/>
时序图镇楼:
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的实例对象。
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");// 获取应用的uidint 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。
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/0getWallpaperDir(UserHandle.USER_OWNER).mkdirs();//载入系统保存的配置,UserHandle.USER_OWNER为0loadSettingsLocked(UserHandle.USER_OWNER);}/*** 返回 /data/system/users/{userId}* @param userId* @return /data/system/users/{userId}*/private static File getWallpaperDir(int userId) {return Environment.getUserSystemDirectory(userId);}
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 systemmigrateFromOld();}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文件// 省略}
载入系统保存的配置,这里也是分三步:
static final String WALLPAPER = "wallpaper";WallpaperData(int userId) {//0this.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文件
再回到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);//返回这个文件的fdParcelFileDescriptor 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。
接着根据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