Android壁纸服务WallpaperManagerService启动在SystemServer中。当NonCoreServices没有被禁止且config_enableWallpaperService的config值为true时,启动WallpaperManagerService。

/frameworks/base/services/java/com/android/server/SystemServer.java

            ...if (!disableNonCoreServices && context.getResources().getBoolean(R.bool.config_enableWallpaperService)) {try {Slog.i(TAG, "Wallpaper Service");wallpaper = new WallpaperManagerService(context);ServiceManager.addService(Context.WALLPAPER_SERVICE, wallpaper);} catch (Throwable e) {reportWtf("starting Wallpaper Service", e);}}...

  mImageWallpaper的值是一个ComponentName,其值是”com.android.systemui/com.android.systemui.ImageWallpaper”。getWallpaperDir则在设备上创建/data/system/users/0的目录。loadSettingsLocked用来读取保存在xml文件里面的壁纸信息。

/frameworks/base/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java

    public WallpaperManagerService(Context context) {if (DEBUG) Slog.v(TAG, "WallpaperService startup");mContext = context;mImageWallpaper = ComponentName.unflattenFromString(context.getResources().getString(R.string.image_wallpaper_component));mIWindowManager = IWindowManager.Stub.asInterface(ServiceManager.getService(Context.WINDOW_SERVICE));mIPackageManager = AppGlobals.getPackageManager();mMonitor = new MyPackageMonitor();mMonitor.register(context, null, UserHandle.ALL, true);getWallpaperDir(UserHandle.USER_OWNER).mkdirs();loadSettingsLocked(UserHandle.USER_OWNER);}

  loadSettingsLocked中,先是读取/data/system/users/0/wallpaper_info.xml的内容,将一些信息例如壁纸的宽度(width),壁纸的高度(height),名字(name),下一个壁纸组件(component)等信息记录在WallpaperData中,这个WallpaperData和userid组成一个键值对保存在mWallpaperMap中。这样,不同的userid就可以拥有不同的壁纸配置信息。最后,getMaximumSizeDimension返回的是当前屏幕逻辑高度和逻辑宽度的最大值,若壁纸信息记录的宽度或高度小于这个值,则把壁纸的宽度或高度设置成getMaximumSizeDimension的返回值。屏幕逻辑高度和逻辑宽度根据当前的分辨率而定,可能1920x1080的屏幕物理分辨率是1920x1080,但是修改分辨率后,例如1280x720,那么逻辑宽度和逻辑高度就分别为1280和720。

/frameworks/base/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java

    private void loadSettingsLocked(int userId) {if (DEBUG) Slog.v(TAG, "loadSettingsLocked");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;try {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;}if (DEBUG) {Slog.v(TAG, "mWidth:" + wallpaper.width);Slog.v(TAG, "mHeight:" + wallpaper.height);Slog.v(TAG, "mName:" + wallpaper.name);Slog.v(TAG, "mNextWallpaperComponent:"+ wallpaper.nextWallpaperComponent);}}}} while (type != XmlPullParser.END_DOCUMENT);success = true;} catch (FileNotFoundException e) {Slog.w(TAG, "no current wallpaper -- first boot?");} catch (NullPointerException e) {Slog.w(TAG, "failed parsing " + file + " " + e);} catch (NumberFormatException e) {Slog.w(TAG, "failed parsing " + file + " " + e);} catch (XmlPullParserException e) {Slog.w(TAG, "failed parsing " + file + " " + e);} catch (IOException e) {Slog.w(TAG, "failed parsing " + file + " " + e);} catch (IndexOutOfBoundsException e) {Slog.w(TAG, "failed parsing " + file + " " + e);}try {if (stream != null) {stream.close();}} catch (IOException e) {// Ignore}if (!success) {wallpaper.width = -1;wallpaper.height = -1;wallpaper.padding.set(0, 0, 0, 0);wallpaper.name = "";}// We always want to have some reasonable width hint.int baseSize = getMaximumSizeDimension();if (wallpaper.width < baseSize) {wallpaper.width = baseSize;}if (wallpaper.height < baseSize) {wallpaper.height = baseSize;}}

  在SystemServer的另一处,调用了WallpaperManagerService的systemRunning函数来真正运行壁纸服务并加载壁纸。其中,switchWallpaper用来启动真正的壁纸组件,WallpaperObserver使用inotify来监视/data/system/users/0/下面文件的变化,若发生变化的是”wallpaper”或者”wallpaper_info.xml”文件,则回调onEvent函数作相应的处理。下面来看看switchWallpaper
的实现。

/frameworks/base/services/java/com/android/server/SystemServer.java

                ...try {if (wallpaperF != null) wallpaperF.systemRunning();} catch (Throwable e) {reportWtf("Notifying WallpaperService running", e);}...

  switchWallpaper主要是调用bindWallpaperComponentLocked来启动真正的壁纸组件”com.android.systemui/com.android.systemui.ImageWallpaper”。

/frameworks/base/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java

    void switchWallpaper(WallpaperData wallpaper, IRemoteCallback reply) {synchronized (mLock) {RuntimeException e = null;try {ComponentName cname = wallpaper.wallpaperComponent != null ?wallpaper.wallpaperComponent : wallpaper.nextWallpaperComponent;if (bindWallpaperComponentLocked(cname, true, false, wallpaper, reply)) {return;}} catch (RuntimeException e1) {e = e1;}Slog.w(TAG, "Failure starting previous wallpaper", e);clearWallpaperLocked(false, wallpaper.userId, reply);}}

  只看bindWallpaperComponentLocked的关键部分,简而言之就是用bindServiceAsUser来启动一个WallpaperService,而SystemUI中的ImageWallpaper继承自WallpaperService,可以借此启动。若bindWallpaperComponentLocked没有出错返回,则更新WallpaperData的wallpaperComponent和connection为启动的壁纸组件的包名和ServiceConnection。最后,往WindowManagerService添加一个类型为TYPE_WALLPAPER的WindowToken,将mLastWallpaper设为当前的WallpaperData。

/frameworks/base/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java

    boolean bindWallpaperComponentLocked(ComponentName componentName, boolean force,boolean fromUser, WallpaperData wallpaper, IRemoteCallback reply) {...// Bind the service!if (DEBUG) Slog.v(TAG, "Binding to:" + componentName);WallpaperConnection newConn = new WallpaperConnection(wi, wallpaper);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)));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) {if (DEBUG)Slog.v(TAG, "Adding window token: " + newConn.mToken);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;}

  我们知道,当通过bindService启动一个Service时,就会回调ServiceConnection的onServiceConnected函数。看看实现了ServiceConnection接口的WallpaperConnection对onServiceConnected的实现。
  服务端的WallpaperService通过onBind返回一个IBinder对象,作为onServiceConnected的第二个参数传入,这个IBinder对象是一个IWallpaperServiceWrapper对象。asInterface调用返回一个IWallpaperService.Stub的代理对象并保存在mService中。attachServiceLocked
用来启动壁纸的Engine。Engine类提供了许多壁纸呈现的流程的回调接口及属性获取接口,并通过updateSurface来实现添加壁纸窗口和申请Surface的操作,当壁纸的属性发生变化时,我们可以在对应的流程回调接口加入例如drawFrame(画图渲染)等的操作。saveSettingsLocked用来将Engine启动过程中对WallpaperData的修改写回到配置文件wallpaper_info.xml中。

/frameworks/base/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java

        @Overridepublic void onServiceConnected(ComponentName name, IBinder service) {synchronized (mLock) {if (mWallpaper.connection == this) {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.saveSettingsLocked(mWallpaper);}}}

/frameworks/base/core/java/android/service/wallpaper/WallpaperService.java

    @Overridepublic final IBinder onBind(Intent intent) {return new IWallpaperServiceWrapper(this);}

/frameworks/base/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java

    void attachServiceLocked(WallpaperConnection conn, WallpaperData wallpaper) {try {conn.mService.attach(conn, conn.mToken,WindowManager.LayoutParams.TYPE_WALLPAPER, false,wallpaper.width, wallpaper.height, wallpaper.padding);} catch (RemoteException e) {Slog.w(TAG, "Failed attaching wallpaper; clearing", e);if (!wallpaper.wallpaperUpdating) {bindWallpaperComponentLocked(null, false, false, wallpaper, null);}}}

/frameworks/base/core/java/android/service/wallpaper/WallpaperService.java

        @Overridepublic 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);}

  IWallpaperEngineWrapper的构造函数中,发送了一个DO_ATTACH的消息到WallpaperService的Looper的MessageQueue中。

/frameworks/base/core/java/android/service/wallpaper/WallpaperService.java

        IWallpaperEngineWrapper(WallpaperService context,IWallpaperConnection conn, IBinder windowToken,int windowType, boolean isPreview, int reqWidth, int reqHeight, Rect padding) {mCaller = new HandlerCaller(context, context.getMainLooper(), this, true);mConnection = conn;mWindowToken = windowToken;mWindowType = windowType;mIsPreview = isPreview;mReqWidth = reqWidth;mReqHeight = reqHeight;mDisplayPadding.set(padding);Message msg = mCaller.obtainMessage(DO_ATTACH);mCaller.sendMessage(msg);}

/frameworks/base/core/java/android/service/wallpaper/WallpaperService.java

        public void executeMessage(Message message) {switch (message.what) {case DO_ATTACH: {try {mConnection.attachEngine(this);} catch (RemoteException e) {Log.w(TAG, "Wallpaper host disappeared", e);return;}Engine engine = onCreateEngine();mEngine = engine;mActiveEngines.add(engine);engine.attach(this);return;}

  WallpaperConnection将mEngine成员设为传过来的IWallpaperEngineWrapper对象。当我们还没调用attachEngine来attach一个Engine到WallpaperConnection时,而我们希望修改attach到Engine后改变Engine的宽度和高度(mDimensionsChanged),或边距(mPaddingChanged),则可以通过WallpaperManager#suggestDesiredDimensions(改变宽度和高度)和WallpaperManager#setDisplayPadding(改变边距)来实现,这样,这些改变属性的操作将会延迟到attach之后才会执行。WallpaperManager对象可以通过ContextImpl#getSystemService(Context.WINDOW_SERVICE)获得.。

/frameworks/base/core/java/android/service/wallpaper/WallpaperService.java

        @Overridepublic void attachEngine(IWallpaperEngine engine) {synchronized (mLock) {mEngine = engine;if (mDimensionsChanged) {try {mEngine.setDesiredSize(mWallpaper.width, mWallpaper.height);} catch (RemoteException e) {Slog.w(TAG, "Failed to set wallpaper dimensions", e);}mDimensionsChanged = false;}if (mPaddingChanged) {try {mEngine.setDisplayPadding(mWallpaper.padding);} catch (RemoteException e) {Slog.w(TAG, "Failed to set wallpaper padding", e);}mPaddingChanged = false;}}}

  ImageWallpaper类复写了onCreateEngine方法,创建了一个继承自Engine类的DrawableEngine。这个DrawableEngine将被保存在IWallpaperEngineWrapper的mEngine成员中。

/frameworks/base/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java

    @Overridepublic Engine onCreateEngine() {mEngine = new DrawableEngine();return mEngine;}

  mSurfaceHolder是Engine的一个内部类BaseSurfaceHolder对象,其继承了BaseSurfaceHolder类,提供了管理Surface对象和保存Surface生命周期回调接口的能力。onCreate函数在子类DrawableEngine有实现,
用来初始化壁纸使用的Bitmap和使用的Bitmap及Surface的大小,见于updateSurfaceSize函数。最后调用updateSurface进行首次的添加Window,获取Surface和渲染的操作。

/frameworks/base/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java

        void attach(IWallpaperEngineWrapper wrapper) {if (DEBUG) Log.v(TAG, "attach: " + this + " wrapper=" + wrapper);if (mDestroyed) {return;}mIWallpaperEngine = wrapper;mCaller = wrapper.mCaller;mConnection = wrapper.mConnection;mWindowToken = wrapper.mWindowToken;mSurfaceHolder.setSizeFromLayout();mInitializing = true;mSession = WindowManagerGlobal.getWindowSession();mWindow.setSession(mSession);mDisplayManager = (DisplayManager)getSystemService(Context.DISPLAY_SERVICE);mDisplayManager.registerDisplayListener(mDisplayListener, mCaller.getHandler());mDisplay = mDisplayManager.getDisplay(Display.DEFAULT_DISPLAY);if (DEBUG) Log.v(TAG, "onCreate(): " + this);onCreate(mSurfaceHolder);mInitializing = false;mReportedVisible = false;updateSurface(false, false, false);}

  getDefaultDisplaySize获取了屏幕的逻辑宽高。forgetLoadedWallpaper表示忘记原来的壁纸Bitmap,即取消一些变量对Bitmap的引用,防止Bitmap无法被回收造成内存泄露。updateWallpaperLocked用来获得壁纸对应的Bitmap及其宽高数据。我们在壁纸宽度和屏幕逻辑宽度,壁纸高度和屏幕逻辑高度中均选出一个较大值,作为Surface的宽高。
  壁纸的选取地方有(以下按查找顺序排列):1./data/system/users/{userid}/wallpaper;2.ro.config.wallpaper属性配置的壁纸路径;3.默认壁纸default_wallpaper.jpg,存在于/frameworks/base/core/res/res/drawable-xxx/default_wallpaper.jpg。

/frameworks/base/core/java/android/service/wallpaper/WallpaperService.java

        void updateSurfaceSize(SurfaceHolder surfaceHolder) {Point p = getDefaultDisplaySize();// Load background image dimensions, if we haven't saved them yetif (mBackgroundWidth <= 0 || mBackgroundHeight <= 0) {// Need to load the image to get dimensionsmWallpaperManager.forgetLoadedWallpaper();updateWallpaperLocked();if (mBackgroundWidth <= 0 || mBackgroundHeight <= 0) {// Default to the display size if we can't find the dimensionsmBackgroundWidth = p.x;mBackgroundHeight = p.y;}}// Force the wallpaper to cover the screen in both dimensionsint surfaceWidth = Math.max(p.x, mBackgroundWidth);int surfaceHeight = Math.max(p.y, mBackgroundHeight);if (FIXED_SIZED_SURFACE) {// Used a fixed size surface, because we are special.  We can do// this because we know the current design of window animations doesn't// cause this to break.surfaceHolder.setFixedSize(surfaceWidth, surfaceHeight);} else {surfaceHolder.setSizeFromLayout();}}

/frameworks/base/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java

        private void updateWallpaperLocked() {Throwable exception = null;try {Log.d(TAG,"choose a bitmap for wallpaper.");mBackground = null;mBackgroundWidth = -1;mBackgroundHeight = -1;mBackground = mWallpaperManager.getBitmap();mBackgroundWidth = mBackground.getWidth();mBackgroundHeight = mBackground.getHeight();} catch (RuntimeException e) {exception = e;} catch (OutOfMemoryError e) {exception = e;}Log.v(TAG,"mBackgroundWidth is "+mBackgroundWidth+",mBackgroundHeight is "+mBackgroundHeight);if (exception != null) {mBackground = null;mBackgroundWidth = -1;mBackgroundHeight = -1;// Note that if we do fail at this, and the default wallpaper can't// be loaded, we will go into a cycle.  Don't do a build where the// default wallpaper can't be loaded.Log.w(TAG, "Unable to load wallpaper!", exception);try {mWallpaperManager.clear();} catch (IOException ex) {// now we're really screwed.Log.w(TAG, "Unable reset to default wallpaper!", ex);}}}

  当强制layout或Window未创建或Surface未创建或属性改变或Window属性改变或需要重绘时,updateWallpaperLocked分为以下几步:1.更新Engine的对应属性为Surface的属性,更新Window属性;2.当Window尚未添加到WMS时(mCreated为false),使用addToDisplay添加窗口;3.relayout,申请Surface保存在mSurfaceHolder.mSurface中;4.根据具体的属性变化进行BaseSurfaceHolder中CallBack的回调和DrawableEngine的回调。在ImageWallpaper的实现中,会在onVisibilityChanged(可视性改变),onOffsetsChanged(壁纸窗口发生偏移),onSurfaceChanged(Surface属性改变),onSurfaceRedrawNeeded(壁纸窗口需要重绘)中调用drawFrame函数进行绘图渲染。

/frameworks/base/core/java/android/service/wallpaper/WallpaperService.java

        void updateSurface(boolean forceRelayout, boolean forceReport, boolean redrawNeeded) {if (mDestroyed) {Log.w(TAG, "Ignoring updateSurface: destroyed");}boolean fixedSize = false;int myWidth = mSurfaceHolder.getRequestedWidth();Log.v(TAG,"mRequestedWidth is "+myWidth);if (myWidth <= 0) myWidth = ViewGroup.LayoutParams.MATCH_PARENT;else fixedSize = true;int myHeight = mSurfaceHolder.getRequestedHeight();Log.v(TAG,"mRequestedHeight is "+myHeight);if (myHeight <= 0) myHeight = ViewGroup.LayoutParams.MATCH_PARENT;else fixedSize = true;final boolean creating = !mCreated;//Window是否需要添加final boolean surfaceCreating = !mSurfaceCreated;//Surface是否已得到final boolean formatChanged = mFormat != mSurfaceHolder.getRequestedFormat();//像素格式是否改变boolean sizeChanged = mWidth != myWidth || mHeight != myHeight;//Surafce大小是否改变boolean insetsChanged = !mCreated;//边衬区域是否改变final boolean typeChanged = mType != mSurfaceHolder.getRequestedType();//Surface buffer类型改变final boolean flagsChanged = mCurWindowFlags != mWindowFlags ||mCurWindowPrivateFlags != mWindowPrivateFlags;//Window的Flags或PrivateFlags是否改变if (forceRelayout || creating || surfaceCreating || formatChanged || sizeChanged|| typeChanged || flagsChanged || redrawNeeded|| !mIWallpaperEngine.mShownReported) {if (DEBUG) Log.v(TAG, "Changes: creating=" + creating+ " format=" + formatChanged + " size=" + sizeChanged);try {mWidth = myWidth;mHeight = myHeight;mFormat = mSurfaceHolder.getRequestedFormat();mType = mSurfaceHolder.getRequestedType();//初始化传给WindowManagerService的WindowManager.LayoutParams参数mLayout.x = 0;mLayout.y = 0;mLayout.width = myWidth;mLayout.height = myHeight;mLayout.format = mFormat;mCurWindowFlags = mWindowFlags;mLayout.flags = mWindowFlags| WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS| WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN| WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;mCurWindowPrivateFlags = mWindowPrivateFlags;mLayout.privateFlags = mWindowPrivateFlags;mLayout.memoryType = mType;mLayout.token = mWindowToken;if (!mCreated) {// Retrieve watch round and outset infofinal WindowManager windowService = (WindowManager)getSystemService(Context.WINDOW_SERVICE);TypedArray windowStyle = obtainStyledAttributes(com.android.internal.R.styleable.Window);final Display display = windowService.getDefaultDisplay();final boolean shouldUseBottomOutset =display.getDisplayId() == Display.DEFAULT_DISPLAY;if (shouldUseBottomOutset && windowStyle.hasValue(R.styleable.Window_windowOutsetBottom)) {if (mOutsetBottom == null) mOutsetBottom = new TypedValue();windowStyle.getValue(R.styleable.Window_windowOutsetBottom,mOutsetBottom);} else {mOutsetBottom = null;}mWindowIsRound = getResources().getBoolean(com.android.internal.R.bool.config_windowIsRound);windowStyle.recycle();// detect emulatormIsEmulator = Build.HARDWARE.contains("goldfish");mIsCircularEmulator = SystemProperties.getBoolean(ViewRootImpl.PROPERTY_EMULATOR_CIRCULAR, false);// Add windowmLayout.type = mIWallpaperEngine.mWindowType;mLayout.gravity = Gravity.START|Gravity.TOP;Log.v(TAG,"title is "+WallpaperService.this.getClass().getName());mLayout.setTitle(WallpaperService.this.getClass().getName());mLayout.windowAnimations =com.android.internal.R.style.Animation_Wallpaper;mInputChannel = new InputChannel();if (mSession.addToDisplay(mWindow, mWindow.mSeq, mLayout, View.VISIBLE,Display.DEFAULT_DISPLAY, mContentInsets, mStableInsets,mInputChannel) < 0) {Log.w(TAG, "Failed to add window while updating wallpaper surface.");return;}mCreated = true;mInputEventReceiver = new WallpaperInputEventReceiver(mInputChannel, Looper.myLooper());}//上锁mSurfaceHolder.mSurfaceLock.lock();mDrawingAllowed = true;if (!fixedSize) {//宽高为非固定值的情况下,更新Surface边距(Surface与Window的边距)mLayout.surfaceInsets.set(mIWallpaperEngine.mDisplayPadding);} else {//宽高为固定值的情况下,不设边距mLayout.surfaceInsets.set(0, 0, 0, 0);}//relayoutfinal int relayoutResult = mSession.relayout(mWindow, mWindow.mSeq, mLayout, mWidth, mHeight,View.VISIBLE, 0, mWinFrame, mOverscanInsets, mContentInsets,mVisibleInsets, mStableInsets, mConfiguration, mSurfaceHolder.mSurface);if (DEBUG) Log.v(TAG, "New surface: " + mSurfaceHolder.mSurface+ ", frame=" + mWinFrame);int w = mWinFrame.width();int h = mWinFrame.height();if (!fixedSize) {//宽高非固定值的情况下,更新各种边距值final Rect padding = mIWallpaperEngine.mDisplayPadding;w += padding.left + padding.right;h += padding.top + padding.bottom;mOverscanInsets.left += padding.left;mOverscanInsets.top += padding.top;mOverscanInsets.right += padding.right;mOverscanInsets.bottom += padding.bottom;mContentInsets.left += padding.left;mContentInsets.top += padding.top;mContentInsets.right += padding.right;mContentInsets.bottom += padding.bottom;mStableInsets.left += padding.left;mStableInsets.top += padding.top;mStableInsets.right += padding.right;mStableInsets.bottom += padding.bottom;}if (mCurWidth != w) {sizeChanged = true;mCurWidth = w;}if (mCurHeight != h) {sizeChanged = true;mCurHeight = h;}insetsChanged |= !mDispatchedOverscanInsets.equals(mOverscanInsets);insetsChanged |= !mDispatchedContentInsets.equals(mContentInsets);insetsChanged |= !mDispatchedStableInsets.equals(mStableInsets);mSurfaceHolder.setSurfaceFrameSize(w, h);mSurfaceHolder.mSurfaceLock.unlock();if (!mSurfaceHolder.mSurface.isValid()) {reportSurfaceDestroyed();if (DEBUG) Log.v(TAG, "Layout: Surface destroyed");return;}boolean didSurface = false;try {mSurfaceHolder.ungetCallbacks();if (surfaceCreating) {//Surface创建成功后的操作mIsCreating = true;didSurface = true;if (DEBUG) Log.v(TAG, "onSurfaceCreated("+ mSurfaceHolder + "): " + this);onSurfaceCreated(mSurfaceHolder);SurfaceHolder.Callback callbacks[] = mSurfaceHolder.getCallbacks();if (callbacks != null) {for (SurfaceHolder.Callback c : callbacks) {c.surfaceCreated(mSurfaceHolder);}}}redrawNeeded |= creating || (relayoutResult& WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) != 0;if (forceReport || creating || surfaceCreating|| formatChanged || sizeChanged) {//Surface发生改变的操作if (DEBUG) {RuntimeException e = new RuntimeException();e.fillInStackTrace();Log.w(TAG, "forceReport=" + forceReport + " creating=" + creating+ " formatChanged=" + formatChanged+ " sizeChanged=" + sizeChanged, e);}if (DEBUG) Log.v(TAG, "onSurfaceChanged("+ mSurfaceHolder + ", " + mFormat+ ", " + mCurWidth + ", " + mCurHeight+ "): " + this);didSurface = true;onSurfaceChanged(mSurfaceHolder, mFormat,mCurWidth, mCurHeight);SurfaceHolder.Callback callbacks[] = mSurfaceHolder.getCallbacks();if (callbacks != null) {for (SurfaceHolder.Callback c : callbacks) {c.surfaceChanged(mSurfaceHolder, mFormat,mCurWidth, mCurHeight);}}}if (insetsChanged) {//边距发生改变的操作mDispatchedOverscanInsets.set(mOverscanInsets);mDispatchedContentInsets.set(mContentInsets);mDispatchedStableInsets.set(mStableInsets);final boolean isRound = (mIsEmulator && mIsCircularEmulator)|| mWindowIsRound;mFinalSystemInsets.set(mDispatchedOverscanInsets);mFinalStableInsets.set(mDispatchedStableInsets);if (mOutsetBottom != null) {final DisplayMetrics metrics = getResources().getDisplayMetrics();mFinalSystemInsets.bottom =( (int) mOutsetBottom.getDimension(metrics) )+ mIWallpaperEngine.mDisplayPadding.bottom;}WindowInsets insets = new WindowInsets(mFinalSystemInsets,null, mFinalStableInsets, isRound);onApplyWindowInsets(insets);}if (redrawNeeded) {//需要重绘的操作onSurfaceRedrawNeeded(mSurfaceHolder);SurfaceHolder.Callback callbacks[] = mSurfaceHolder.getCallbacks();if (callbacks != null) {for (SurfaceHolder.Callback c : callbacks) {if (c instanceof SurfaceHolder.Callback2) {((SurfaceHolder.Callback2)c).surfaceRedrawNeeded(mSurfaceHolder);}}}}if (didSurface && !mReportedVisible) {//Surface发生了改变且壁纸尚不可见// This wallpaper is currently invisible, but its// surface has changed.  At this point let's tell it// again that it is invisible in case the report about// the surface caused it to start running.  We really// don't want wallpapers running when not visible.if (mIsCreating) {//属于创建Surface的情况// Some wallpapers will ignore this call if they// had previously been told they were invisble,// so if we are creating a new surface then toggle// the state to get them to notice.if (DEBUG) Log.v(TAG, "onVisibilityChanged(true) at surface: "+ this);onVisibilityChanged(true);}if (DEBUG) Log.v(TAG, "onVisibilityChanged(false) at surface: "+ this);onVisibilityChanged(false);}} finally {mIsCreating = false;mSurfaceCreated = true;if (redrawNeeded) {//需要重绘的情况,一般发生在首次relayout或者首次获得Surface,需要显示一个新的窗口mSession.finishDrawing(mWindow);}mIWallpaperEngine.reportShown();}} catch (RemoteException ex) {}if (DEBUG) Log.v(TAG, "Layout: x=" + mLayout.x + " y=" + mLayout.y +" w=" + mLayout.width + " h=" + mLayout.height);}}

 &esmp;drawFrame作为壁纸服务的绘图渲染函数。

/frameworks/base/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java

        void drawFrame() {try {int newRotation = ((WindowManager) getSystemService(WINDOW_SERVICE)).getDefaultDisplay().getRotation();// Sometimes a wallpaper is not large enough to cover the screen in one dimension.// Call updateSurfaceSize -- it will only actually do the update if the dimensions// should changeif (newRotation != mLastRotation) {// Update surface size (if necessary)//如果屏幕的旋转发生了变化,更新SUrface的大小updateSurfaceSize(getSurfaceHolder());}SurfaceHolder sh = getSurfaceHolder();final Rect frame = sh.getSurfaceFrame();final int dw = frame.width();final int dh = frame.height();//Surface中的Window的frame的宽和高对比上次保存的此值是否有变化boolean surfaceDimensionsChanged = dw != mLastSurfaceWidth|| dh != mLastSurfaceHeight;boolean redrawNeeded = surfaceDimensionsChanged || newRotation != mLastRotation;if (!redrawNeeded && !mOffsetsChanged) {//当Surface中的Window的frame的宽和高没有变化&旋转状态没有变化&壁纸偏移没有变化,直接返回if (DEBUG) {Log.d(TAG, "Suppressed drawFrame since redraw is not needed "+ "and offsets have not changed.");}return;}mLastRotation = newRotation;// Load bitmap if it is not yet loaded or if it was loaded at a different sizeif (mBackground == null || surfaceDimensionsChanged) {if (DEBUG) {Log.d(TAG, "Reloading bitmap: mBackground, bgw, bgh, dw, dh = " +mBackground + ", " +((mBackground == null) ? 0 : mBackground.getWidth()) + ", " +((mBackground == null) ? 0 : mBackground.getHeight()) + ", " +dw + ", " + dh);}//没有壁纸图片或者当Surface中的Window的frame的宽和高有变化mWallpaperManager.forgetLoadedWallpaper();updateWallpaperLocked();if (mBackground == null) {if (DEBUG) {Log.d(TAG, "Unable to load bitmap");}return;}if (DEBUG) {if (dw != mBackground.getWidth() || dh != mBackground.getHeight()) {Log.d(TAG, "Surface != bitmap dimensions: surface w/h, bitmap w/h: " +dw + ", " + dh + ", " + mBackground.getWidth() + ", " +mBackground.getHeight());}}}// Center the scaled image//mScale表示壁纸图片的拉伸比例mScale = Math.max(1f, Math.max(dw / (float) mBackground.getWidth(),dh / (float) mBackground.getHeight()));final int availw = dw - (int) (mBackground.getWidth() * mScale);final int availh = dh - (int) (mBackground.getHeight() * mScale);int xPixels = availw / 2;int yPixels = availh / 2;// Adjust the image for xOffset/yOffset values. If window manager is handling offsets,// mXOffset and mYOffset are set to 0.5f by default and therefore xPixels and yPixels// will remain unchangedfinal int availwUnscaled = dw - mBackground.getWidth();final int availhUnscaled = dh - mBackground.getHeight();if (availwUnscaled < 0)xPixels += (int) (availwUnscaled * (mXOffset - .5f) + .5f);if (availhUnscaled < 0)yPixels += (int) (availhUnscaled * (mYOffset - .5f) + .5f);mOffsetsChanged = false;mRedrawNeeded = false;if (surfaceDimensionsChanged) {mLastSurfaceWidth = dw;mLastSurfaceHeight = dh;}if (!redrawNeeded && xPixels == mLastXTranslation && yPixels == mLastYTranslation) {if (DEBUG) {Log.d(TAG, "Suppressed drawFrame since the image has not "+ "actually moved an integral number of pixels.");}return;}mLastXTranslation = xPixels;mLastYTranslation = yPixels;if (DEBUG) {Log.d(TAG, "Redrawing wallpaper");}if (mIsHwAccelerated) {//一般情况下都启动硬件加速if (!drawWallpaperWithOpenGL(sh, availw, availh, xPixels, yPixels)) {//OpenGL渲染失败则使用canvas绘制drawWallpaperWithCanvas(sh, availw, availh, xPixels, yPixels);}} else {drawWallpaperWithCanvas(sh, availw, availh, xPixels, yPixels);}} finally {if (FIXED_SIZED_SURFACE && !mIsHwAccelerated) {// If the surface is fixed-size, we should only need to// draw it once and then we'll let the window manager// position it appropriately.  As such, we no longer needed// the loaded bitmap.  Yay!// hw-accelerated renderer retains bitmap for faster rotationmBackground = null;mWallpaperManager.forgetLoadedWallpaper();}}}

  关于壁纸偏移。首先关注的是mOffsetsChanged变量。在对壁纸的窗口进行relayout时,传入了一个继承自IWindow.Stub的BaseIWindow对象,里面实现了WMS对壁纸窗口进行调整后的回调。其中有resized(ContentInsets,VisibleInsets,Surface的大小改变或者Configuration改变时的回调),moved(窗口坐标改变的回调),dispatchAppVisibility(窗口可见性改变的回调),dispatchWallpaperOffsets(窗口偏移发生改变的回调)等。那mOffsetsChanged什么时候为true表示壁纸窗口偏移发生了改变呢?1.调用setDimensionHints来设置Engine的期望壁纸宽高时(没有设置则为传过来的WallpaperData记录的壁纸宽高值),因为壁纸窗口偏移的值需要根据Engine的期望壁纸宽高来决定,但是在SystemUI的ImageWallpaper的相应实现里没有利用到Engine的期望壁纸宽高。2.dispatchWallpaperOffsets回调来更新偏移值时。在Surface已创建的情况下,若壁纸窗口处于可见状态,onOffsetsChanged(ImageWallpaper的实现)会检查传入的xOffset和yOffset是否与mXOffset和mYOffset保存的值相同,若有一个不同,则认为是壁纸窗口偏移发生了变化,设置mOffsetsChanged为true。若壁纸窗口不处于可见状态,则直接设置mOffsetsChanged为true,而不必要更新mXOffset,mYOffset,更不需要调用drawFrame重画一帧,这些等到壁纸壁纸窗口可见以后再更新就可以了。3.dispatchAppVisibility回调更新壁纸窗口可见性时,如果在壁纸窗口不可见时有人申请了更新窗口偏移值(当时没更新mXOffset,mYOffset,只是设置mOffsetsChanged为true),则再壁纸窗口变得可见后再重新处理这些请求,见onOffsetsChanged的实现。

/frameworks/base/core/java/android/service/wallpaper/WallpaperService.java

        void doOffsetsChanged(boolean always) {if (mDestroyed) {return;}if (!always && !mOffsetsChanged) {return;}float xOffset;float yOffset;float xOffsetStep;float yOffsetStep;boolean sync;synchronized (mLock) {xOffset = mPendingXOffset;yOffset = mPendingYOffset;xOffsetStep = mPendingXOffsetStep;yOffsetStep = mPendingYOffsetStep;sync = mPendingSync;mPendingSync = false;mOffsetMessageEnqueued = false;}if (mSurfaceCreated) {if (mReportedVisible) {if (DEBUG) Log.v(TAG, "Offsets change in " + this+ ": " + xOffset + "," + yOffset);final int availw = mIWallpaperEngine.mReqWidth-mCurWidth;final int xPixels = availw > 0 ? -(int)(availw*xOffset+.5f) : 0;final int availh = mIWallpaperEngine.mReqHeight-mCurHeight;final int yPixels = availh > 0 ? -(int)(availh*yOffset+.5f) : 0;onOffsetsChanged(xOffset, yOffset, xOffsetStep, yOffsetStep, xPixels, yPixels);} else {mOffsetsChanged = true;}}if (sync) {try {if (DEBUG) Log.v(TAG, "Reporting offsets change complete");mSession.wallpaperOffsetsComplete(mWindow.asBinder());} catch (RemoteException e) {}}}

/frameworks/base/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java

        @Overridepublic void onOffsetsChanged(float xOffset, float yOffset,float xOffsetStep, float yOffsetStep,int xPixels, int yPixels) {if (DEBUG) {Log.d(TAG, "onOffsetsChanged: xOffset=" + xOffset + ", yOffset=" + yOffset+ ", xOffsetStep=" + xOffsetStep + ", yOffsetStep=" + yOffsetStep+ ", xPixels=" + xPixels + ", yPixels=" + yPixels);}if (mXOffset != xOffset || mYOffset != yOffset) {if (DEBUG) {Log.d(TAG, "Offsets changed to (" + xOffset + "," + yOffset + ").");}mXOffset = xOffset;mYOffset = yOffset;mOffsetsChanged = true;}drawFrame();}

/frameworks/base/core/java/android/service/wallpaper/WallpaperService.java

        void reportVisibility() {if (!mDestroyed) {boolean visible = mVisible& mDisplay != null && mDisplay.getState() != Display.STATE_OFF;if (mReportedVisible != visible) {mReportedVisible = visible;if (DEBUG) Log.v(TAG, "onVisibilityChanged(" + visible+ "): " + this);if (visible) {// If becoming visible, in preview mode the surface// may have been destroyed so now we need to make// sure it is re-created.doOffsetsChanged(false);updateSurface(false, false, false);}onVisibilityChanged(visible);}}}

  但实际上壁纸的分辨率和屏幕的分辨率不相同,我们应该将壁纸画在哪个位置也是一个问题。有3种情形:1.壁纸可以完全包含屏幕;2.屏幕可以完全包含壁纸;3.壁纸和屏幕相交但不互相完全包含。
  对于壁纸可以完全包含屏幕的情况,就是根据mXOffset和mYOffset的值把屏幕的位置放在壁纸的适当位置,当mXOffset和mYOffset的值均为0.5时,屏幕放在壁纸的中央位置,壁纸坐标(left,top,right,bottom)为(-(屏幕宽-壁纸宽)/2,-(屏幕高-壁纸高)/2,(屏幕宽-壁纸宽)/2+壁纸宽,(屏幕高-壁纸高)/2+壁纸高),mXOffset的值从0到1增长时,会将壁纸往左边移动(假设屏幕位置不动),mYOffset的值从0到1增长时,会将壁纸往上边移动,也就是说,mXOffset和mYOffset的值越大,屏幕占用的区域就越接近壁纸的右下方;对于屏幕可以完全包含壁纸的情况,需要计算壁纸的拉伸值mScale(屏幕宽/壁纸宽,屏幕高/壁纸高中的最大值),屏幕宽高值分别减去壁纸的宽高乘以拉伸值得到的值得到availw和availh,这个时候availw和availh必然有一个为0,有一个为负数,则把壁纸放在负数对应的那条边的中间位置以屏幕边的中点位置进行以mScale值为比例的拉伸。对于壁纸和屏幕相交但不互相完全包含的情况,方法同屏幕可以完全包含壁纸的情况基本一致,都是先拉伸壁纸到恰好可以可以完全包含屏幕后,将壁纸贴合沿着拉伸后屏幕中长度还未和壁纸一致的边进行移动,直到屏幕恰好在在拉伸后壁纸中央。

Android壁纸服务WallpaperManagerService相关推荐

  1. android 壁纸服务,Android视频壁纸的实现

    视频壁纸属于动态壁纸,所以视频壁纸就可以用Android系统提供的动态壁纸服务来实现.首先先介绍一下在实现过程中会用到的几个类. WallpaperManager Android提供的用于管理壁纸的类 ...

  2. android 壁纸服务,Android-Service实现手机壁纸自动更换

    本文实例为大家分享了Android Service实现自动更换手机壁纸的具体代码,供大家参考,具体内容如下 先看下效果: 使用界面: 划重点,使用service前别忘了给相应的service添加服务 ...

  3. android 壁纸服务,Android开发学习之WallPaper设置壁纸详细介绍与实例

    今天和大家分享的是关于在android中设置壁纸的方法,在android中设置壁纸的方法有三种,分别是: 1.使用wallpapermanager的setresource(int resourceid ...

  4. android壁纸显示逻辑

    所有文章仅限自己备忘,并无他用 壁纸主要分为两类,锁屏壁纸和桌面壁纸 一,壁纸服务的启动 壁纸服务WallpaperManagerService中,有一个内部类LifeCycle继承自SystemSe ...

  5. Android 10.0WallpaperPicker2添加动态壁纸服务

    1.概述 在10.0的系统产品开发中,在定制化开发中,由于需要动态壁纸,而系统自带的只有默认的静态壁纸, 所以需要添加动态壁纸的功能 2.WallpaperPicker2添加动态壁纸服务的核心类 pa ...

  6. 《深入理解Android 卷III》第八章深入理解Android壁纸

    <深入理解Android 卷III>即将发布,作者是张大伟.此书填补了深入理解Android Framework卷中的一个主要空白,即Android Framework中和UI相关的部分. ...

  7. 《深入理解Android 卷III》第八章深入理解Android壁纸(完整版)

    第8章 深入理解Android壁纸 本章主要内容: ·  讨论动态壁纸的实现. ·  在动态壁纸的基础上讨论静态壁纸的实现. ·  讨论WMS对壁纸窗口所做的特殊处理. 本章涉及的源代码文件名及位置: ...

  8. Android壁纸机制(Android Q与老版本对比)

    一.什么是Android壁纸? Android中,壁纸分为动态壁纸和静态壁纸两种.静态壁纸是一张图片,动态壁纸是以动画为表现形式,有的可以对用户的操作作出反应.二者表现形式看似差异很大,但是二者的本质 ...

  9. Android壁纸管理(Android N)

    初识Android壁纸 本章将对壁纸的实现原理进行讨论.在Android中,壁纸分为静态与动态两种.静态壁纸是一张图片,而动态壁纸则以动画为表现形式,或者可以对用户的操作作出反应.这两种形式看似差异很 ...

最新文章

  1. 工作流引擎在视频网站架构中的应用
  2. 学php需要哪些基础,学习php需要什么基础?(附php学习路线图)
  3. @scheduled只执行一次_SpringBoot第四篇:定时任务@Scheduled
  4. docker命令及挂载
  5. 深度解析Cocoa异步请求和libxml2.dylib教程(1)
  6. C#数据库事务机制及实践(下)
  7. 12月江苏计算机考试报名入口,江苏2019年12月计算机等级报名时间丨报名系统
  8. 爱立信发布体验版WebRTC移动浏览器…
  9. spark分片个数的确定及Spark内存错误(GC error)的迂回解决方式
  10. imp遇到重复数据_oracle的imp导入时覆盖目标数据库
  11. css cursor用法
  12. usermod -a -G group1 user1
  13. 优动漫PAINT是什么?有哪些功能和特色
  14. aptx与ldac音质区别_蓝牙协议LDAC和aptx的区别?
  15. tomcat端口修改
  16. 学习HTML5 Canvas这一篇文章就够了
  17. Android高级页面设计 -- Recycler
  18. ExoPlayer 中的音频时间戳计算
  19. 斐波拉契数列多种算法实现
  20. 自学K60时的部分小结

热门文章

  1. Swift入门教程10-subscript下标脚本
  2. signal软件如何退出账号_AppStore今日分享 很贵很强大的视频编辑软件
  3. 奥浦迈科创板过会:毛利率高,实控人肖志华、贺芸芬持美国绿卡
  4. Typecho上手指南
  5. 民营企业家胡国安:慈无涯,善为源
  6. 阿里云视频上传视频获取进度条问题(使用session方案,获取进度一直为0的解决方案)补充:前后端分离项目中获取进度解决方案
  7. 详解工业机器人机器视觉系统
  8. 数据寄存器多少位怎么算_2020新车购置税怎么算 新车购置税在哪交 新车购置税多少钱...
  9. CSS Spritec下载,精灵图,雪碧图,初探之原理、使用
  10. 2021年中国大豆行业发展现状分析,行业仍需大量进口「图」