android壁纸服务流程浅析

时间:2022-01-24 18:34:30

      由于最近工作需要了解android的壁纸机制,当时急切地想在网上找点资料来了解WallpaperManager.setResource()之后的流程,但网上仅有一点不全的东西,其它的全是粘贴复制那点不全的内容,真是捉急。今天自己来写下关于设置壁纸的流程,希望后来者不用像本人一样找不到能用的资料。

      假设调用WallpaperManager.setResource()方法来设置壁纸(还有wallpaperManager的其它方法也可以设置壁纸,但流程是一样的),

public void setResource(int resid) throws IOException {
        if (sGlobals.mService == null) {
            Log.w(TAG, "WallpaperService not running");
            return;
        }
        try {
            Resources resources = mContext.getResources();
            /* Set the wallpaper to the default values */
            ParcelFileDescriptor fd = sGlobals.mService.setWallpaper(
                    "res:" + resources.getResourceName(resid));
            if (fd != null) {
                FileOutputStream fos = null;
                try {
                    fos = new ParcelFileDescriptor.AutoCloseOutputStream(fd);
                    setWallpaper(resources.openRawResource(resid), fos);		
                } finally {
                    if (fos != null) {
                        fos.close();
                    }
                }
            }
        } catch (RemoteException e) {
        }
    }

      代码里牵涉到sGlobals.mService,这个mService是WallpaperManagerService的实例对象,它是通过下面这两句话来初始化的。

            IBinder b = ServiceManager.getService(Context.WALLPAPER_SERVICE);
            mService = IWallpaperManager.Stub.asInterface(b);

      可以去查看WallpaperManagerService的代码,发现WallpaperManagerService正是实现了IWallpaperManager.Stub,而ServiceManager中正是以关键字Context.WALLPAPER_SERVICE保存的WallpaperManagerService实例,由以上两点可以得知mService正是WallpaperManagerService的实例对象。WallpaperManagerService的setWallpaper其实主要作用是取得一个ParcelFileDescriptor对象,这个对象指向了/data/system/user/0/wallpaper这个文件,接着根据ParcelFileDescriptor生成文件输出流fos,再调用resources.openRawResource(resid)获得源壁纸的文件输入流,传入WallpaperManager的下一个方法,setWallpaper(InputStream,FileOutputStream)。
     而在这个方法中主要代码为

       while ((amt=data.read(buffer)) > 0) {
            fos.write(buffer, 0, amt);
	   if (mSaveBakFlag && null != fos_bak) {
		fos_bak.write(buffer, 0, amt);
	   }
      }

      到了这里可能有读者纳闷了,这不是文件的复制吗?对,WallpaperManager.setResource方法到最后就是进行文件复制而已,把源壁纸图片复制到之前曾经提到过的
/data/system/user/0/wallpaper这个文件中(可能不同的厂商做的定制化rom文件路径会有不一样,但大体相同),那接下来如何在桌面绘制所设壁纸呢?这就与之前提到的WallpaperManagerService和另一个新类ImageWallpaper有关。(如何有读者对ParcelFileDescriptor类不大理解,可以不求甚解把它当成一个类似于File的类就可以了)

 

     WallpaperManagerService中含有一个WallpaperObserver内部类,这个内部类继承自FileObserver,FileObserver主要用于对文件进行监听,如果被监听的文件被重写了或删除或其它操作它都能监听并触发相应的方法。

    

public WallpaperObserver(WallpaperData wallpaper) {
            super(getWallpaperDir(wallpaper.userId).getAbsolutePath(),
                    CLOSE_WRITE | DELETE | DELETE_SELF);
            mWallpaperDir = getWallpaperDir(wallpaper.userId);
            mWallpaper = wallpaper;
            mWallpaperFile = new File(mWallpaperDir, WALLPAPER);
        }

      WallpaperObserver在构造方法中指明了它要监听的文件及对文件的操作,

      super(getWallpaperDir(wallpaper.userId).getAbsolutePath(),CLOSE_WRITE | DELETE | DELETE_SELF);

      getWallpaperDir(wallpaper.userId).getAbsolutePath()是它是监听的文件路径/data/system/user/0/,这个路径包含wallpaperManager复制的wallpaper文件,所以一旦通过wallpaperManager设置壁纸,文件监听器WallpaperObserver就会监听到关执行相应方法。

     

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(mWallpaperDir, path);
                if (mWallpaperFile.equals(changedFile)) {
                    notifyCallbacksLocked(mWallpaper);
                    if (mWallpaper.wallpaperComponent == null || event != CLOSE_WRITE
                            || mWallpaper.imageWallpaperPending) {
                        if (event == CLOSE_WRITE) {
                            mWallpaper.imageWallpaperPending = false;
                        }
                        bindWallpaperComponentLocked(mWallpaper.imageWallpaperComponent, true,
                                false, mWallpaper);
                        saveSettingsLocked(mWallpaper);
                    }
                }
            }
        }


        在bindWallpaperComponentLocked方法中传入的第一个参数是mWallpaer.imageWallpaperComponent,这个Component指向ImageWallpaper,这个方法太过复杂,关键代码在于

       mContext.bindService(intent, newConn, Context.BIND_AUTO_CREATE, serviceUserId)这句话的执行,

       intent:Intent intent = new Intent(WallpaperService.SERVICE_INTERFACE);               

                     intent.setComponent(componentName);  componentName即是之前传入的imageWallpaperComponent,表明这个intent最终会被解析定位到ImageWallpaper这个类中,ImageWallpaper继承了WallpaperService。接下来看newConn的代码

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

注意这句话,mService = IWallpaperService.Stub.asInterface(service),而wallpaperService中的内部类IWallpaperServiceWrapper实现了IWallpaperService.stub,

                                 class IWallpaperServiceWrapper extends IWallpaperService.Stub

所以根据之前的intent和IWallpaperService.Stub可知最终bindService会得到一个WallpaperService中的内部类IWallpaerServiceWrapper的引用(注意到但WallpaperService是一个abstract类,而ImageWallpaper是WallpaerService的子类,所以最终是得到的是ImageWallpaper中IWallpaerServiceWrapper的引用),看上面的onServiceConnected方法,主要执行两个方法。attachServiceLocked           saveSettingsLocked。

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

attachServiceLocked方法将执行mService中的attach方法,转到WallpaperService查看attach方法,发现此方法将生成一个IWallpaperEngineWrapper对象(之前说bindService将定位到ImageWallpaper,怎么现在去WallpaperService去查看代码呢?因为ImageWallaper中没有attach方法,自然只能去其父类中查找相关的方法),在
IWallpaperEngineWrapper构造方法中将发送一个message

         Message msg = mCaller.obtainMessage(DO_ATTACH);
            mCaller.sendMessage(msg);

在message的处理代码中将创建绘图引擎并调用 engine.attach(this),继续追查engine.attach方法(注意ImageWallpaper中有内部类继承Engine类),发现在些方法中会调用Engine.onCreate(mSurfaceHolder),查看ImageWallpaper中的onCreate(mSurfaceHolder),接下来有一系列的逻辑来进行壁纸的绘制工作。

 

     至些,文章里边提到了四个类,每个类都已经联系起来了。接着看ImageWallaper的绘图工作吧。

     在onCreate(SurfaceHolder)方法中执行updateSurfaceSize方法,用于更新surface的大小,在updateSurfaceSize方法中调用WallpaperManager.isUseSingleWallPaper()方法确定是绘制单屏壁纸还是宽屏壁纸,updateSurfaceSize方法执行完后,onSurfaceChanged这个回调函数自动执行,在这个回调函数中再执行drawFrameLocked方法,在drawFrameLocked中就是真正绘制地方,在drawFrameLocked方法中将首先获取设置好的壁纸图片,再由是否执行硬件加速来决定是用opengl绘图还是用canvas绘图。至此,设置壁纸的整个流程已然介绍完毕。

      由于本人对于绘图这一块还不是很熟悉,所以在ImageWallpaper的绘图工作介绍的不是很详细,如有哪位清楚的欢迎补充。