Android壁纸开发流程分析

       起因是产品汪的一个奇葩需求,要求播放视频使用背景播放,前台还可以随意操作其他东西,就像PS4那样的。所以不得不研究一下动态壁纸了,中途也是遇到了无数坑,但终于还是做出来了。这里就简单介绍一下Android对壁纸的管理流程。

壁纸设置

       Android为我们提供了壁纸服务,让我们的桌面看起来更加绚丽。打开设置我们都会发现,壁纸有动态和静态之分。静态壁纸就是一张图片,可以选择系统预留的也可以设置自己的照片;动态壁纸就比较复杂了,内容在不停地变换,系统也给我们提供了许多种选择。接下来我们先说说这两种壁纸如何设置。

静态壁纸

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

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

       举个栗子,就选第一个:

1
2
3
4
5
6
WallpaperManager wallpaperManager = WallpaperManager.getInstance(this);
try {
wallpaperManager.setResource(R.drawable.picture);
} catch (IOException e) {
e.printStackTrace();
}

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

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

动态壁纸

       首先动态壁纸的动态体现出这个组件是实时变化的,也就是说有一个后台在不停的刷新这个组件。联想到后台组件首先想到的就是service,从代码角度看,果然如此。每一个动态壁纸都继承自WallpaperService,其中必须实现的抽象方法onCreateEngine,返回一个Engine对象,实际上所有的绘图与刷新都是由engine完成,service正是提供engine的部分。
       打个比方说,在我们设置一个动态壁纸时有预览,这时启动了一个预览的engine来绘制与刷新,当我们设置了壁纸以后又启动了一个填充整个桌面的engine在实时的绘制与刷新。所以动态壁纸的重点在engine里,有几个重要的方法:

public void onCreate(SurfaceHolder surfaceHolder)
有了surfaceholder我们可以获得canvas对象,有了canvas我们就可以绘图

public void onOffsetsChanged(float xOffset, float yOffset, float xOffsetStep, float yOffsetStep, int xPixelsOffset, int yPixelsOffset)
屏幕滑动的时候触发此方法
xOffset可以用来判断屏幕序号 百分比的形式,举例说如果你手机上有5个分屏,第一屏这里是0.000,第二屏是0.2000 第三屏是0.4000以此类推
xOffsetStep从字面意义就能理解是步进 同样和你的分屏数有关 如果你的分屏数为5 则每次步进xOffsetStep都是0.20000
xPixelsOffset就是实际上像素的移动距离,也就是说移动了多少像素,奇怪的是这里左右移动像素点的offset都为负数

public void onVisibilityChanged(boolean visible)
当动态壁纸的可见性发生变化时触发此方法,举例说在桌面上时,动态壁纸的visibility为true,当你运行某个程序的时候动态壁纸的visibility变为false,这里很好理解

public Bundle onCommand(String action, int x, int y, int z, Bundle extras, boolean resultRequested)
可以监听点击事件,点击时触发此方法,action为 android.wallpaper.tap,x记录了横坐标,y记录了纵坐标,z的作用未知,可能是为3d桌面预留下的? 没有试验过,z一般为0

还有一些重要的生命周期方法,和activity类似,就不多说了。

       因为动态壁纸是一个Service,还必须在清单文件中进行一些配置,比如:

1
2
3
4
5
6
7
8
9
10
11
12
<!-- 配置动态壁纸Service -->
<service android:label="@string/app_name"
android:name=".LiveWallpaper"
android:permission="android.permission.BIND_WALLPAPER">

<!-- 为动态壁纸配置intent-filter -->
<intent-filter>
<action android:name="android.service.wallpaper.WallpaperService" />
</intent-filter>
<!-- 为动态壁纸配置meta-data -->
<meta-data android:name="android.service.wallpaper"
android:resource="@xml/livewallpaper" />

</service>

       比较重要的部分首先是权限android:permission=”android.permission.BIND_WALLPAPER”;
       其次service需要响应action:android:name=”android.service.wallpaper.WallpaperService;
       再就是配置文件:

1
2
<meta-data android:name="android.service.wallpaper"
android:resource="@xml/livewallpaper" />

接下来接收配置文件。首先在res文件夹下建立一个xml目录,和写appwidget一样。在目录下我们创建一个xml文件:

1
2
3
4
5
6

<wallpaper xmlns:android="http://schemas.android.com/apk/res/android"
android:settingsActivity="LiveWallPreference"
android:thumbnail="@drawable/ic_launcher"
android:description="@string/wallpaper_description"
/>

其中含义如下:

android:settingsActivity=”LiveWallPreference”
指定配置动态壁纸的PreferenceActivity,这个PreferenceActivity同样需要在AndroidManifest.xml中注册,不过和一般的activity一样。当我们点击动态壁纸的设置按钮时,导向这个activity。不可缺少,否则点击设置会报错。

android:thumbnail=”@drawable/ic_launcher”
android:description=”@string/wallpaper_description”
第一个图标对应动态壁纸列表中的图标,第二条description则是图标右边你创建的动态壁纸的名字。

       至此动态壁纸的框架就算完成了。接下来只需要在wallpaperservice类中加入刷新机制,加入动态内容,一个动态桌面就完成了。

       这里我贴一个魔方的动态壁纸:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
/* A wallpaper service is responsible for showing a live wallpaper behind
* applications that would like to sit on top of it. This service object itself
* does very little -- its only purpose is to generate instances of
* WallpaperService.Engine as needed. Implementing a wallpaper thus involves
* subclassing from this, subclassing an Engine implementation, and implementing
* onCreateEngine() to return a new instance of your engine
*/

public class CubeWallpaper1 extends WallpaperService {

/*常用的都是这样的,用一个handler来动态的去刷新UI,对吧?猜的,看下面代码到底是不是*/
private final Handler mHandler = new Handler();

/**
* 这个方法与Activity里面的一样,当这个类的服务被第一次创建时
* 调用,也就是说,这个方法只调用一次..
*/

@Override
public void onCreate() {
super.onCreate();
}

/**
* 与上面反的,销毁时调用,这个猜下,
* 不懂了查文档
*/

@Override
public void onDestroy() {
super.onDestroy();
}

/**
* 这个方法在类注释中写明了
* implementing onCreateEngine() to return a new instance of your engine
* 必须实现这个方法来返回我们自己定义引擎的一个实例
*/

@Override
public Engine onCreateEngine() {
return new CubeEngine();
}


/**
*
* @Title: CubeWallpaper1.java
* @Package cube1
* @Description: 自定义引擎类
*/

class CubeEngine extends Engine {

private final Paint mPaint = new Paint();
private float mOffset;
/*用户触摸位置*/
private float mTouchX = -1;
private float mTouchY = -1;
private long mStartTime;

/*屏幕中心坐标,记下,是中心不是原心(0,0)*/
private float mCenterX;
private float mCenterY;

private final Runnable mDrawCube = new Runnable() {
public void run() {
drawFrame();
}
};
private boolean mVisible;

CubeEngine() {

/*下面这几行就为了在屏幕中画立方体的线条而做准备*/
final Paint paint = mPaint;
paint.setColor(0xffffffff);//画笔颜色
paint.setAntiAlias(true);//抗锯齿
paint.setStrokeWidth(2);//线条粗细,猜的,不知道对不对
paint.setStrokeCap(Paint.Cap.ROUND);
paint.setStyle(Paint.Style.STROKE);
//系统启动完之后,开始绘制壁纸的时间,这个时间里面包含有系统睡眠时间
mStartTime = SystemClock.elapsedRealtime();
}

/**
* 大家发现这个onCreate与Activity的方法有什么不同了吧?
* 老规矩的,还是在初始化壁纸引擎的时候调用这个方法,并设置触
* 屏事件为可用
*/

@Override
public void onCreate(SurfaceHolder surfaceHolder) {
super.onCreate(surfaceHolder);
setTouchEventsEnabled(true);
}

@Override
public void onDestroy() {
super.onDestroy();
mHandler.removeCallbacks(mDrawCube);
}

/**
* 系统壁纸状态改变时会调用这个方法,如:
* 壁纸由隐藏转换为显示状态时会调用这个方法
*/

@Override
public void onVisibilityChanged(boolean visible) {
mVisible = visible;
/*下面这个判断好玩,就是说,如果屏幕壁纸状态转为显式时重新绘制壁纸,否则黑屏幕,隐藏就可以*/
if (visible) {
drawFrame();
} else {
mHandler.removeCallbacks(mDrawCube);
}
}

@Override
public void onSurfaceChanged(SurfaceHolder holder, int format,
int width, int height)
{

super.onSurfaceChanged(holder, format, width, height);
//下面是来保存屏幕显示立方体的,也就是你能看到的正面图的中心位置
mCenterX = width / 2.0f;
mCenterY = height / 2.0f;
drawFrame();
}

/**
* 下面两个方法是为了方便调用SurfaceHolder交互来重写的
*/

@Override
public void onSurfaceCreated(SurfaceHolder holder) {
super.onSurfaceCreated(holder);
}

@Override
public void onSurfaceDestroyed(SurfaceHolder holder) {
super.onSurfaceDestroyed(holder);
mVisible = false;
mHandler.removeCallbacks(mDrawCube);
}

/**
* 当手动壁纸时根据偏移量重绘壁纸
*/

@Override
public void onOffsetsChanged(float xOffset, float yOffset, float xStep,
float yStep, int xPixels, int yPixels)
{

mOffset = xOffset;
drawFrame();
}

/*
* 在这个地方保存触摸的位置,我们会在绘制壁纸的时候使用触摸值
*/

@Override
public void onTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_MOVE) {
mTouchX = event.getX();
mTouchY = event.getY();
} else {
mTouchX = -1;
mTouchY = -1;
}
super.onTouchEvent(event);
}

/*
* 绘制立方体方法实现
*/

void drawFrame() {
final SurfaceHolder holder = getSurfaceHolder();

Canvas c = null;
try {
c = holder.lockCanvas();
if (c != null) {
drawCube(c);
drawTouchPoint(c);
}
} finally {
if (c != null)
holder.unlockCanvasAndPost(c);
}

// 在指定时间里重绘制,这个地方大家可以看效果图,如果你拖动过快的话,立方体
//每个顶点之间会有一个短暂的未连接延迟,就是在这个地方使用了延迟来绘制的
mHandler.removeCallbacks(mDrawCube);
if (mVisible) {
mHandler.postDelayed(mDrawCube, 1000 / 25);
}
}

/*
* 这个地方是以立方体某个顶点为起始端,绘制三条线
* 一堆数字,看着好晕
* 在这小马顺便贴在这个DEMO里面用到的基本的绘制,如下:
* graphics.Canvas有四种画矩形的方法。
canvas.drawRect(new RectF(10, 10, 300, 100), paint);
canvas.drawRect(10, 150, 300, 200, paint);
canvas.drawRect(new Rect(10, 250, 300, 300), paint);
第四种:画圆角的矩形
canvas.drawRoundRect(new RectF(10, 350, 300, 450), 10, 10, paint);
第二个和第三个参数为圆角的宽高。
有兴趣的朋友可以改下下面这些东西
*/

void drawCube(Canvas c) {
c.save();
c.translate(mCenterX, mCenterY);
c.drawColor(0xff000000);
drawLine(c, -400, -400, -400, 400, -400, -400);
drawLine(c, 400, -400, -400, 400, 400, -400);
drawLine(c, 400, 400, -400, -400, 400, -400);
drawLine(c, -400, 400, -400, -400, -400, -400);

drawLine(c, -400, -400, 400, 400, -400, 400);
drawLine(c, 400, -400, 400, 400, 400, 400);
drawLine(c, 400, 400, 400, -400, 400, 400);
drawLine(c, -400, 400, 400, -400, -400, 400);

drawLine(c, -400, -400, 400, -400, -400, -400);
drawLine(c, 400, -400, 400, 400, -400, -400);
drawLine(c, 400, 400, 400, 400, 400, -400);
drawLine(c, -400, 400, 400, -400, 400, -400);
c.restore();
}

/*
* 在屏幕中绘制三维空间的线
*/

void drawLine(Canvas c, int x1, int y1, int z1, int x2, int y2, int z2) {
/*
*因为大家都知道,壁纸是手机启动完成之后就已经开始绘制的,一般取时间什么的
*我们都用Timer System.currentTimeMillis() Calendar来取
*这个地方取系统级启动时间等的,记住这个类,SystemClock,方法自己查
*/

long now = SystemClock.elapsedRealtime();
/*取得三维坐标轴的旋转值*/
float xrot = ((float) (now - mStartTime)) / 1000;
float yrot = (0.5f - mOffset) * 2.0f;
float zrot = 0;


// rotation around X-axis ???
float newy1 = (float) (Math.sin(xrot) * z1 + Math.cos(xrot) * y1);
float newy2 = (float) (Math.sin(xrot) * z2 + Math.cos(xrot) * y2);
float newz1 = (float) (Math.cos(xrot) * z1 - Math.sin(xrot) * y1);
float newz2 = (float) (Math.cos(xrot) * z2 - Math.sin(xrot) * y2);

// rotation around Y-axis ???
float newx1 = (float) (Math.sin(yrot) * newz1 + Math.cos(yrot) * x1);
float newx2 = (float) (Math.sin(yrot) * newz2 + Math.cos(yrot) * x2);
newz1 = (float) (Math.cos(yrot) * newz1 - Math.sin(yrot) * x1);
newz2 = (float) (Math.cos(yrot) * newz2 - Math.sin(yrot) * x2);

// 3D-to-2D projection ???
float startX = newx1 / (4 - newz1 / 400);
float startY = newy1 / (4 - newz1 / 400);
float stopX = newx2 / (4 - newz2 / 400);
float stopY = newy2 / (4 - newz2 / 400);

c.drawLine(startX, startY, stopX, stopY, mPaint);
}

/*
* 按位屏幕手动时绘制一个白色的圈
*/

void drawTouchPoint(Canvas c) {
if (mTouchX >= 0 && mTouchY >= 0) {
c.drawCircle(mTouchX, mTouchY, 80, mPaint);
}
}

}
}

流程分析

       上面都是一些设置壁纸的方法,但这实现不了产品汪的需求啊。产品汪想要Launcher一直用背景播放视频,设计喵出了许多应用的效果图,要求这些应用都可以和背景进行交互……唉,坑爹的需求,继续看源码吧。

静态壁纸流程

       三种设置静态壁纸的方法,其实原理都大同小异。我们只看看WallpaperManager的setBitmap(Bitmap bitmap)方法,其他两种有兴趣的同学也可以自己看看位于framework/base/core/java/android/app/WallpaperManager.java:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
    public void setBitmap(Bitmap bitmap) throws IOException {
if (sGlobals.mService == null) {
Log.w(TAG, "WallpaperService not running");
return;
}
try {
//sGlobals.mService声明在WallpaperManager的内部类Globals里,对应系统服务WallpaperManagerService;
ParcelFileDescriptor fd = sGlobals.mService.setWallpaper(null);
if (fd == null) {
return;
}
FileOutputStream fos = null;
try {
//获得WallpaperManagerService返回的一个壁纸相关的文件描述符,然后将新壁纸内容写入
fos = new ParcelFileDescriptor.AutoCloseOutputStream(fd);
bitmap.compress(Bitmap.CompressFormat.PNG, 90, fos);
} finally {
if (fos != null) {
fos.close();
}
}
} catch (RemoteException e) {
// Ignore
}
}
static class Globals extends IWallpaperManagerCallback.Stub {
private IWallpaperManager mService;
......

Globals(Looper looper) {
//对应系统服务WallpaperManagerService
IBinder b = ServiceManager.getService(Context.WALLPAPER_SERVICE);
mService = IWallpaperManager.Stub.asInterface(b);
}
}

       setBitmap方法内部有一段ParcelFileDescriptor fd = sGlobals.mService.setWallpaper(null),这里sGlobals.mService声明在WallpaperManager的内部类Globals里,这个mService对应系统服务的WallpaperManagerService。它的setWallpaper方法返回的是一个文件描述符,我们猜测这个就是壁纸内容存放的fd,往下的代码就是将我们指定的bitmap写入这个fd。所以我们看看WallpaperManagerService的setWallpaper方法,位于frameworks/base/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public ParcelFileDescriptor setWallpaper(String name) {
checkPermission(android.Manifest.permission.SET_WALLPAPER);
synchronized (mLock) {
if (DEBUG) Slog.v(TAG, "setWallpaper");
//先获取用户的userId
int userId = UserHandle.getCallingUserId();
//根据userId这个键去取WallpaperData这个壁纸包装类的值
WallpaperData wallpaper = mWallpaperMap.get(userId);
if (wallpaper == null) {
throw new IllegalStateException("Wallpaper not yet initialized for user " + userId);
}
final long ident = Binder.clearCallingIdentity();
try {
//获得壁纸文件的fd
ParcelFileDescriptor pfd = updateWallpaperBitmapLocked(name, wallpaper);
if (pfd != null) {
wallpaper.imageWallpaperPending = true;
}
return pfd;
} finally {
Binder.restoreCallingIdentity(ident);
}
}
}

       这里分三步:
       1)先获取用户的userId,看看UserHandle.的getCallingUserId()方法,位于framework/base/core/java/android/os/UserHandle.java中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
 /**
* @hide Range of uids allocated for a user.
*/

public static final int PER_USER_RANGE = 100000;
/**
* @hide Enable multi-user related side effects. Set this to false if
* there are problems with single user use-cases.
*/

public static final boolean MU_ENABLED = true;
/** @hide */
public static final int getCallingUserId() {
return getUserId(Binder.getCallingUid());
}
/**
* Returns the user id for a given uid.
* @hide
*/

public static final int getUserId(int uid) {
if (MU_ENABLED) {
return uid / PER_USER_RANGE;
} else {
return 0;
}
}

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

       回到上面代码,返回值是用应用的uid除以100000,所以这里返回的是0。

       2)根据userId这个键去取WallpaperData这个壁纸包装类的值。
       这里userId是0,因为系统崩溃,所以下面那个异常不会抛出,这里mWallpaperMap.get(userId)获得的WallpaperData 对象wallpaper 不为空。
       所以我们就得找这个WallpaperData 被存入mWallpaperMap的时机。在浏览WallpaperManagerService的构造方法是发现了存入时机,调用WallpaperManagerService构造方法是在SystemServer启动时调用的。因为android系统开机会启动framework,framework会启动它的SystemServer,代码实现如下,位于framework/base/services/java/com/android/server/SystemServer.java:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public final class SystemServer {
/**
* The main entry point from zygote.
*/

public static void main(String[] args) {
new SystemServer().run();
}
private void run() {
......
startOtherServices();
......
}
/**
* Starts a miscellaneous grab bag of stuff that has yet to be refactored
* and organized.
*/

private void startOtherServices() {
......
WallpaperManagerService wallpaper = null;
......
if (!disableNonCoreServices && context.getResources().getBoolean(
R.bool.config_enableWallpaperService)) {
try {
Slog.i(TAG, "Wallpaper Service");
//这里启动了WallpaperManagerService系统服务,加入ServiceManager中管理
wallpaper = new WallpaperManagerService(context);
ServiceManager.addService(Context.WALLPAPER_SERVICE, wallpaper);
} catch (Throwable e) {
reportWtf("starting Wallpaper Service", e);
}
}
......
}
}

       这里启动了WallpaperManagerService后,然后我们继续分析他的构造方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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);
}

       这里先生成壁纸相关目录,/data/system/users/0这个目录。然后就是载入系统保存的配置,我们接着看loadSettingsLocked(UserHandle.USER_OWNER)方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
   private void loadSettingsLocked(int userId) {//0

......
//封装/data/system/users/0/wallpaper_info.xml文件的方法
JournaledFile journal = makeJournaledFile(userId);
FileInputStream stream = null;
File file = journal.chooseForRead();
......
//此时mWallpaperMap.get(0)为null
WallpaperData wallpaper = mWallpaperMap.get(userId);
if (wallpaper == null) {//会进入这里
wallpaper = new WallpaperData(userId);
mWallpaperMap.put(userId, wallpaper);
}

//--------------解析/data/system/users/0/wallpaper_info.xml START----------------
stream = new FileInputStream(file);
XmlPullParser parser = Xml.newPullParser();
parser.setInput(stream, null);
int type;
do {
type = parser.next();
if (type == XmlPullParser.START_TAG) {
String tag = parser.getName();
if ("wp".equals(tag)) {
wallpaper.width = Integer.parseInt(parser.getAttributeValue(null, "width"));
wallpaper.height = Integer.parseInt(parser
.getAttributeValue(null, "height"));
wallpaper.padding.left = getAttributeInt(parser, "paddingLeft", 0);
wallpaper.padding.top = getAttributeInt(parser, "paddingTop", 0);
wallpaper.padding.right = getAttributeInt(parser, "paddingRight", 0);
wallpaper.padding.bottom = getAttributeInt(parser, "paddingBottom", 0);
wallpaper.name = parser.getAttributeValue(null, "name");
String comp = parser.getAttributeValue(null, "component");
wallpaper.nextWallpaperComponent = comp != null
? ComponentName.unflattenFromString(comp)
: null;
if (wallpaper.nextWallpaperComponent == null
|| "android".equals(wallpaper.nextWallpaperComponent
.getPackageName())) {
wallpaper.nextWallpaperComponent = mImageWallpaper;
}
}
}
} while (type != XmlPullParser.END_DOCUMENT);
//--------------解析/data/system/users/0/wallpaper_info.xml END----------------

......
}

static final String WALLPAPER_INFO = "wallpaper_info.xml";

//封装/data/system/users/0/wallpaper_info.xml文件的方法
private static JournaledFile makeJournaledFile(int userId) {
final String base = new File(getWallpaperDir(userId), WALLPAPER_INFO).getAbsolutePath();
return new JournaledFile(new File(base), new File(base + ".tmp"));
}

       载入系统保存的配置,这里也是分三步:
       1. 封装一下/data/system/users/0/wallpaper_info.xml文件,用JournaledFile这个工具类,这个工具类会包装两个文件,一个是wallpaper_info.xml正式文件,另一个是wallpaper_info.xml.tmp临时文件。如果正式文件存在就选出正式文件,并删除临时文件;如果正式文件不存在就将临时文件重名为正式文件。
       2. 创建一个WallpaperData并存入WallpaperData ,我们可以看看WallpaperData 的构造,这是一个内部类:

1
2
3
4
5
6
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文件,用pull解析器简单解析,这个xml也很简单,这里我举个测试栗子:

1
2
<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<wp width="1920" height="1920" name="res:com.example.aaaandroid:drawable/ic_launcher" />

       这里获取WallpaperData的流程就分析完了,然后继续往下走。

       3)获得壁纸文件的fd,查看updateWallpaperBitmapLocked(name, wallpaper)方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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。

       到这里三步走就完了,千万不能晕了。赶紧回到刚才WallpaperManager的setBitmap方法中。接着就是将我们指定的Bitmap写入到这个fd当中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public void setBitmap(Bitmap bitmap) throws IOException {
......
try {
//sGlobals.mService声明在WallpaperManager的内部类Globals里,对应系统服务WallpaperManagerService;
ParcelFileDescriptor fd = sGlobals.mService.setWallpaper(null);
if (fd == null) {
return;
}
FileOutputStream fos = null;
try {
//获得WallpaperManagerService返回的一个壁纸相关的文件描述符,然后将新壁纸内容写入
fos = new ParcelFileDescriptor.AutoCloseOutputStream(fd);
bitmap.compress(Bitmap.CompressFormat.PNG, 90, fos);
} finally {
if (fos != null) {
fos.close();
}
}
} catch (RemoteException e) {
// Ignore
}
}

       然后我们的设置静态壁纸就完了。
       我们的设置静态壁纸就完了。
       设置静态壁纸就完了。
       静态壁纸就完了。
       就完了。
       。。。。。。。。。
       总感觉不对啊~~我们只是把一个图片的内容写入到了一个目录的文件中而已,怎么就会改变壁纸呢,这不合常理啊!
       当时我也是一脸懵逼啊!。。。。。。。
       然后我就想:嗯。。。这个fd指向的文件一定另有玄机,然后翻阅WallpaperManagerService时发现了WallpaperObserver这个类,这个类继承FileObserver,这是一个监听文件改变的类,看来八九不离十。
       接着查找调用它构造方法的地方,最后顺藤摸瓜还是到了SystemServer里:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public final class SystemServer {
//调用startOtherServices
private void startOtherServices() {
//wallpaper就是刚才启动的WallpaperManagerService
final WallpaperManagerService wallpaperF = wallpaper;

// We now tell the activity manager it is okay to run third party
// code. It will call back into us once it has gotten to the state
// where third party code can really run (but before it has actually
// started launching the initial applications), for us to complete our
// initialization.
//当可以启动Launcher之前会回调
mActivityManagerService.systemReady(new Runnable() {
......

try {
//调用WallpaperManagerService 的systemRunning方法
if (wallpaperF != null) wallpaperF.systemRunning();
} catch (Throwable e) {
reportWtf("Notifying WallpaperService running", e);
}
......
}
}
}

       这是startOtherServices的部分代码,设置了一下监听,ActivityManagerService准备工作完成后可以启动Launcher之前触发回调。我们接着看WallpaperManagerService 的systemRunning方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public void systemRunning() {
......
WallpaperData wallpaper = mWallpaperMap.get(UserHandle.USER_OWNER);
......
wallpaper.wallpaperObserver = new WallpaperObserver(wallpaper);
wallpaper.wallpaperObserver.startWatching();

......
}
//WallpaperObserver的构造方法
public WallpaperObserver(WallpaperData wallpaper) {
//已经对/data/system/users/0目录进行监听
super(getWallpaperDir(wallpaper.userId).getAbsolutePath(),
CLOSE_WRITE | MOVED_TO | DELETE | DELETE_SELF);
mWallpaperDir = getWallpaperDir(wallpaper.userId);
mWallpaper = wallpaper;
//这个就是/data/system/users/0/wallpaper这个壁纸文件
mWallpaperFile = new File(mWallpaperDir, WALLPAPER);
}

       这里已经开始了壁纸文件的改动监听,我们可以看看它的回调方法,即WallpaperObserver的onEvent方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
    @Override
public void onEvent(int event, String path) {
if (path == null) {
return;
}
synchronized (mLock) {

......
//改动的文件
File changedFile = new File(mWallpaperDir, path);
//如果改动的文件是/data/system/users/0/wallpaper
if (mWallpaperFile.equals(changedFile)) {
......
//绑定壁纸组件,请注意mImageWallpaper这个参数
bindWallpaperComponentLocked(mImageWallpaper, true,
false, mWallpaper, null);
//保存壁纸信息,和上面的loadSettingsLocked正好相反,一个保存,一个取出
saveSettingsLocked(mWallpaper);
}
}
}
}

       如果改动的文件是/data/system/users/0/wallpaper,则就要绑定壁纸组件,调用bindWallpaperComponentLocked方法。我们这里需要注意第一个参数mImageWallpaper,我们可以看看它是在哪里初始化的:

1
2
3
4
5
6
7
8
9
10
11
  /**
* Name of the component used to display bitmap wallpapers from either the gallery or
* built-in wallpapers.
*/

final ComponentName mImageWallpaper;
public WallpaperManagerService(Context context) {
......
mImageWallpaper = ComponentName.unflattenFromString(
context.getResources().getString(R.string.image_wallpaper_component));
......
}

       它也是在WallpaperManagerService的构造方法中初始化的,R.string.image_wallpaper_component这个资源是个string类型,位于framework/base/core/res/res/values/config.xml中:

1
2
<!-- Component name of the built in wallpaper used to display bitmap wallpapers. This must not be null. -->
<string name="image_wallpaper_component" translatable="false">com.android.systemui/com.android.systemui.ImageWallpaper</string>

这个mImageWallpaper是个ComponentName,指向SystemUI中的ImageWallpaper。这个类是SystemUI app中的一个类,位于framework/base/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java中。

       我们继续,分析bindWallpaperComponentLocked方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
 boolean bindWallpaperComponentLocked(ComponentName componentName, boolean force,
boolean fromUser, WallpaperData wallpaper, IRemoteCallback reply)
{//mImageWallpaper, true, false, mWallpaper, null


......
// Has the component changed?
if (!force) {//force为true,所以不进入这段逻辑
if (wallpaper.connection != null) {
if (wallpaper.wallpaperComponent == null) {
if (componentName == null) {

// Still using default wallpaper.
return true;
}
} else if (wallpaper.wallpaperComponent.equals(componentName)) {
// Changing to same wallpaper.

return true;
}
}
}

try {
if (componentName == null) {//componentName 不为null,所以不进入下面逻辑
componentName = WallpaperManager.getDefaultWallpaperComponent(mContext);
if (componentName == null) {
// Fall back to static image wallpaper
componentName = mImageWallpaper;
//clearWallpaperComponentLocked();
//return;

}
}

......

WallpaperInfo wi = null;

Intent intent = new Intent(WallpaperService.SERVICE_INTERFACE);
if (componentName != null && !componentName.equals(mImageWallpaper)) { // true && false ,因此不进入下面逻辑
// Make sure the selected service is actually a wallpaper service.
List<ResolveInfo> ris =
mIPackageManager.queryIntentServices(intent,
intent.resolveTypeIfNeeded(mContext.getContentResolver()),
PackageManager.GET_META_DATA, serviceUserId);
for (int i=0; i<ris.size(); i++) {
ServiceInfo rsi = ris.get(i).serviceInfo;
if (rsi.name.equals(si.name) &&
rsi.packageName.equals(si.packageName)) {
try {
wi = new WallpaperInfo(mContext, ris.get(i));
} catch (XmlPullParserException e) {
if (fromUser) {
throw new IllegalArgumentException(e);
}
Slog.w(TAG, e);
return false;
} catch (IOException e) {
if (fromUser) {
throw new IllegalArgumentException(e);
}
Slog.w(TAG, e);
return false;
}
break;
}
}
if (wi == null) {
String msg = "Selected service is not a wallpaper: "
+ componentName;
if (fromUser) {
throw new SecurityException(msg);
}
Slog.w(TAG, msg);
return false;
}
}

// Bind the service!
//WallpaperConnection实现了ServiceConnection接口,绑定服务时和解绑时回调两个接口
WallpaperConnection newConn = new WallpaperConnection(wi, wallpaper);
//com.android.systemui/.ImageWallpaper
intent.setComponent(componentName);
intent.putExtra(Intent.EXTRA_CLIENT_LABEL,
com.android.internal.R.string.wallpaper_binding_label);
intent.putExtra(Intent.EXTRA_CLIENT_INTENT, PendingIntent.getActivityAsUser(
mContext, 0,
Intent.createChooser(new Intent(Intent.ACTION_SET_WALLPAPER),
mContext.getText(com.android.internal.R.string.chooser_wallpaper)),
0, null, new UserHandle(serviceUserId)));
//绑定com.android.systemui/.ImageWallpaper这个服务
if (!mContext.bindServiceAsUser(intent, newConn,
Context.BIND_AUTO_CREATE | Context.BIND_SHOWING_UI,
new UserHandle(serviceUserId))) {
String msg = "Unable to bind service: "
+ componentName;
if (fromUser) {
throw new IllegalArgumentException(msg);
}
Slog.w(TAG, msg);
return false;
}
//移除原先壁纸和窗口的管理
if (wallpaper.userId == mCurrentUserId && mLastWallpaper != null) {
detachWallpaperLocked(mLastWallpaper);
}
wallpaper.wallpaperComponent = componentName;
wallpaper.connection = newConn;
newConn.mReply = reply;
try {
if (wallpaper.userId == mCurrentUserId) {
//给壁纸窗口加入TYPE_WALLPAPER类型
mIWindowManager.addWindowToken(newConn.mToken,
WindowManager.LayoutParams.TYPE_WALLPAPER);
mLastWallpaper = wallpaper;
}
} catch (RemoteException e) {
}
} catch (RemoteException e) {
String msg = "Remote exception for " + componentName + "\n" + e;
if (fromUser) {
throw new IllegalArgumentException(msg);
}
Slog.w(TAG, msg);
return false;
}
return true;
}

       这个方法虽然复杂,然是很多逻辑不会进入,少了干扰项。因为我们是设置静态壁纸,所以核心步骤也不多,大概如下:

  • 绑定com.android.systemui/.ImageWallpaper这个服务
  • 移除原先壁纸和窗口的管理,给壁纸窗口加入TYPE_WALLPAPER类型

绑定壁纸服务

       核心就在绑定com.android.systemui/.ImageWallpaper这个服务。
       ImageWallpaper继承于WallpaperService,并且我们看一下它的写法,和上述的动态壁纸的模式一模一样啊:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class ImageWallpaper extends WallpaperService {
@Override
public void onCreate() {
super.onCreate();
mWallpaperManager = (WallpaperManager) getSystemService(WALLPAPER_SERVICE);
......
}
@Override
public Engine onCreateEngine() {
mEngine = new DrawableEngine();
return mEngine;
}
class DrawableEngine extends Engine {
......一些Engine的方法,和上面的魔方那个例子几乎一样.....
}
}

       但是我们分析还是按着步骤一步一来,就回到绑定服务那一步。
       绑定ImageWallpaper这个服务,会回调ServiceConnection接口的onServiceConnected方法。所以我们看看这个WallpaperConnection类的onServiceConnected方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//ImageWallpaper的onBind方法返回值就是这个mService 
IWallpaperService mService;

@Override
public void onServiceConnected(ComponentName name, IBinder service) {
synchronized (mLock) {
if (mWallpaper.connection == this) {
//ImageWallpaper的onBind方法返回值就是这个mService
mService = IWallpaperService.Stub.asInterface(service);
//接下来会调用的
attachServiceLocked(this, mWallpaper);
// XXX should probably do saveSettingsLocked() later
// when we have an engine, but I'm not sure about
// locking there and anyway we always need to be able to
// recover if there is something wrong.
//保存壁纸信息,这个方法上面说过的,和loadSettingsLocked方法正好相对
saveSettingsLocked(mWallpaper);
}
}
}

       ImageWallpaper的onBind方法返回值就是这个mService ,这个得从ImageWallpaper的父类WallpaperService中查找:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/**
* Implement to return the implementation of the internal accessibility
* service interface. Subclasses should not override.
*/

@Override
public final IBinder onBind(Intent intent) {
return new IWallpaperServiceWrapper(this);
}
/**
* Implements the internal {@link IWallpaperService} interface to convert
* incoming calls to it back to calls on an {@link WallpaperService}.
*/

class IWallpaperServiceWrapper extends IWallpaperService.Stub {
private final WallpaperService mTarget;

public IWallpaperServiceWrapper(WallpaperService context) {
mTarget = context;
}

@Override
public void attach(IWallpaperConnection conn, IBinder windowToken,
int windowType, boolean isPreview, int reqWidth, int reqHeight, Rect padding)
{

new IWallpaperEngineWrapper(mTarget, conn, windowToken,
windowType, isPreview, reqWidth, reqHeight, padding);
}
}

       这里给这个类型为IWallpaperService 的mService变量赋值的是IWallpaperServiceWrapper这个类的一个实例。

       然后我们回到上面,接着调用attachServiceLocked(this, mWallpaper),我们再跟进去看看:

1
2
3
4
5
6
7
8
9
10
 void attachServiceLocked(WallpaperConnection conn, WallpaperData wallpaper) {
try {
//这里会调用IWallpaperServiceWrapper 的attach方法
conn.mService.attach(conn, conn.mToken,
WindowManager.LayoutParams.TYPE_WALLPAPER, false,
wallpaper.width, wallpaper.height, wallpaper.padding);
} catch (RemoteException e) {
......
}
}

       内部有调用了IWallpaperServiceWrapper 的attach方法,上面代码已经贴出。可以看到,attach方法内部又创建了一个IWallpaperEngineWrapper对象,所以我们还得看看IWallpaperEngineWrapper的构造方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

IWallpaperEngineWrapper(WallpaperService context,
IWallpaperConnection conn, IBinder windowToken,
int windowType, boolean isPreview, int reqWidth, int reqHeight, Rect padding) {
//这个HandlerCaller就是对Handler的一个封装类,大家以后可以参考它
mCaller = new HandlerCaller(context, context.getMainLooper(), this, true);
mConnection = conn;//WallpaperManagerService中的WallpaperConnection类的对象
mWindowToken = windowToken;//final Binder mToken = new Binder();
mWindowType = windowType;//WindowManager.LayoutParams.TYPE_WALLPAPER
mIsPreview = isPreview;//false
mReqWidth = reqWidth;//wallpaper_info.xml中读取的width
mReqHeight = reqHeight;//wallpaper_info.xml中读取的height
mDisplayPadding.set(padding);//new Rect(0,0,0,0);
//发送DO_ATTACH消息
Message msg = mCaller.obtainMessage(DO_ATTACH);
mCaller.sendMessage(msg);
}

       这里的HandlerCaller就是对Handler的一个封装类,大家以后可以参考它 ,位于framework/base/core/java/com/android/internal/os/HandlerCaller.java,这里不做详细分析。

       IWallpaperEngineWrapper的构造方法最后发送了DO_ATTACH消息,处理消息在IWallpaperEngineWrapper类的executeMessage方法中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public void executeMessage(Message message) {
switch (message.what) {
case DO_ATTACH: {
try {
//调用WallpaperConnection的attachEngine方法
mConnection.attachEngine(this);
} catch (RemoteException e) {
Log.w(TAG, "Wallpaper host disappeared", e);
return;
}
//调用重写的onCreateEngine返回一个Engine类型的对象
Engine engine = onCreateEngine();
mEngine = engine;
mActiveEngines.add(engine);
//在调用Engine的attach方法
engine.attach(this);
return;
}

.......
}
}

       处理DO_ATTACH消息会先调用WallpaperConnection的attachEngine方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Override
public void attachEngine(IWallpaperEngine engine) {
synchronized (mLock) {
//用IWallpaperEngineWrapper类对象赋值
mEngine = engine;
if (mDimensionsChanged) {//false
try {
mEngine.setDesiredSize(mWallpaper.width, mWallpaper.height);
} catch (RemoteException e) {
Slog.w(TAG, "Failed to set wallpaper dimensions", e);
}
mDimensionsChanged = false;
}
if (mPaddingChanged) {//false
try {
mEngine.setDisplayPadding(mWallpaper.padding);
} catch (RemoteException e) {
Slog.w(TAG, "Failed to set wallpaper padding", e);
}
mPaddingChanged = false;
}
}
}

       这个方法就是用IWallpaperEngineWrapper类对象赋值,然后如果尺寸或者缩进改变了,就调整一下大小。

       处理DO_ATTACH消息接着调用重写的onCreateEngine返回一个Engine类型的对象,并将它存入这个mActiveEngines的ArrayList中;

       处理DO_ATTACH消息最后会调用Engine的attach方法,我们进入WallpaperService的内部类Engine的attach方法中看看:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
void attach(IWallpaperEngineWrapper wrapper) {
if (DEBUG) Log.v(TAG, "attach: " + this + " wrapper=" + wrapper);
if (mDestroyed) {
return;
}

mIWallpaperEngine = wrapper;
mCaller = wrapper.mCaller;
mConnection = wrapper.mConnection;
//一个binder对象
mWindowToken = wrapper.mWindowToken;
//mSurfaceHolder是BaseSurfaceHolder类对象,继承与SurfaceHolder,用于控制绘制图层
mSurfaceHolder.setSizeFromLayout();
mInitializing = true;
//申请一个window需要一个WindowSession
mSession = WindowManagerGlobal.getWindowSession();
//一个BaseIWindow类型的对象,继承于IWindow.Stub
mWindow.setSession(mSession);

......

//Engine的初始化工作
onCreate(mSurfaceHolder);

mInitializing = false;
mReportedVisible = false;
//开始往window上的surface里绘制壁纸
updateSurface(false, false, false);
}

       这个attach方法也做了两件事:
       1. 初始化壁纸窗口和绘制图层相关工作
       2. 开始往window上的surface里绘制壁纸

       关于初始化窗口相关内容比较复杂,下面篇幅我会给出参考资料,这一部分原理也很复杂,有兴趣的同学可以自行研究(我也没研究过,以后再补上= 。=)

       绘制壁纸就是在updateSurface方法里,不过这个方法也是很复杂的,可以看出,这个方法会被很多种情况不停触发。我们简单分析一下触发条件:

1
2
3
4
5
6
7
8
9
void updateSurface(boolean forceRelayout, boolean forceReport, boolean redrawNeeded) {
...省略一些逻辑,条件1...
onVisibilityChanged(true);
...省略一些逻辑,条件2...
onSurfaceRedrawNeeded(mSurfaceHolder);
...省略一些逻辑,条件3...
onSurfaceChanged(mSurfaceHolder, mFormat, mCurWidth, mCurHeight);
...省略一些逻辑...
}

       以上三个条件均会触发绘制,即调用了ImageWallpaper的drawFrame方法。drawFrame方法内部对壁纸的大小和位置都做了很多调整,这里我们不作详细分析,我们只care它的绘制:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void drawFrame() {

...省略大部分调整壁纸大小和窗口位置的逻辑...

if (mIsHwAccelerated) {//如果机器支持硬件加速,就用OpenGL绘制
if (!drawWallpaperWithOpenGL(sh, availw, availh, xPixels, yPixels)) {
drawWallpaperWithCanvas(sh, availw, availh, xPixels, yPixels);
}
} else {//否则就用Skia绘制
drawWallpaperWithCanvas(sh, availw, availh, xPixels, yPixels);
......
}
}
}

       关于OpenGL的绘制其实我看不懂,但是我们知道这是往窗口那一层Layer绘制壁纸图像。所以看看使用Canvas开绘制:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
private void drawWallpaperWithCanvas(SurfaceHolder sh, int w, int h, int left, int top) {
Canvas c = sh.lockCanvas();
if (c != null) {
try {
if (DEBUG) {
Log.d(TAG, "Redrawing: left=" + left + ", top=" + top);
}

final float right = left + mBackground.getWidth() * mScale;
final float bottom = top + mBackground.getHeight() * mScale;
if (w < 0 || h < 0) {
c.save(Canvas.CLIP_SAVE_FLAG);
c.clipRect(left, top, right, bottom,
Op.DIFFERENCE);
c.drawColor(0xff000000);
c.restore();
}
if (mBackground != null) {
RectF dest = new RectF(left, top, right, bottom);
// add a filter bitmap?
c.drawBitmap(mBackground, null, dest, null);
}
} finally {
sh.unlockCanvasAndPost(c);
}
}
}

       这个没啥难度吧,就是往Canvas画图。

       到这里绑定壁纸服务的流程就分析完了,唉,也是绕来绕去,绕的头晕。。。。。

窗口管理

       窗口管理这一块其实流程也很复杂,要涉及WindowManagerService等一系列逻辑。上面绑定壁纸服务,在绘制前会申请一个窗口专门用来绘制壁纸。

        在Android系统中,壁纸窗口和输入法窗口一样,都是一种特殊类型的窗口,而且它们都是喜欢和一个普通的Activity窗口缠绵在一起。大家可以充分地想象这样的一个3W场景:输入法窗口在上面,壁纸窗口在下面,Activity窗口夹在它们的中间。

        一个Activity窗口如果需要显示壁纸,那么它必须满足以下两个条件:

       1. 背景是半透明的,例如,它在AndroidManifest.xml文件中的android:theme属性设置为Theme.Translucent:

1
2
3
4
<activity android:name=".WallpaperActivity"
android:theme="@android:style/Theme.Translucent">

......
</activity>

       2. 窗口属性中的WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER位设置为1:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class WallpaperActivity extends Activity {
......


@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);

getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER);
}


......
}

       如果想详细了解Android窗口管理服务WindowManagerService对壁纸窗口(Wallpaper Window)的管理分析 ,可以看看老罗这篇文章

动态壁纸流程分析

       为了应付产品汪的奇葩需求,就得研究一下动态壁纸是如何设置的。

       所以呢:打开模拟器——打开设置——显示——壁纸——动态壁纸——随便选一个——设置壁纸按钮。用IED工具发现这个界面是这样的:com.android.wallpaper.livepicker/.LiveWallpaperPreview。这就随意多了,果断翻源码。位于packages/wallpapers/LivePicker/src/com/android/wallpaper/livepicker/LiveWallpaperPreview.java中,并找到了设置动态壁纸按钮的源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

public class LiveWallpaperPreview extends Activity {

private WallpaperManager mWallpaperManager;
private WallpaperConnection mWallpaperConnection;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

......
mWallpaperManager = WallpaperManager.getInstance(this);

mWallpaperConnection = new WallpaperConnection(mWallpaperIntent);
}

//设置壁纸
public void setLiveWallpaper(View v) {
try {
//这句话是设置动态壁纸的核心
mWallpaperManager.getIWallpaperManager().setWallpaperComponent(
mWallpaperIntent.getComponent());
mWallpaperManager.setWallpaperOffsetSteps(0.5f, 0.0f);
mWallpaperManager.setWallpaperOffsets(v.getRootView().getWindowToken(), 0.5f, 0.0f);
setResult(RESULT_OK);
} catch (RemoteException e) {
// do nothing
} catch (RuntimeException e) {
Log.w(LOG_TAG, "Failure setting wallpaper", e);
}
finish();
}
}

       设置动态壁纸就一句核心代码:
       mWallpaperManager.getIWallpaperManager().setWallpaperComponent(mWallpaperIntent.getComponent());

       因为动态壁纸都是Service,所以上述代码中的参数mWallpaperIntent.getComponent()就是每一个Service的ComponentName。

       为了分析流程,我们继续跟进去。最终是调用WallpaperManagerService的setWallpaperComponent方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public void setWallpaperComponent(ComponentName name) {
checkPermission(android.Manifest.permission.SET_WALLPAPER_COMPONENT);
synchronized (mLock) {
if (DEBUG) Slog.v(TAG, "setWallpaperComponent name=" + name);
int userId = UserHandle.getCallingUserId();
WallpaperData wallpaper = mWallpaperMap.get(userId);
if (wallpaper == null) {
throw new IllegalStateException("Wallpaper not yet initialized for user " + userId);
}
final long ident = Binder.clearCallingIdentity();
try {
wallpaper.imageWallpaperPending = false;
//最终还是调用了bindWallpaperComponentLocked方法,这个我们上面分析静态壁纸分析过了
bindWallpaperComponentLocked(name, false, true, wallpaper, null);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
}

       可以发现,最终还是调用了bindWallpaperComponentLocked方法,只不过第一个参数ComponentName 换了。静态壁纸都是ImageWallpaper这个类,动态壁纸都是自己的类。这个方法我们上面已经分析过了,流程就那么些。

       如果我们想自己绑定自己的壁纸,只要调用mWallpaperManager.getIWallpaperManager().setWallpaperComponent(mWallpaperIntent.getComponent());这句话就行了,只不过需要系统权限,必须在清单文件中配置android:sharedUserId=”android.uid.system”属性,还要打系统签名。

小结

       到此Android的壁纸服务就分析完了。
       动态壁纸和静态壁纸设置流程本质都是一样的,只不过静态壁纸是ImageWallpaper这个服务,动态壁纸是自己的。
       设置静态壁纸可以直接使用WallpaperManager提供的三种方法,setResource(int resId),setBitmap(Bitmap bm)和setStream(InputStream is)。
       设置动态壁纸可以调用:
WallpaperManager.getInstance(context).getIWallpaperManager().setWallpaperComponent(componentName)。

妹子

坚持技术分享,您的支持将鼓励我继续创作!