Android WallpaperManager 壁纸分析
Android中的壁紙分為靜態壁紙和動態壁紙, 這兩類壁紙本質都是一樣的, 都是通過繼承WallpaperService來實現的,只不過是繪制方面的差異。WallpaperManagerService用于管理壁紙的運行與切換,并通過WallpaperManager類向外界提供操作壁紙的接口,主要體現了對壁紙的管理方式。WallpaperService則對應壁紙的具體實現,實現壁紙服務相關的核心是WallpaperService中的Engine類
1 簡單壁紙案列
1.1 新建壁紙服務
public class MyWallPaperService extends WallpaperService {private static final String TAG = "MyWallPaperService";@Overridepublic Engine onCreateEngine() {return new MyEngine();}class MyEngine extends Engine {@Overridepublic void onSurfaceCreated(SurfaceHolder holder) {super.onSurfaceCreated(holder);Canvas canvas = holder.lockCanvas();canvas.drawColor(Color.GREEN);holder.unlockCanvasAndPost(canvas);}} }1.2 配置文件中配置相關
<!-- AndroidManifest.xml中service配置 --> <service android:name=".MyWallPaperService"android:enabled="true"android:permission="android.permission.BIND_WALLPAPER"><intent-filter ><action android:name="android.service.wallpaper.WallpaperService"/></intent-filter><meta-dataandroid:name="android.service.wallpaper"android:resource="@xml/wallpaper_resource"/> </service><!-- wallpaper_resource.xml配置 --> <?xml version="1.0" encoding="utf-8"?> <wallpaper xmlns:android="http://schemas.android.com/apk/res/android"/>1.3 通過壁紙選擇器設置壁紙,最終壁紙顯示為綠色背景
void setWallpaper() {Intent intent = new Intent(WallpaperManager.ACTION_CHANGE_LIVE_WALLPAPER);ComponentName componentName = new ComponentName(this, MyWallPaperService.class);intent.putExtra(WallpaperManager.EXTRA_LIVE_WALLPAPER_COMPONENT, componentName);startActivity(intent); }2 WallpaperManager相關介紹
// 主屏幕壁紙flag public static final int FLAG_SYSTEM = 1 << 0;// 鎖屏壁紙flag public static final int FLAG_LOCK = 1 << 1; // 設置動態壁紙 public boolean setWallpaperComponent(ComponentName name)// 清除鎖屏壁紙 public void clear(WallpaperManager.FLAG_LOCK)// 清除所有壁紙 public void clearWallpaper()// 獲取靜態壁紙圖片 public Bitmap getBitmap()// 設置靜態壁紙 public void setBitmap(Bitmap bitmap)2.1 WallpaperService和WallpaperManagerService之間的關系
2.2 WallpaperManager和WallpaperManagerService之間的關系
2.3 壁紙涉及到的類介紹
// 壁紙服務信息封裝 WallpaperInfo// 提供接口對壁紙服務的訪問 WallpaperManager// IWallpaperEngine接口服務端對象 // class IWallpaperEngineWrapper extends IWallpaperEngine.Stub IWallpaperEngineWrapper// IWallpaperService接口服務端對象 // IWallpaperServiceWrapper extends IWallpaperService.Stub IWallpaperServiceWrapper// 用戶壁紙相關信息存儲 WallpaperData// 監聽WallPaperService之間的連接狀態和實現IWallpaperConnection接口服務端實現 // class WallpaperConnection extends IWallpaperConnection.Stub // implements ServiceConnection WallpaperConnection// 壁紙服務 // public class WallpaperManagerService extends IWallpaperManager.Stub WallpaperManagerService// 壁紙核心和壁紙繪制相關 Engine3 壁紙相關代碼分析
WallpaperManager的setWallpaperComponent函數為設置動態壁紙函數,接下來以動態壁紙為入口分析相關代碼。Globals實現了IWallpaperManagerCallback接口,sGlobals.mService為WallpaperManagerService的代理對象,sGlobals.mService最終通過binder調用WallpaperManagerService的setWallpaperComponent函數,name為對應壁紙服務的包名。
private static class Globals extends IWallpaperManagerCallback.Stub {private final IWallpaperManager mService; }public boolean setWallpaperComponent(ComponentName name, int userId) {if (sGlobals.mService == null) {Log.w(TAG, "WallpaperService not running");throw new RuntimeException(new DeadSystemException());}try {// step 1, 調用WallpaperManagerService的setWallpaperComponentChecked函數sGlobals.mService.setWallpaperComponentChecked(name, mContext.getOpPackageName(),userId);return true;} catch (RemoteException e) {throw e.rethrowFromSystemServer();} }3.1 WallpaperManagerService::setWallpaperComponent
擁有android.Manifest.permission.SET_WALLPAPER_COMPONENT權限的apk才能設置動態壁紙,壁紙選擇器有這個設置權限,首先進行權限的相關檢查, 然后獲取用戶設置的壁紙相關信息, 錯誤判斷等, 接下來bindWallpaperComponentLocked函數才是核心,對WallpaperService的條件判斷以及綁定
private void setWallpaperComponent(ComponentName name, int userId) {userId = ActivityManager.handleIncomingUser(getCallingPid(), getCallingUid(), userId,false /* all */, true /* full */, "changing live wallpaper", null /* pkg */);// step 1, 權限檢查checkPermission(android.Manifest.permission.SET_WALLPAPER_COMPONENT);int which = FLAG_SYSTEM;boolean shouldNotifyColors = false;WallpaperData wallpaper;synchronized (mLock) {if (DEBUG) Slog.v(TAG, "setWallpaperComponent name=" + name);// step 2, 獲取用戶壁紙相關信息wallpaper = mWallpaperMap.get(userId);if (wallpaper == null) {throw new IllegalStateException("Wallpaper not yet initialized for user " + userId);}final long ident = Binder.clearCallingIdentity();// step 3 , 如果鎖屏壁紙沒有添加,則將鎖屏壁紙flag添加上if (mLockWallpaperMap.get(userId) == null) {which |= FLAG_LOCK;}try {wallpaper.imageWallpaperPending = false;boolean same = changingToSame(name, wallpaper);// step 3, 綁定WallpaperService服務if (bindWallpaperComponentLocked(name, false, true, wallpaper, null)) {if (!same) {wallpaper.primaryColors = null;}wallpaper.wallpaperId = makeWallpaperIdLocked();notifyCallbacksLocked(wallpaper);shouldNotifyColors = true;}} finally {Binder.restoreCallingIdentity(ident);}}if (shouldNotifyColors) {// step 4, 通知壁紙狀態改變notifyWallpaperColorsChanged(wallpaper, which);notifyWallpaperColorsChanged(mFallbackWallpaper, FLAG_SYSTEM);} }3.2 WallpaperManagerService::bindWallpaperComponentLocked
- bindWallpaperComponentLocked函數較長,分開來分析。
- step 1, 如果設置的壁紙服務包名和現在設置的包名相同則返回不做處理
- step 2, 根據componentName來查詢對應的服務相關信息,即ServiceInfo,si為空說明對應包名的服務不存在,也返回不做處理了
- step 3, 對應包名的WallpaperService沒有包含android.Manifest.permission.BIND_WALLPAPER權限,返回不做后續處理
3.3 WallpaperManagerService::bindWallpaperComponentLocked
ris為查詢系統中所有的WallpaperService,篩選componentName包名相同的WallpaperService,并創建相關WallpaperInfo,wi為null的情況, 則說明對應的WallpaperService不存在,返回
WallpaperInfo wi = null;Intent intent = new Intent(WallpaperService.SERVICE_INTERFACE); if (componentName != null && !componentName.equals(mImageWallpaper)) {// 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).getList();for (int i=0; i<ris.size(); i++) {ServiceInfo rsi = ris.get(i).serviceInfo;// step 1, 如果componentName包名對應的service存在,創建WallpaperInfo保存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;}}// step 2, componentName包名對應的service不存在, 直接返回if (wi == null) {String msg = "Selected service is not a wallpaper: "+ componentName;if (fromUser) {throw new SecurityException(msg);}Slog.w(TAG, msg);return false;} }3.4 WallpaperManagerService::bindWallpaperComponentLocked
wi不為空, 對應的WallpaperService存在,后續做綁定服務的工作,創建WallpaperConnection對象,WallpaperConnection繼承IWallpaperConnection接口, 并且實現了ServiceConnection接口。接下來就是通過mContext.bindServiceAsUser來綁定componentName對應的WallpaperService,綁定服務成功后會回調ServiceConnection接口的onServiceConnected函數,即WallpaperConnection的onServiceConnected函數,onServiceConnected函數放到后面分析,同一個用戶,并且上次用戶設置的信息不為空(mLastWallpaper != null),取消掉上一次的壁紙設定。
class WallpaperConnection extends IWallpaperConnection.StubServiceConnection {}final int componentUid = mIPackageManager.getPackageUid(componentName.getPackageName(),MATCH_DIRECT_BOOT_AUTO, wallpaper.userId); // step 1, 創建WallpaperConnection對象 WallpaperConnection newConn = new WallpaperConnection(wi, wallpaper, componentUid); 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)));// step 2, 綁定服務, 服務綁定成功會回調WallpaperConnection的onServiceConnected函數 if (!mContext.bindServiceAsUser(intent, newConn,Context.BIND_AUTO_CREATE | Context.BIND_SHOWING_UI| Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE| Context.BIND_INCLUDE_CAPABILITIES,new UserHandle(serviceUserId))) {String msg = "Unable to bind service: "+ componentName;if (fromUser) {throw new IllegalArgumentException(msg);}Slog.w(TAG, msg);return false; }// step 3, 取消上一次的壁紙服務 if (wallpaper.userId == mCurrentUserId && mLastWallpaper != null&& !wallpaper.equals(mFallbackWallpaper)) {detachWallpaperLocked(mLastWallpaper); } wallpaper.wallpaperComponent = componentName; wallpaper.connection = newConn; newConn.mReply = reply; if (wallpaper.userId == mCurrentUserId && !wallpaper.equals(mFallbackWallpaper)) {mLastWallpaper = wallpaper; }3.5 detachWallpaperLocked
wallpaper.connection不為空, 說明WallPaperService還在運行,wallpaper.connection.mService為IWallpaperService接口,detach函數Android9.0以前不存在,回調IWallpaperService的detach函數,最終調用WallPaperService的detach函數來回收相關資源,和attach函數相對應,通過mContext.unbindService(wallpaper.connection)取消綁定服務來銷毀服務。WallpaperConnection.DisplayConnector::disconnectLocked善后工作
private void detachWallpaperLocked(WallpaperData wallpaper) {// step 1, wallpaper.connection不為空, 說明WallPaperService還在運行if (wallpaper.connection != null) {try {// step 2, 回調WallpaperService的detach函數if (wallpaper.connection.mService != null) {wallpaper.connection.mService.detach();}} catch (RemoteException e) {Slog.w(TAG, "Failed detaching wallpaper service ", e);}// step 3, 取消綁定WallpaperServicemContext.unbindService(wallpaper.connection);// step 4調用WallpaperConnection.DisplayConnector::disconnectLocked函數// 將壁紙的WindowToken從WindowManagerService中移除,壁紙將無法顯示wallpaper.connection.forEachDisplayConnector(WallpaperConnection.DisplayConnector::disconnectLocked);wallpaper.connection.mService = null;wallpaper.connection.mDisplayConnector.clear();wallpaper.connection = null;if (wallpaper == mLastWallpaper) mLastWallpaper = null;} }3.6 WallpaperConnection::DisplayConnector::disconnectLocked
void WallpaperConnection::DisplayConnector::disconnectLocked() {try {// step 1 清除壁紙的WindowTokenmIWindowManager.removeWindowToken(mToken, mDisplayId);} catch (RemoteException e) {}try {// step 2 回調mEngine的destroy函數if (mEngine != null) {mEngine.destroy();}} catch (RemoteException e) {}mEngine = null; }3.7 WallpaperConnection::onServiceConnected
public void WallpaperConnection::onServiceConnected(ComponentName name, IBinder service) {synchronized (mLock) {if (mWallpaper.connection == this) {mService = IWallpaperService.Stub.asInterface(service);// step 1, 調用attachServiceLocked函數attachServiceLocked(this, mWallpaper);// step 2, 將用戶相關數據保存到XML文件中if (!mWallpaper.equals(mFallbackWallpaper)) {saveSettingsLocked(mWallpaper.userId);}FgThread.getHandler().removeCallbacks(mResetRunnable);if (mPerformance != null) {// step 3, 通知壁紙相關信息改變mPerformance.notifyWallpaperChanged(name.getPackageName());}}} }3.8 WallpaperConnection::attachServiceLocked
private void attachServiceLocked(WallpaperConnection conn, WallpaperData wallpaper) {conn.forEachDisplayConnector(connector-> connector.connectLocked(conn, wallpaper)); }3.9 WallpaperConnection::DisplayConnector::connectLocked
connection.mService為IWallpaperService接口,如果為空直接返回, 接下來添加壁紙的token到WindowManagerService中, 這樣壁紙窗口才能添加到WindowManagerService中顯示出來.IWallpaperService的attach為WallpaperService的attach函數,回調attach函數來對WallpaperService的Engine等相關資源的初始化。
void WallpaperConnection::DisplayConnector::connectLocked(WallpaperConnection connection, WallpaperData wallpaper) {// step 1, IWallpaperService為空直接返回if (connection.mService == null) {Slog.w(TAG, "WallpaperService is not connected yet");return;}try {// step 2, 將壁紙相關的WindowToken添加到WMS中// 得有壁紙Token, WallPaperService才能往WMS中添加壁紙窗口mIWindowManager.addWindowToken(mToken, TYPE_WALLPAPER, mDisplayId);} catch (RemoteException e) {Slog.e(TAG, "Failed add wallpaper window token on display " + mDisplayId, e);return;}final DisplayData wpdData = getDisplayDataOrCreate(mDisplayId);try {// step 3, 回調WallPaperService的attach函數connection.mService.attach(connection, mToken, TYPE_WALLPAPER, false,wpdData.mWidth, wpdData.mHeight,wpdData.mPadding, mDisplayId);} catch (RemoteException e) {Slog.w(TAG, "Failed attaching wallpaper on display", e);if (wallpaper != null && !wallpaper.wallpaperUpdating&& connection.getConnectedEngineSize() == 0) {bindWallpaperComponentLocked(null /* componentName */, false /* force */,false /* fromUser */, wallpaper, null /* reply */);}} }4 WallPaperService代碼分析
4.1 WallpaperService::attach
如果服務以及銷毀,則返回,接下來是初始化相關.mSession變量的初始化, 用來向WindowManagerService添加壁紙窗口,surfeace相關初始化后,初始化完成, 更新surface相關信息,最后調用updateSurface函數
void attach(IWallpaperEngineWrapper wrapper) {// step 1 ,如果已經銷毀,返回if (mDestroyed) {return;}mInitializing = true;// step 2 ,獲取Session對象, 和WMS直接通信mSession = WindowManagerGlobal.getWindowSession();mWindow.setSession(mSession);// step 3 , surface相關初始化onCreate(mSurfaceHolder);mInitializing = false;mReportedVisible = false;updateSurface(false, false, false); }WallpaperService::updateSurface
創建事件接收者對象mInputEventReceiver ,調用mSession的addToDisplay將壁紙窗口mWinodw添加到WindowManagerservice中,mSession.relayout將為mWindow分配surface,以及窗口大小告知WallpaperService。mIWallpaperEngine.reportShown()來告知WallpaperManagerService壁紙已經開始顯示了。
void updateSurface(boolean forceRelayout, boolean forceReport, boolean redrawNeeded) {if (mDestroyed) {Log.w(TAG, "Ignoring updateSurface: destroyed");}if (forceRelayout || creating || surfaceCreating || formatChanged || sizeChanged|| typeChanged || flagsChanged || redrawNeeded|| !mIWallpaperEngine.mShownReported) {try {if (!mCreated) {mInputChannel = new InputChannel();// step 1, 將mWindow添加到WindowManagerService中if (mSession.addToDisplay(mWindow, mWindow.mSeq, mLayout, View.VISIBLE,mDisplay.getDisplayId(), mWinFrame, mContentInsets, mStableInsets,mOutsets, mDisplayCutout, mInputChannel,mInsetsState) < 0) {Log.w(TAG, "Failed to add window while updating wallpaper surface.");return;}mCreated = true;// step 2, 創建按鍵接收者mInputEventReceiver = new WallpaperInputEventReceiver(mInputChannel, Looper.myLooper());}// step 3, 給壁紙窗口布局和分配surfacefinal int relayoutResult = mSession.relayout(mWindow, mWindow.mSeq, mLayout, mWidth, mHeight,View.VISIBLE, 0, -1, mWinFrame, mOverscanInsets, mContentInsets,mVisibleInsets, mStableInsets, mOutsets, mBackdropFrame,mDisplayCutout, mMergedConfiguration, mSurfaceControl,mInsetsState);} finally {mIsCreating = false;mSurfaceCreated = true;if (redrawNeeded) {mSession.finishDrawing(mWindow);}// step 4, 通知WallPaperManagerService已經顯示了mIWallpaperEngine.reportShown();}} catch (RemoteException ex) {}} }壁紙相關總結
- 1 壁紙服務相關的配置需要在配置文件中聲明
- 2 實現壁紙服務相關的核心是WallpaperService中的Engine類和surface相關操作
- 3 靜態壁紙和動態壁紙都繼承自WallpaperService
- 4 壁紙窗口WindowToken是通過WindowPaperManagerService來向WMS中來添加和移除操作,和輸入法窗口相似
- 5 Android中的壁紙實現采用系統服務和四大組件中的服務兩層框架來實現, 只需要我們實現自定義的WallpaperService, 就能夠通過系統服務來控制整個壁紙的顯示,隱藏,切換等, 隱藏服務之間的接口調用細節,能夠很輕松的來開發壁紙服務
總結
以上是生活随笔為你收集整理的Android WallpaperManager 壁纸分析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: stm32-sbus数据接收,并通过CA
- 下一篇: 基于 DirectShow 的播放器