Android Wallpaper之設置壁紙流程

What?
什麼是壁紙?java

android wallpaper包括鎖屏壁紙和桌面壁紙,壁紙又區分靜態和動態兩種。咱們天天使用手機第一眼看到的就是壁紙,好看的壁紙對於手機的顏值也有大大的提高(滑稽),就讓咱們對壁紙一探究竟吧。android

本文基於Android 8.1源碼,相關文件以下:app

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

2./frameworks/base/services/core/java/android/app/WallpaperManager.javaide

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

4./frameworks/base/core/java/android/service/wallpaper/IwallpaperService.aidlui

5./frameworks/base/packages/apps/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.javathis

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

7./frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.javacomponent

How?
1.做爲開發者如何去設置壁紙?

2.壁紙是怎麼顯示的?

3.壁紙存儲在什麼位置?

如何設置壁紙?舉個栗子。


我們手機中通常有內置主題的應用,在這裏能夠下載使用很是多好看的壁紙,點擊便可設置爲鎖屏或桌面壁紙。

設置方法很簡單,look:

WallpaperManager wallpaperManager = WallpaperManager.getInstance(this);
try {
wallpaperManager.setStream(InputStream,null,true,WallpaperManager.FLAG_LOCK);
} catch (IOException e) {
   e.printStackTrace();
}
三步驟:
1.添加設置壁紙的權限

<uses-permission android:name="android.permission.SET_WALLPAPER"/>
2.獲取WallpaperManager對象

3.設置壁紙,四個參數分別對應:

a.InputStream:圖片對於的輸入流

b.visibleCropHint:圖片裁剪相關,通常默認爲null

c.allowBack:是否容許回退

d.which:壁紙分爲鎖屏壁紙和桌面壁紙,因此須要設置FLGA:鎖屏壁紙--WallpaperManager.FLAG_LOCK,桌面壁紙:WallpaperManager.FLAG_SYSTEM

壁紙是如何顯示的?


下面就對如上圖中的每一個過程作一個簡單的分析:

1.setStream
try {
        //sGlobals.mService即WallpaperManagerService
        ParcelFileDescriptor fd = sGlobals.mService.setWallpaper(null,
                mContext.getOpPackageName(), visibleCropHint, allowBackup,
                result, which, completion, UserHandle.myUserId());
        if (fd != null) {
            FileOutputStream fos = null;
            try {
                //將壁紙copy一份並存儲到對應目錄,默認是/data/system/users/0/wallpaper(或wallpaper_lock),其中0是主用戶的userId,支持多用戶
                fos = new ParcelFileDescriptor.AutoCloseOutputStream(fd);
                copyStreamToWallpaperFile(bitmapData, fos);
                fos.close();
                completion.waitForCompletion();
            } finally {
                IoUtils.closeQuietly(fos);
            }
        }
    } catch (RemoteException e) {
        throw e.rethrowFromSystemServer();
    }
}
2.WallpaperManagerService.java#setWallpaper
    @Override
    public ParcelFileDescriptor setWallpaper(String name, String callingPackage,
                                             Rect cropHint, boolean allowBackup, Bundle extras, int which,
                                             IWallpaperManagerCallback completion, int userId) {
 
        //檢查有沒有設置壁紙的權限
        checkPermission(android.Manifest.permission.SET_WALLPAPER);
        //調用setStream方法的時候參數which必須是正確的
        if ((which & (FLAG_LOCK|FLAG_SYSTEM)) == 0) {
            final String msg = "Must specify a valid wallpaper category to set";
            Slog.e(TAG, msg);
            throw new IllegalArgumentException(msg);
        }
 
        /* If we're setting system but not lock, and lock is currently sharing the system
          * wallpaper, we need to migrate that image over to being lock-only before
          * the caller here writes new bitmap data.
          */
        //若是當前沒有鎖屏壁紙的話,而且是設置桌面壁紙即which == FLAG_SYSTEM,那麼同時設置爲鎖屏壁紙
        if (which == FLAG_SYSTEM && mLockWallpaperMap.get(userId) == null) {
            if (DEBUG) {
                Slog.i(TAG, "Migrating system->lock to preserve");
            }
            migrateSystemToLockWallpaperLocked(userId);
        }
 
        ParcelFileDescriptor pfd = updateWallpaperBitmapLocked(name, wallpaper, extras);
    }

3.最主要的方法:WallpaperObserver#onEvent
WallpaperObserver是WallpaperManagerservice.java的內部類,它的主要職責是監聽文件變化,也就是壁紙對應的文件更新,看下源碼中關於它的註釋:

/**
     * Observes the wallpaper for changes and notifies all IWallpaperServiceCallbacks
     * that the wallpaper has changed. The CREATE is triggered when there is no
     * wallpaper set and is created for the first time. The CLOSE_WRITE is triggered
     * every time the wallpaper is changed.
     */
監聽wallpaper變化並通知IWallpaperServiceCallbacks,前文提到的LockscreenWallpaper就是繼承了

IWallpaperServiceCallbacks,並重寫了它的onWallppaerChanged方法,在這裏更新鎖屏壁紙的。

 @Override
    public void onEvent(int event, String path) {
        //若是是鎖屏壁紙更新
        if (moved && lockWallpaperChanged) {
            notifyLockWallpaperChanged();
            //android 8.0新增的一個變化,鎖屏包括下拉快捷的主題會根據當前的壁紙來變化,避免壁紙和鎖屏的圖標顏色一致致使的顯示不清問題,可是有一個缺陷就是:
            //獲取的是當前壁紙的主色調,而不是某個區域的主色調,這樣就會致使雖然主色調是白色,好比時間的區域是黑色,這一點小米作的比較好,它是根據當前區域的壁紙的主色調來進行反色的。
            notifyWallpaperColorsChanged(wallpaper, FLAG_LOCK);
            return;
        }
 
        if (sysWallpaperChanged || lockWallpaperChanged) {
            notifyCallbacksLocked(wallpaper);
 
        }
 
        if (sysWallpaperChanged) {
            //桌面壁紙變化,那麼bind ImageWallpaper,ImageWallpaper是負責顯示靜態桌面壁紙的
            // If this was the system wallpaper, rebind...
            bindWallpaperComponentLocked(mImageWallpaper, true,
                    false, wallpaper, null);
            notifyColorsWhich |= FLAG_SYSTEM;
        }
 
        if (lockWallpaperChanged
                || (wallpaper.whichPending & FLAG_LOCK) != 0) {
            if (DEBUG) {
                Slog.i(TAG, "Lock-relevant wallpaper changed");
            }
            // either a lock-only wallpaper commit or a system+lock event.
            // if it's system-plus-lock we need to wipe the lock bookkeeping;
            // we're falling back to displaying the system wallpaper there.
            //若是參數which是system+lock,也就是同時設置鎖屏和桌面壁紙,那麼remove鎖屏壁紙,由於已是同一張壁紙了
            if (!lockWallpaperChanged) {
                mLockWallpaperMap.remove(wallpaper.userId);
            }
            // and in any case, tell keyguard about it
            notifyLockWallpaperChanged();
            notifyColorsWhich |= FLAG_LOCK;
        }
 
    }
先看鎖屏壁紙更新這一部分:
void notifyLockWallpaperChanged() {
        final IWallpaperManagerCallback cb = mKeyguardListener;
        if (cb != null) {
            try {
                cb.onWallpaperChanged();
            } catch (RemoteException e) {
                // Oh well it went away; no big deal
            }
        }
    }
mKeyguardListener賦值的地方:

    @Override
    public boolean setLockWallpaperCallback(IWallpaperManagerCallback cb) {
        checkPermission(android.Manifest.permission.INTERNAL_SYSTEM_WINDOW);
        synchronized (mLock) {
            mKeyguardListener = cb;
        }
        return true;
    }
前面咱們說過LockscreenWallpaper.java是繼承了IWallpaperManagerCallback的,那麼setLockWallpaperCallback調用的地方應該是在這裏:

  public LockscreenWallpaper(Context ctx, PhoneStatusBar bar, Handler h) {
        
        mService = IWallpaperManager.Stub.asInterface(
                ServiceManager.getService(Context.WALLPAPER_SERVICE));
        mWallpaperManager = (WallpaperManager) ctx.getSystemService(Context.WALLPAPER_SERVICE);
        try {
            //在這裏給mKeyguardListener賦值的
            mService.setLockWallpaperCallback(this);
        } catch (RemoteException e) {
            Log.e(TAG, "System dead?" + e);
        }
    }
4.LockscreenWallpaper.java#onWallpaperChanged:
@Override
    public void onWallpaperChanged() {
        // Called on Binder thread.
        mH.removeCallbacks(this);
        mH.post(this);
    }
LockscreenWallpaper實現了Runnable接口的,因此看下它的run方法:
    @Override
    public void run() {
        // Called in response to onWallpaperChanged on the main thread.
        mLoader = new AsyncTask<Void, Void, LoaderResult>() {
            @Override
            protected LoaderResult doInBackground(Void... params) {
                return loadBitmap(currentUser, selectedUser);
            }
            @Override
            protected void onPostExecute(LoaderResult result) {
                super.onPostExecute(result);
                if (isCancelled()) {
                    return;
                }
                if (result.success) {
                    mCached = true;
                    mCache = result.bitmap;
                    mUpdateMonitor.setHasLockscreenWallpaper(result.bitmap != null);
                    //通知StatsuBar更新壁紙
                    mBar.updateMediaMetaData(
                            true /* metaDataChanged */, true /* allowEnterAnimation */);
                }
                mLoader = null;
            }
        }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
    }
異步獲取壁紙,並通知StatusBar去更新壁紙。
5.StatusBar.java#updateMediaMetaData:
這裏主要就是對鎖屏壁紙所在的View作最基本的setImageBitmap。

6.再看桌面壁紙部分:

6.1 bindWallpaperComponentLocked
if (sysWallpaperChanged) {
     // If this was the system wallpaper, rebind...
   bindWallpaperComponentLocked(mImageWallpaper, true,
                          false, wallpaper, null);
    notifyColorsWhich |= FLAG_SYSTEM;
  }

mImageWallpaper = ComponentName.unflattenFromString(
                context.getResources().getString(R.string.image_wallpaper_component));
也就是一開始提到的:

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

 boolean bindWallpaperComponentLocked(ComponentName componentName, boolean force,
                                         boolean fromUser, WallpaperData wallpaper, IRemoteCallback reply) {
 
 
        Intent intent = new Intent(WallpaperService.SERVICE_INTERFACE);
 
 
        WallpaperConnection newConn = new WallpaperConnection(wi, wallpaper);
        //componentName就是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)));
    }
ImageWallpaper繼承了Service,既然是bindService,那麼主要看下conn,也就是WallpaperConnection。
它的onServiceConnected方法:
   @Override
    public 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.userId);
                FgThread.getHandler().removeCallbacks(mResetRunnable);
            }
        }
    }
6.2 繼續看attachServcieLocked方法:
void attachServiceLocked(WallpaperConnection conn, WallpaperData wallpaper) {
        try {
            conn.mService.attach(conn, conn.mToken,
                    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);
            }
        }
   }
conn.mService.attach是調用了IWallpaperServiceWrapper 的attach方法,IWallpaperServiceWrapper 繼承了
IWallpaperService.Stub。
       @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);
        }
在看它的構造方法,發送了一個DO_ATTACH的消息:

Message msg = mCaller.obtainMessage(DO_ATTACH);
mCaller.sendMessage(msg);
                 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;
                }
onCreateEngine也是一個抽象的方法:

   /**
     * Must be implemented to return a new instance of the wallpaper's engine.
     * Note that multiple instances may be active at the same time, such as
     * when the wallpaper is currently set as the active wallpaper and the user
     * is in the wallpaper picker viewing a preview of it as well.
     */
    public abstract Engine onCreateEngine();
實現的地方仍然是在ImageWallpaper.java裏

   @Override
    public Engine onCreateEngine() {
        mEngine = new DrawableEngine();
        return mEngine;
    }
DrawableEngine是自定義的繼承Engine的內部類

最後調用engine.attach方法。

WallpaperService.java的attach方法:

void attach(IWallpaperEngineWrapper wrapper) {
      onCreate(mSurfaceHolder);
 
      mInitializing = false;
      mReportedVisible = false;
      updateSurface(false, false, false);
 }

6.3 onCreate(mSurfaceHolder)
它是一個抽象方法

       /**
         * Called once to initialize the engine.  After returning, the
         * engine's surface will be created by the framework.
         */
        public void onCreate(SurfaceHolder surfaceHolder) {
        }
它是一個抽象方法,那麼真正的實現是在它的子類,也就是ImageWallpaper.java裏
       @Override
        public void onCreate(SurfaceHolder surfaceHolder) {
            if (DEBUG) {
                Log.d(TAG, "onCreate");
            }
            super.onCreate(surfaceHolder);
            mDefaultDisplay = getSystemService(WindowManager.class).getDefaultDisplay();
            setOffsetNotificationsEnabled(false);
            updateSurfaceSize(surfaceHolder, getDefaultDisplayInfo(), false /* forDraw */);
        }
surfaceHolder是在父類裏初始化的,surfaceHolder = new BaseSurfaceHolder();

6.4 updateSurfaceSize 
在這裏主要是繼續調用loadWallpaper方法去解析壁紙並最終繪製到surfaceHolder上。
6.5 drawFrame
對壁紙進行一些裁剪操做,根據是否支持硬件加速來決定繪製的方法:

    //支持硬件加速
    if (mIsHwAccelerated) {
        if (!drawWallpaperWithOpenGL(sh, availw, availh, xPixels, yPixels)) {
            drawWallpaperWithCanvas(sh, availw, availh, xPixels, yPixels);
        }
    } else {
        drawWallpaperWithCanvas(sh, availw, availh, xPixels, yPixels);
        if (FIXED_SIZED_SURFACE) {
            // 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 rotation
            unloadWallpaper(false /* forgetSize */);
        }
    }
到這裏,把壁紙的設置的簡單過程基本上就講完了,做爲筆記作一個記錄。

若有錯誤的地方,歡迎指正。

相關文章
相關標籤/搜索
本站公眾號
   歡迎關注本站公眾號,獲取更多信息