@guhuizaifeiyang
2017-08-31T16:44:11.000000Z
字数 5850
阅读 1668
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");
// 获取应用的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。
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);
}
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文件
// 省略
}
载入系统保存的配置,这里也是分三步:
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文件
再回到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。
接着根据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