1.涉及核心類:
1>ImageWallpaper.java(IW):繼承WallpaperService主要負責靜態壁紙的draw處理;
2>WallpaperManager.java(WM):主要負責壁紙的存取方法管理(可能會多個實例);
3>WallpaperManagerService(WMS).java:主要是對WalllpaperManager一些核心方法提供,及一些初始參數的保存(服務);
4>iWallpaperManager.aidl(IWM):負責WallpaperManager與WallpaperManagerService之間的通信;
5>IWallpaperManagerCallback(IMC).aidl:負責WallpaperManager與WallpaperManagerService之間的通信,這是一個回調機制與前面不同;
6>WallpaperService.java(WS):設置壁紙的引擎機制(包括動態與靜態);//這類工作時沒有修改過,所以個人瞭解不是很清楚,希望有朋友補充.
7>launcher.java(LC)設置壁紙初始化值,帶到壁紙機制的轉屏.
2. 壁紙從設置存到取流程:
1>首先WM.setBitmap(bitmap)
public void setBitmap(Bitmap bitmap) throws IOException {
try {
ParcelFileDescriptor fd = sGlobals.mService.setWallpaper(null);
if (fd == null) {
return;
}
FileOutputStream fos = null;
try {
fos = new ParcelFileDescriptor.AutoCloseOutputStream(fd);
bitmap.compress(Bitmap.CompressFormat.PNG, 90, fos);
} finally {
if (fos != null) {
fos.close();
}
}
} catch (RemoteException e) {
}
}
2>然後WMS.setWallpaper(null),設置成功會寫入到壁紙相應文件裏,文件監聽類此時會觸發
private final FileObserver mWallpaperObserver = new FileObserver(
WALLPAPER_DIR.getAbsolutePath(), CREATE | CLOSE_WRITE | DELETE | DELETE_SELF) {
@Override
public void onEvent(int event, String path) {
if (path == null) {
return;
}
synchronized (mLock) {
// changing the wallpaper means we'll need to back up the new one
long origId = Binder.clearCallingIdentity();
BackupManager bm = new BackupManager(mContext);
bm.dataChanged();
Binder.restoreCallingIdentity(origId);
File changedFile = new File(WALLPAPER_DIR, path);
if (WALLPAPER_FILE.equals(changedFile)) {
notifyCallbacksLocked();//會發出廣播與調用回調方法
}
}
}
};
//notifyCallbacksLocked()做兩件事情
private void notifyCallbacksLocked() {
final int n = mCallbacks.beginBroadcast();
for (int i = 0; i < n; i++) {
try {
mCallbacks.getBroadcastItem(i).onWallpaperChanged();//回調機制在WM實現onWallpaperChanged()
} catch (RemoteException e) {
// The RemoteCallbackList will take care of removing
// the dead object for us.
}
}
mCallbacks.finishBroadcast();
final Intent intent = new Intent(Intent.ACTION_WALLPAPER_CHANGED);//壁紙變化的廣播意圖
mContext.sendBroadcast(intent);//IW會接收到此意圖,IW.updateWallpaper()去獲取新壁紙.
}
3>WM.onWallpaperChanged()通過handler機制清除壁紙緩存
private final Handler mHandler;
Globals(Looper looper) {
IBinder b = ServiceManager.getService(Context.WALLPAPER_SERVICE);
mService = IWallpaperManager.Stub.asInterface(b);
mHandler = new Handler(looper) {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_CLEAR_WALLPAPER:
synchronized (this) {
mWallpaper = null;//用戶自定義壁紙
mDefaultWallpaper = null;//系統默認壁紙
}
break;
}
}
};
}
public void onWallpaperChanged() {
/* The wallpaper has changed but we shouldn't eagerly load the
* wallpaper as that would be inefficient. Reset the cached wallpaper
* to null so if the user requests the wallpaper again then we'll
* fetch it.
*/
mHandler.sendEmptyMessage(MSG_CLEAR_WALLPAPER);
}
//IW.updateWallpaper()
void updateWallpaper() {
synchronized (mLock) {
try {
mBackground = mWallpaperManager.getFastDrawable();//WM去獲取壁紙
} catch (RuntimeException e) {
Log.w("ImageWallpaper", "Unable to load wallpaper!", e);
}
}
}
//收到壁紙變換廣播
class WallpaperObserver extends BroadcastReceiver {
public void onReceive(Context context, Intent intent) {
updateWallpaper();//調用
drawFrame();
// Assume we are the only one using the wallpaper in this
// process, and force a GC now to release the old wallpaper.
System.gc();
}
}
@Override
public void onCreate(SurfaceHolder surfaceHolder) {
super.onCreate(surfaceHolder);
IntentFilter filter = new IntentFilter(Intent.ACTION_WALLPAPER_CHANGED);//
mReceiver = new WallpaperObserver();
registerReceiver(mReceiver, filter);//註冊
updateWallpaper();
surfaceHolder.setSizeFromLayout();
}
4>mWallpaperManager.getFastDrawable();//WM去獲取壁紙
a. public Drawable getFastDrawable() {
Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, true);//獲取壁紙總方法
if (bm != null) {
Drawable dr = new FastBitmapDrawable(bm);
return dr;
}
return null;
}
b. public Bitmap peekWallpaperBitmap(Context context, boolean returnDefault) {
synchronized (this) {
if (mWallpaper != null) {
return mWallpaper;
}
if (mDefaultWallpaper != null) {
return mDefaultWallpaper;
}
mWallpaper = null;
try {
mWallpaper = getCurrentWallpaperLocked(context);//調用獲取用戶自定義壁紙方法
} catch (OutOfMemoryError e) {
Log.w(TAG, "No memory load current wallpaper", e);
}
if (mWallpaper == null && returnDefault) {
mDefaultWallpaper = getDefaultWallpaperLocked(context);調用默認壁紙方法
return mDefaultWallpaper;
}
return mWallpaper;
}
}
c. 兩方法分析
private Bitmap getCurrentWallpaperLocked(Context context) {
try {
Bundle params = new Bundle();
ParcelFileDescriptor fd = mService.getWallpaper(this, params);//WMS.getWallpaper(this, params),params是out型表示參數傳送是從WMS傳到WM,是與平時java編碼不合適習慣的這android一特性也是aidl機制的一部分;這裏留個問題就WMS是如何獲取到的params參數呢?
if (fd != null) {
int width = params.getInt("width", 0);
int height = params.getInt("height", 0);
if (width <= 0 || height <= 0) {
// Degenerate case: no size requested, just load
// bitmap as-is.
Bitmap bm = null;
try {
bm = BitmapFactory.decodeFileDescriptor(
fd.getFileDescriptor(), null, null);
} catch (OutOfMemoryError e) {
Log.w(TAG, "Can't decode file", e);
}
try {
fd.close();
} catch (IOException e) {
}
if (bm != null) {
bm.setDensity(DisplayMetrics.DENSITY_DEVICE);
}
return bm;
}
// Load the bitmap with full color depth, to preserve
// quality for later processing.
BitmapFactory.Options options = new BitmapFactory.Options();
options.inDither = false;
options.inPreferredConfig = Bitmap.Config.ARGB_8888;
Bitmap bm = BitmapFactory.decodeFileDescriptor(
fd.getFileDescriptor(), null, options);
try {
fd.close();
} catch (IOException e) {
}
return generateBitmap(context, bm, width, height);
}
} catch (RemoteException e) {
}
return null;
}
private Bitmap getDefaultWallpaperLocked(Context context) {
try {
InputStream is = context.getResources().openRawResource(
com.android.internal.R.drawable.default_wallpaper);
if (is != null) {
int width = mService.getWidthHint();
int height = mService.getHeightHint();
if (width <= 0 || height <= 0) {
// Degenerate case: no size requested, just load
// bitmap as-is.
Bitmap bm = null;
try {
bm = BitmapFactory.decodeStream(is, null, null);
} catch (OutOfMemoryError e) {
Log.w(TAG, "Can't decode stream", e);
}
try {
is.close();
} catch (IOException e) {
}
if (bm != null) {
bm.setDensity(DisplayMetrics.DENSITY_DEVICE);
}
return bm;
}
5>WMS.getWallpaper(this, params)
public ParcelFileDescriptor getWallpaper(IWallpaperManagerCallback cb,
Bundle outParams) {
synchronized (mLock) {
try {
if (outParams != null) {
outParams.putInt("width", mWidth);
outParams.putInt("height", mHeight);
}
mCallbacks.register(cb);
File f = WALLPAPER_FILE;
if (!f.exists()) {
return null;
}
return ParcelFileDescriptor.open(f, MODE_READ_ONLY);//ParcelFileDescriptor是google自定義的句柄具有安全性,對它所屬的流具體保護性,否會圖像丟失出現花屏情況.我對它瞭解也不深,不遇到類似的bug.
} catch (FileNotFoundException e) {
/* Shouldn't happen as we check to see if the file exists */
Slog.w(TAG, "Error getting wallpaper", e);
}
return null;
}
}
6>呵呵呵,該畫了IW.draw(),考慮有很多東西要跟大家分享下就把IW類粘貼出來
package com.android.internal.service.wallpaper;
import com.android.internal.view.WindowManagerPolicyThread;
import android.app.WallpaperManager;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.Region.Op;
import android.graphics.drawable.Drawable;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Process;
import android.service.wallpaper.WallpaperService;
import android.util.Log;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.content.Context;
import android.content.IntentFilter;
import android.content.Intent;
import android.content.BroadcastReceiver;
/**
* Default built-in wallpaper that simply shows a static image.
*/
public class ImageWallpaper extends WallpaperService {
WallpaperManager mWallpaperManager;
private HandlerThread mThread;
@Override
public void onCreate() {
super.onCreate();
mWallpaperManager = (WallpaperManager) getSystemService(WALLPAPER_SERVICE);
Looper looper = WindowManagerPolicyThread.getLooper();
if (looper != null) {
setCallbackLooper(looper);
} else {
mThread = new HandlerThread("Wallpaper", Process.THREAD_PRIORITY_FOREGROUND);
mThread.start();
setCallbackLooper(mThread.getLooper());
}
}
public Engine onCreateEngine() {
return new DrawableEngine();
}
@Override
public void onDestroy() {
super.onDestroy();
if (mThread != null) {
mThread.quit();
}
}
class DrawableEngine extends Engine {
private final Object mLock = new Object();
private WallpaperObserver mReceiver;
Drawable mBackground;
float mXOffset;
float mYOffset;
class WallpaperObserver extends BroadcastReceiver {
public void onReceive(Context context, Intent intent) {
updateWallpaper();
drawFrame();
// Assume we are the only one using the wallpaper in this
// process, and force a GC now to release the old wallpaper.
System.gc();
}
}
@Override
public void onCreate(SurfaceHolder surfaceHolder) {
super.onCreate(surfaceHolder);
IntentFilter filter = new IntentFilter(Intent.ACTION_WALLPAPER_CHANGED);
mReceiver = new WallpaperObserver();
registerReceiver(mReceiver, filter);
updateWallpaper();
surfaceHolder.setSizeFromLayout();
}
@Override
public void onDestroy() {
super.onDestroy();
unregisterReceiver(mReceiver);
}
@Override
public void onVisibilityChanged(boolean visible) {//亮屏時會執行
drawFrame();
}
@Override
public void onTouchEvent(MotionEvent event) {
super.onTouchEvent(event);
}
@Override
public void onOffsetsChanged(float xOffset, float yOffset,
float xOffsetStep, float yOffsetStep,
int xPixels, int yPixels) {//滑動壁紙時會執行
mXOffset = xOffset;
mYOffset = yOffset;
drawFrame();
}
@Override
public void onSurfaceChanged(SurfaceHolder holder, int format, int width, int height) {//開機和轉屏時會執行
super.onSurfaceChanged(holder, format, width, height);
drawFrame();
}
@Override
public void onSurfaceCreated(SurfaceHolder holder) {
super.onSurfaceCreated(holder);
}
@Override
public void onSurfaceDestroyed(SurfaceHolder holder) {
super.onSurfaceDestroyed(holder);
}
void drawFrame() {
SurfaceHolder sh = getSurfaceHolder();
Canvas c = sh.lockCanvas();//鎖住canvas
if (c != null) {
final Rect frame = sh.getSurfaceFrame();
synchronized (mLock) {
final Drawable background = mBackground;
final int dw = frame.width();
final int dh = frame.height();
final int bw = background != null ? background.getIntrinsicWidth() : 0;
final int bh = background != null ? background.getIntrinsicHeight() : 0;
final int availw = dw-bw;
final int availh = dh-bh;
int xPixels = availw < 0 ? (int)(availw*mXOffset+.5f) : (availw/2);
int yPixels = availh < 0 ? (int)(availh*mYOffset+.5f) : (availh/2);
c.translate(xPixels, yPixels);//滑動後計算到壁紙畫起點位置
if (availw<0 || availh<0) {
c.save(Canvas.CLIP_SAVE_FLAG);
c.clipRect(0, 0, bw, bh, Op.DIFFERENCE);
c.drawColor(0xff000000);//出現壁紙尺寸異常或是轉屏延遲就會畫黑
c.restore();
}
if (background != null) {
background.draw(c);
}
}
sh.unlockCanvasAndPost(c);//解鎖canvas並提交
}
}
void updateWallpaper() {
synchronized (mLock) {
try {
mBackground = mWallpaperManager.getFastDrawable();
} catch (RuntimeException e) {
Log.w("ImageWallpaper", "Unable to load wallpaper!", e);
}
}
}
}
}
3.壁紙長寬初始化值及轉屏處理
1>LC.setWallpaperDimension()
private void setWallpaperDimension() {
WallpaperManager wpm = (WallpaperManager)getSystemService(WALLPAPER_SERVICE);
Display display = getWindowManager().getDefaultDisplay();
boolean isPortrait = display.getWidth() < display.getHeight();
final int width = isPortrait ? display.getWidth() : display.getHeight();
final int height = isPortrait ? display.getHeight() : display.getWidth();
wpm.suggestDesiredDimensions(width * WALLPAPER_SCREENS_SPAN, height);//WM.setsuggestDesiredDimensions(width,height) 設置長寬
}
2>
public void suggestDesiredDimensions(int minimumWidth, int minimumHeight) {
try {
sGlobals.mService.setDimensionHints(minimumWidth, minimumHeight);//WMS.setsetDimensionHints(..)
} catch (RemoteException e) {
}
}
3>
public void setDimensionHints(int width, int height) throws RemoteException {
checkPermission(android.Manifest.permission.SET_WALLPAPER_HINTS);
if (width <= 0 || height <= 0) {
throw new IllegalArgumentException("width and height must be > 0");
}
synchronized (mLock) {
if (width != mWidth || height != mHeight) {
mWidth = width;
mHeight = height;
saveSettingsLocked();//將值以xml形式存儲,開機時候會調用loadSettingsLocked()讀取
if (mWallpaperConnection != null) {
if (mWallpaperConnection.mEngine != null) {
try {
mWallpaperConnection.mEngine.setDesiredSize(
width, height);
} catch (RemoteException e) {
}
notifyCallbacksLocked();//通知壁紙有變化(包括換壁紙與橫豎轉換).
}
}
}
}
}
4>android的壁紙機制用得IPC機制,aidl機制,廣播機制,這裏我們就不再介紹.不太清楚google吧.另外我將aidl內容貼出來,希望對大家的理解有幫助.
/** @hide */
1>IWallpaperManager.aidl
interface IWallpaperManager {
/**
* Set the wallpaper.
*/
ParcelFileDescriptor setWallpaper(String name);
/**
* Set the live wallpaper.
*/
void setWallpaperComponent(in ComponentName name);
/**
* Get the wallpaper.
*/
ParcelFileDescriptor getWallpaper(IWallpaperManagerCallback cb,
out Bundle outParams);
/**
* Get information about a live wallpaper.
*/
WallpaperInfo getWallpaperInfo();
/**
* Clear the wallpaper.
*/
void clearWallpaper();
/**
* Sets the dimension hint for the wallpaper. These hints indicate the desired
* minimum width and height for the wallpaper.
*/
void setDimensionHints(in int width, in int height);
/**
* Returns the desired minimum width for the wallpaper.
*/
int getWidthHint();
/**
* Returns the desired minimum height for the wallpaper.
*/
int getHeightHint();
}
2>IWallpaperManagerCallback.aidl
oneway interface IWallpaperManagerCallback {
/**
* Called when the wallpaper has changed
*/
void onWallpaperChanged();
}