Android壁纸管理(Android N)


初识Android壁纸

        本章将对壁纸的实现原理进行讨论。在Android中,壁纸分为静态与动态两种。静态壁纸是一张图片,而动态壁纸则以动画为表现形式,或者可以对用户的操作作出反应。这两种形式看似差异很大,其实二者的本质是统一的。它们都以一个Service的形式运行在系统后台,并在一个类型为TYPE_WALLPAPER的窗口上绘制内容。进一步讲,静态壁纸是一种特殊的动态壁纸,它仅在窗口上渲染一张图片,并且不会对用户的操作作出反应。因此本章将首先通过动态壁纸的实现讨论Android壁纸的实现与管理原理,然后在对静态壁纸的实现做介绍。

        Android壁纸的实现与管理分为三个层次:

  • WallpaperService与Engine。同SystemUI一样,壁纸运行在一个Android服务之中,这个服务的名字叫做WallpaperService。当用户选择了一个壁纸之后,此壁纸所对应的WallpaperService便会启动并开始进行壁纸的绘制工作,因此继承并定制WallpaperService是开发者进行壁纸开发的第一步。Engine是WallpaperService中的一个内部类,实现了壁纸窗口的创建以及Surface的维护工作。另外,Engine提供了可供子类重写的一系列回调,用于通知壁纸开发者关于壁纸的生命周期、Surface状态的变化以及对用户的输入事件进行响应。可以说,Engine类是壁纸实现的核心所在。壁纸开发者需要继承Engine类,并重写其提供的回调以完成壁纸的开发。这一层次的内容主要体现了壁纸的实现原理。

  • WallpaperManagerService,这个系统服务用于管理壁纸的运行与切换,并通过WallpaperManager类向外界提供操作壁纸的接口。当通过WallpaperManagaer的接口进行壁纸的切换时,WallpaperManagerService会取消当前壁纸的WallpaperService的绑定,并启动新壁纸的WallpaperService。另外,Engine类进行窗口创建时所使用的窗口令牌也是由WallpaperManagerService提供的。这一层次主要体现了Android对壁纸的管理方式。

  • WindowManagerService,用于计算壁纸窗口的Z序、可见性以及为壁纸应用窗口动画。壁纸窗口(TYPE_WALLPAPER)的Z序计算不同于其他类型的窗口。其他窗口依照其类型会有固定的mBaseLayer以及mSubLayer,并结合它们所属的Activity的顺序或创建顺序进行Z序的计算,因此这些窗口的Z序相对固定。而壁纸窗口则不然,它的Z序会根据FLAG_SHOW_WALLPAPER标记在其它窗口的LayoutParams.flags中的存在情况而不断地被调整。这一层次主要体现了Android对壁纸窗口的管理方式。

深入理解动态壁纸

1、启动动态壁纸的方法

        启动动态壁纸可以通过调用WallpaperManager.getIWallpaperManager().setWallpaperComponent()方法完成。它接受一个ComponentName类型的参数,用于将希望启动的壁纸的WallpaperService的ComponentName告知WallpaperManagerService。WallpaperManager.getIWallpaperManager()方法返回的是WallpaperManagerService的Bp端。因此setWallpaperComponent()方法的实现位于WallpaperManagerService之中。
    @Override
    public void setWallpaperComponent(ComponentName name) {
        checkPermission(android.Manifest.permission.SET_WALLPAPER_COMPONENT);//设置动态壁纸需要调用者拥有的权限
        synchronized (mLock) {
            if (DEBUG) Slog.v(TAG, "setWallpaperComponent name=" + name);
            int userId = UserHandle.getCallingUserId();
            WallpaperData wallpaper = mWallpaperMap.get(userId);//首先从mWallpaperMap中获取壁纸的运行信息WallpaperData。
            if (wallpaper == null) {
                throw new IllegalStateException("Wallpaper not yet initialized for user " + userId);
            }
            final long ident = Binder.clearCallingIdentity();
            try {
                wallpaper.imageWallpaperPending = false;
                if (bindWallpaperComponentLocked(name, false, true, wallpaper, null)) {//启动新壁纸的WallpaperService
                    wallpaper.wallpaperId = makeWallpaperIdLocked();
                    notifyCallbacksLocked(wallpaper);
                }
            } finally {
                Binder.restoreCallingIdentity(ident);
            }
        }
    }
WallpaperManagerService支持多用户机制,因此设备上的每个用户都可以设置自己的壁纸。mWallpaperMap为每个用户保存了一个WallpaperData实例,这个实例中保存和壁纸运行状态相关的信息。例如WallpaperService的ComponentName,到WallpaperService的ServiceConnection等。于是当发送用户切换时,WallpaperManagerService可以从mWallpaperMap中获取新用户的WallpaperData,并通过保存在其中的ComponentName重新启动该用户所设置的壁纸。因此,当通过setWallpaperComponent方法设置新壁纸时,需要获取当前用户的WallpaperData,并在随后更新其内容使之保存新壁纸的信息。
注意,WallpaperManager.getIWallpaperManager()并没有作为SDK提高给开发者,因此第三方应用程序不能进行动态壁纸的设置。

2、壁纸服务的启动原理

壁纸服务的验证与启动

        bindWallpaperComponentLocked()方法将会启动由ComponentName所指定的WallpaperService,并向WMS申请用于添加壁纸窗口的窗口令牌。不过在此之前,bindWallpaperComponentLocked()会对ComponentName所描述的Service进行一系列的验证,以确保它是一个壁纸服务。而这一系列的验证过程体现了一个Android服务可以被当作壁纸必要的条件。
    boolean bindWallpaperComponentLocked(ComponentName componentName, boolean force,
            boolean fromUser, WallpaperData wallpaper, IRemoteCallback reply) {
        if (DEBUG) Slog.v(TAG, "bindWallpaperComponentLocked: componentName=" + componentName);
        // Has the component changed?
        if (!force) {
            if (wallpaper.connection != null) {
                if (wallpaper.wallpaperComponent == null) {
                    if (componentName == null) {
                        if (DEBUG) Slog.v(TAG, "bindWallpaperComponentLocked: still using default");
                        // Still using default wallpaper.
                        return true;
                    }
                } else if (wallpaper.wallpaperComponent.equals(componentName)) {
                    // Changing to same wallpaper.
                    if (DEBUG) Slog.v(TAG, "same wallpaper");
                    return true;
                }
            }
        }

        try {
            if (componentName == null) {//当componentName为null时,使用默认壁纸。
                // 这里将componentName改为默认壁纸的componentName
                componentName = WallpaperManager.getDefaultWallpaperComponent(mContext);
                if (componentName == null) {//如果没有找到默认壁纸,则使用ImageWallpaper(静态壁纸)代替默认壁纸
                    // Fall back to static image wallpaper
                    componentName = mImageWallpaper;
                    //clearWallpaperComponentLocked();
                    //return;
                    if (DEBUG) Slog.v(TAG, "Using image wallpaper");
                }
            }
            // WallpaperManagerService从PackageManager中获取ComponentName指定的Service信息,
            // 获取此信息的目的在于确认该Service是一个服务要求的壁纸服务.
            int serviceUserId = wallpaper.userId;
            ServiceInfo si = mIPackageManager.getServiceInfo(componentName,
                    PackageManager.GET_META_DATA | PackageManager.GET_PERMISSIONS, serviceUserId);
            if (si == null) {
                // The wallpaper component we're trying to use doesn't exist
                Slog.w(TAG, "Attempted wallpaper " + componentName + " is unavailable");
                return false;
            }
            // 首先,要求这个service必须声明其访问权限BIND_WALLPAPER,用于防止壁纸服务被第三方应用程序启动而产生混乱
            if (!android.Manifest.permission.BIND_WALLPAPER.equals(si.permission)) {
                String msg = "Selected service does not require "
                        + android.Manifest.permission.BIND_WALLPAPER
                        + ": " + componentName;
                if (fromUser) {
                    throw new SecurityException(msg);
                }
                Slog.w(TAG, msg);
                return false;
            }

            WallpaperInfo wi = null;

            // 其次,要求这个service必须可以处理android.service.wallpaper.WallpaperService这个Action。
            Intent intent = new Intent(WallpaperService.SERVICE_INTERFACE);
            if (componentName != null && !componentName.equals(mImageWallpaper)) {
                // Make sure the selected service is actually a wallpaper service.
                // 获取所有可以处理andriod.service.wallpaper.WallpaperService的服务信息
                List<ResolveInfo> ris =
                        mIPackageManager.queryIntentServices(intent,
                                intent.resolveTypeIfNeeded(mContext.getContentResolver()),
                                PackageManager.GET_META_DATA, serviceUserId).getList();
                // 再次,要求这个service必须在其meta-data中提供关于壁纸的描述信息。
                // 如果即将启动的服务位于查询结果中,便可以确定这是一个壁纸服务。此时会创建一个WallpaperInfo对象,以解析并存储此壁纸服务的描述信息。
                for (int i=0; i<ris.size(); i++) {
                    ServiceInfo rsi = ris.get(i).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;
                    }
                }
                // WallpaperInfo为null,表示即将启动的服务没有位于查询结果中,或者没有提供必要的meta-data,绑定失败。
                if (wi == null) {
                    String msg = "Selected service is not a wallpaper: "
                            + componentName;
                    if (fromUser) {
                        throw new SecurityException(msg);
                    }
                    Slog.w(TAG, msg);
                    return false;
                }
            }

            // Bind the service!
            if (DEBUG) Slog.v(TAG, "Binding to:" + componentName);
            // 创建一个WallpaperConnection。
            // WallpaperConnection不仅实现ServiceConnection的接口,用于监听和WallpaperService的连接状态,
            // 同时还实现IWallpaperConnection.Stub,也就是支持跨进程通信。
            // 在服务绑定成功后的WallpaperConnection.onServiceConnected()方法调用中,
            // WallpaperConnection的实例会被发送给WallpaperService,使其作为WallpaperService
            // 向WallpaperManagerService进行通信的桥梁.
            WallpaperConnection newConn = new WallpaperConnection(wi, wallpaper);
            // 为启动壁纸服务准备intent
            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)));
            // 启动指定的壁纸服务。当服务启动完成后,剩下的启动流程会在WallpaperConnection.onServiceConnected()中继续
            if (!mContext.bindServiceAsUser(intent, newConn,
                    Context.BIND_AUTO_CREATE | Context.BIND_SHOWING_UI
                            | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE,
                    new UserHandle(serviceUserId))) {
                String msg = "Unable to bind service: "
                        + componentName;
                if (fromUser) {
                    throw new IllegalArgumentException(msg);
                }
                Slog.w(TAG, msg);
                return false;
            }
            // 新的壁纸服务启动成功后,通过detachWallpaperLocked()方法销毁旧的壁纸服务
            if (wallpaper.userId == mCurrentUserId && mLastWallpaper != null) {
                detachWallpaperLocked(mLastWallpaper);
            }
            // 将新的壁纸服务的运行信息保存到WallpaperData中。
            wallpaper.wallpaperComponent = componentName;
            wallpaper.connection = newConn;
            newConn.mReply = reply;
            // 最后向WMS申请注册一个WALLPAPER类型的窗口令牌。
            // 这个令牌会在onServiceConnected()方法之后被传递给WallpaperService用作后者添加窗口的通行证。
            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;
    }

可见WallpaperManagerService要求被启动的目标Service必须满足以下三个条件:

  • 该服务必须要以android.permission.BIND_WALLPAPER作为其访问权限。壁纸虽然是一个标准的Android服务,但是通过其他途径(如第三方应用程序)启动壁纸所在的服务是没有意义的。因此Android要求作为壁纸的Service必须使用这个签名级的系统权限进行访问限制,以免被意外的应用程序启动。

  • 该服务必须被声明为可以处理android.service.wallpaper.WallpaperService这个Action。WallpaperManagerService会使用这个Action对此服务进行绑定。

  • 该服务必须在其AndroidManifest.xml中提供一个名为android.service.wallpaper的meta-data,用于提供动态壁纸的开发者、缩略图与描述文字。

一旦目标服务满足了上述条件,WallpaperManagerService就会着手进行目标服务的启动与绑定。

bindWallpaperComponentLocked()主要做了如下几件事情:

  • 创建WallpaperConnection。由于实现了ServiceConnection接口,因此它将负责监听WallpaperManagerService与壁纸服务之间的连接状态。另外由于继承了IWallpaperConnection.Stub,因此它具有跨进程通信的能力。在壁纸服务绑定成功后,WallpaperConnection实例会被传递给壁纸服务作为壁纸服务与WallpaperManagerService进行通信的桥梁。

  • 启动壁纸服务。通过Context.bindServiceAsUser()方法完成。可见启动壁纸服务与启动一个普通的服务没有什么区别。

  • 终止旧有的壁纸服务:detachWallpaperLocked()。

  • 将属于当前壁纸的WallpaperConnection实例、componentName机器启动时间戳保存到WallpaperData中。

  • 向WMS注册WALLPAPER类型的窗口令牌。这个窗口令牌保存在WallpaperConnection.mToken中,并随着WallpaperConnection的创建而创建。

仅仅将指定的壁纸服务启动起来尚无法使得壁纸得以显示,因为新启动起来的壁纸服务由于没有可用的窗口令牌而导致其无法添加窗口。WallpaperManagerService必须通过某种方法将窗口令牌交给壁纸服务才行。所以壁纸显示的后半部分的流程将在WallpaperConnection.onServiceConnected()回调中继续。同其他服务一样,WallpaperManagerService会在这个回调之中获得一个Binder对象。因此在进行onServiceConnected()方法的讨论之前,必须了解WallpaperManagerService在这个回调中将会得到一个什么样的Binder对象。

现在把分析目标转移到WallpaperService中。和普通服务一样,WallpaperService的启动也会经历onCreate()、onBind()这样的生命周期回调。为了了解WallpaperManagerService可以从onServiceConnected()获取怎样的Binder对象,需要看下WallpaperService.onBind()的实现:

    /**
     * Implement to return the implementation of the internal accessibility
     * service interface.  Subclasses should not override.
     */
    @Override
    public final IBinder onBind(Intent intent) {
        return new IWallpaperServiceWrapper(this);
    }
onBind()新建了一个IWallpaperServiceWrapper实例,并将其返回给WallpaperManagerService。
	IWallpaperServiceWrapper类继承自IWallpaperService.Stub。它保存了WallpaperService的实例,同时也实现了唯一的一个接口attach()。很显然,当这个Binder对象返回给WallpaperManagerService之后,后者定会调用这个唯一的接口attach()以传递显示壁纸所必须的包括窗口令牌在内的一系列的参数。

向壁纸服务传递创建窗口所需的信息

    当WallpaperService创建IWallpaperServiceWrapper实例并返回后,WallpaperManagerService将在WallpaperConnection的onServiceConnected()方法中收到回调。如下:
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            synchronized (mLock) {
                if (mWallpaper.connection == this) {
                    mService = IWallpaperService.Stub.asInterface(service);//将WallpaperService传回的IWallpaperService接口保存为没Service。
                    attachServiceLocked(this, mWallpaper);//绑定壁纸服务。attachServiceLocked()会调用IWallpaperService的attach()方法以传递壁纸服务创建窗口所需的信息。
                    // 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);//保存当前壁纸的运行状态到文件系统中,以便在系统重启或发生用户切换时可以恢复。
                }
            }
        }
    进一步,attachServiceLocked()方法会调用IWallpaperService的attach()方法,将创建壁纸窗口所需的信息传递给壁纸服务。
    void attachServiceLocked(WallpaperConnection conn, WallpaperData wallpaper) {
        try {
            conn.mService.attach(conn, conn.mToken,
                    WindowManager.LayoutParams.TYPE_WALLPAPER, false,
                    wallpaper.width, wallpaper.height, wallpaper.padding);//调用IWallpaperService的唯一接口attach(),将创建壁纸窗口所需要信息传递个WallpaperService。
        } catch (RemoteException e) {
            Slog.w(TAG, "Failed attaching wallpaper; clearing", e);
            if (!wallpaper.wallpaperUpdating) {
                bindWallpaperComponentLocked(null, false, false, wallpaper, null);
            }
        }
    }
    attach()方法的参数比较,我们看下他们的意义:
  • conn即WallpaperConnection,WallpaperService将通过它向WallpaperManagerService进行通信。WallpaperConnection继承自IWallpaperConnection,提供了三个接口的定义,即attachEngine()以及engineShown()和setWallpaper()。虽说WallpaperManager是WallpaperManagerService向外界提供的标准接口,但是这里仍然选择使用WallpaperConnection。实现这两个接口的原因是由于attachEngine()以及engineShown()是只有WallpaperService才需要用到,而且是它与 WallpaperManagerService之间比较底层且私密的交流,将它们的实现放在通用的接口WallpaperManager中显然并不合适。Engine类是实现壁纸的核心所在,而WallpaperService只是一个用于承载壁纸的运行的容器而已。因此相对于WallpaperService,Engine是WallpaperManagerService更加关心的对象。所以当WallpaperService完成了Engine对象的创建之后,就会通过attachEngine()方法将Engine对象的引用交给WallpaperManagerService。
  • windowToken就是在bindWallpaperComponent()方法中向WMS注册过的窗口令牌。是WallpaperService有权添加壁纸窗口的凭证。
  • windowType,指明了WallpaperService需要添加TYPE_WALLPAPER类型的窗口。壁纸除了是TYPE_WALLPAPER类型以外难道还有其他的可能么?的确在实际的壁纸显示中WallpaperService必然需要使用TYPE_WALLPAPER类型添加窗口。但是有一个例外,即壁纸预览。在LivePicker应用中选择一个动态壁纸时,首先会使得用户对选定的壁纸进行预览。这一预览并不是真的将壁纸设置给了WallpaperManagerService,而是LivePicker应用自行启动了对应的壁纸服务,并要求壁纸服务使用TYPE_APPLICATION_MEDIA_OVERLAY类型创建窗口。这样一来,壁纸服务所创建的窗口将会以子窗口的形式衬在LivePicker的窗口之下,从而实现了动态壁纸的预览。
  • isPreview,用以指示启动壁纸服务的意图。当被实际用作壁纸时取值为false,而作为预览时则为true。仅当LivePicker对壁纸进行预览时才会使用true作为isPreview的取值。壁纸服务可以根据这一参数的取值对自己的行为作出调整。
    当WallpaperManagerService向WallpaperService提供了用于创建壁纸窗口的足够的信息之后,WallpaperService便可以开始着手进行Engine对象的创建了。

Engine对象创建

    调用IWallpaperService.attach()是WallpaperManagerService在壁纸服务启动后第一次与壁纸服务进行联系。我们看下调用WallpaperService的attach()方法,如下:
[WallpaperService.java-->IWallpaperServiceWrapper.attach()]
        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);//使用WallpaperManagerService提供的参数构建一个IWallpaperEngineWrapper对象。
        }
    }

    顾名思义,在attach()方法中所创建的IWallpaperEngineWrapper将会创建并封装Engine实例。IWallpaperEngineWrapper继承自IWallpaperEngine.Stub,因此它也支持跨Binder调用。在随后的代码分析中可知,它将会被传递给WallpaperManagerService,作为WallpaperManagerService与Engine进行通信的桥梁。

    另外需要注意的是,attach()方法的实现非常奇怪,它直接创建一个实例但是并没有将这个实例赋值给某一个成员变量,在attach()方法结束时岂不是会被垃圾回收?不难想到,在IWallpaperEngineWrapper的构造函数中一定有些动作可以使得这个实例不被释放。代码如下:

       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);//创建一个HandlerCaller对象
            mConnection = conn;//保存WallpaperManagerService提供的参数
            mWindowToken = windowToken;
            mWindowType = windowType;
            mIsPreview = isPreview;
            mReqWidth = reqWidth;
            mReqHeight = reqHeight;
            mDisplayPadding.set(padding);
            
            Message msg = mCaller.obtainMessage(DO_ATTACH);//发送DO_ATTACH消息
            mCaller.sendMessage(msg);
        }

     
    HandlerCaller是Handler的一个封装,而它与Handler的区别是额外提供了一个executeOrSendMessage()方法。当开发者在HandlerCaller所在的线程 
    执行此方法时会使得消息的处理函数立刻得到执行,在其他线程中执行此方法的效果 
    则与Handler.sendMessage()别无二致。 
   
    在这里所创建的mCaller具有十分重要的地位。它是一个重要的线程调度器,所有壁纸相关的操作都会以消息的形式发送给mCaller,然后在IWallpaperEngineWrapper的executeMessage()方法中得到处理,从而这些操作转移到mCaller所在的线程上进行(如壁纸绘制、事件处理等)。可以说mCaller的线程就是壁纸的工作线程。
    继续分析DO_ATTACH消息的处理,如下:
        public void executeMessage(Message message) {
            switch (message.what) {
                case DO_ATTACH: {
                    try {
                        mConnection.attachEngine(this);//把IWallpaperEngineWrapper实例传递给WallpaperConnection进行保存
                    } catch (RemoteException e) {
                        Log.w(TAG, "Wallpaper host disappeared", e);
                        return;
                    }
                    Engine engine = onCreateEngine();//onCreateEngine()方法创建一个Engine,在WallpaperService中是一个抽象方法,用户可自行返回一个自定义的Engine子类。
                    mEngine = engine;
                    mActiveEngines.add(engine);//将新建的Engine添加到WallpaperService的mActiveEngines列表中。
                    engine.attach(this);//engine.attach()将会完成窗口的创建、第一帧的绘制等工作。
                    return;
                }
    正如前文所述,作为拥有跨Binder调用的IWallpaperEngineWrapper通过attachEngine()方法将自己传递给了WallpaperConnection,后者将其保存在WallpaperConnection.mEngine成员之中。从此之后,WallpaperManagerService便可以通过WallpaperConnection.mEngine与壁纸服务进程中的IWallpaperEngineWrapper进行通信,而IWallpaperEngineWrapper进一步将来自WallpaperManagerService中的请求或设置转发给Engine对象,从而实现了WallpaperManagerService对壁纸的控制。

    到目前为止,WallpaperManagerService与壁纸服务之间已经出现了三个用于跨Binder通信的对象。它们分别是:

  • IWallpaperService,实现在壁纸服务进程之中,它所提供的唯一的方法attach()用于在壁纸服务启动后接收窗口创建所需的信息,或者说为了完成壁纸的初始化工作。除此之外IWallpaperService不负责任何功能,WallpaperManagerService对壁纸进行的请求与设置都交由在attach()的过程中所创建的IWallpaperEngineWrapper实例完成。

  • WallpaperConnection,实现在WallpaperManagerService中,并通过IWallpaperService.attach()方法传递给了IWallpaperEngineWrapper。壁纸服务通过WallpaperConnection的attachEngine()方法将IWallpaperEngineWrapper实例传递给WallpaperManagerService进行保存。另外壁纸服务还通过它的engineShown()方法将壁纸显示完成的事件通知给WallpaperManagerService。

  • IWallpaperEngineWrapper,实现在壁纸进程中。Engine实例是壁纸实现的核心所在。作为Engine实例的封装者,它是WallpaperManagerService对Engine进行请求或设置的唯一接口。

    总体来说,IWallpaperService与WallpaperConnection主要服务于壁纸的创建阶段,而IWallpaperEngineWrapper则用于在壁纸的运行阶段对Engine进行操作与设置。
    Engine创建完毕之后会通过Engine.attach()方法完成Engine的初始化工作,代码如下:
       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();//mSurfaceHolder是一个BaseSurfaceHolder类型的内部类的实例。Engine对其进行了简单的定制。开发者可以通过mSurfaceHolder定制所需要的Surface类型

            mInitializing = true;
            mSession = WindowManagerGlobal.getWindowSession();//获取WindowSession,用于与WMS通信
            
            mWindow.setSession(mSession);//窗口创建之后,用于接收来自WMS的回调

            mLayout.packageName = getPackageName();

            mDisplayManager = (DisplayManager)getSystemService(Context.DISPLAY_SERVICE);
            mDisplayManager.registerDisplayListener(mDisplayListener, mCaller.getHandler());
            mDisplay = mDisplayManager.getDisplay(Display.DEFAULT_DISPLAY);
            mDisplayState = mDisplay.getState();

            if (DEBUG) Log.v(TAG, "onCreate(): " + this);
            onCreate(mSurfaceHolder);//用Engine.onCreate()方法,Engine的子类往往需要重写此方法以修改mSurfaceHolder的属性,如像素格式,尺寸等。注意此时尚未创建窗口,在这里所设置的SurfaceHolder的属性将会在创建窗口时生效。

            mInitializing = false;
            mReportedVisible = false;
            updateSurface(false, false, false);//最后updateSurface,将会根据SurfaceHolder的属性创建窗口以及Surface,并进行壁纸的第一次绘制

        }
    Engine.attach()方法执行的结束标志着壁纸启动工作的完成,至此在最后的updateSurface()方法结束之后新的壁纸便显示出来了。

壁纸的创建流程

    可见,壁纸的创建过程比较复杂。在这个过程中存在着多个Binder对象之间的互相调用。因此有必要对此过程进行一个简单的整理:
  • 首先,壁纸管理程序(如LivePicker)调用IWallpaperManager.setWallpaperComponent()要求WallpaperManagerService设置指定的壁纸

  • WallpaperManagerService通过调用bindWallpaperComponentLocked()将给定的壁纸服务启动起来。同时旧有的壁纸服务会被终止。

  • WallpaperManagerService成功连接壁纸服务后,调用壁纸服务的attach()方法将窗口令牌等参数交给壁纸服务。

  • 壁纸服务响应attach()的调用,创建一个Engine。

  • Engine的updateSurface()方法将会创建壁纸窗口及Surface,并进行壁纸的绘制。

    而在这个过程中,WallpaperManagerService中存在如下重要的数据结构:
  • WallpaperInfo,存储了动态壁纸的开发者、缩略图与描述信息。这个数据结构创建于WallpaperManagerService.bindWallpaperComponentLocked()方法,其内容来自于壁纸所在应用程序的AndroidManifest.xml中名为android.service.wallpaper的meta-data。

  • WallpaperConnection,它不仅仅是壁纸服务与WallpaperManagerService进行通信的渠道,它同时也保存了与壁纸服务相关的重要的运行时信息,如IWallpaperService、IWallpaperEngineWrapper、WallpaperInfo以及用于创建窗口所需的窗口令牌。WallpaperConnection创建于WallpaperManagerService.bindWallpaperComponentLocked()方法。

  • WallpaperData,它保存了一个壁纸在WallpaperManagerService中可能用到的所有信息,包括壁纸服务的ComponentName,WallpaperConnection,壁纸服务的启动时间等。WallpaperData被保存在一个名为mWallpaperMap的SparseArray中,而且设备中每一个用户都会拥有一个固定的WallpaperData实例。当前用户进行壁纸切换时会更新WallpaperData的内容,而不是新建一个WallpaperData实例。另外,WallpaperData中还保存了与静态壁纸相关的一些信息。

理解UpdateSurface()方法

    Engine.attach()方法最后调用的Engine.updateSurface()方法是Engine所提供的壁纸框架的核心所在。
updateSurface()方法比较大,下面分部分讨论。[WallpaperService.java --> Engine.updateSurface()]:
        void updateSurface(boolean forceRelayout, boolean forceReport, boolean redrawNeeded) {
            if (mDestroyed) {
                Log.w(TAG, "Ignoring updateSurface: destroyed");
            }
            //获取mSurfaceHolder中保存的尺寸。如果这一尺寸为默认情况下的(-1,-1),则updateSurface()会认为其表示的尺寸为MATCH_PARENT.
            boolean fixedSize = false;
            int myWidth = mSurfaceHolder.getRequestedWidth();
            if (myWidth <= 0) myWidth = ViewGroup.LayoutParams.MATCH_PARENT;
            else fixedSize = true;
            int myHeight = mSurfaceHolder.getRequestedHeight();
            if (myHeight <= 0) myHeight = ViewGroup.LayoutParams.MATCH_PARENT;
            else fixedSize = true;
            //下面的一组变量是更新surface的条件。
            final boolean creating = !mCreated;//1、窗口尚未创建
            final boolean surfaceCreating = !mSurfaceCreated;//2、surface尚未创建
            final boolean formatChanged = mFormat != mSurfaceHolder.getRequestedFormat();//3、mSurfaceHolder中的像素格式发生了变化
            boolean sizeChanged = mWidth != myWidth || mHeight != myHeight;//4、尺寸发生了变化
            boolean insetsChanged = !mCreated;
            final boolean typeChanged = mType != mSurfaceHolder.getRequestedType();//5、surface类型发生了变化
            final boolean flagsChanged = mCurWindowFlags != mWindowFlags ||
                    mCurWindowPrivateFlags != mWindowPrivateFlags;//6、窗口的flags发生了变化
            if (forceRelayout || creating || surfaceCreating || formatChanged || sizeChanged
                    || typeChanged || flagsChanged || redrawNeeded
                    || !mIWallpaperEngine.mShownReported) {//只要满足一个条件,便有必要更新surface
                    ......//壁纸窗口的创建、重新布局以及在必要时触发SurfaceHolder的回调
从这些条件中可以看到updateSurface()方法可能进行的工作如下:
  • 创建壁纸窗口,有mCreated成员指定;
  • 从WMS申请surface,有mSurfaceCreated成员指定;
  • 修改Surface的像素格式,有SurfaceHolder.getRequestedFormat()的返回值指定;
  • 修改Surface的尺寸,由SurfaceHolder.getaRequestedWidth()/Height()的返回值指定;
  • 修改Surface的内存的类型,即NORMAL、GPU、HARDWARE、PUSH_BUFFERS。由SurfaceHolder.getRequestType()的返回值指定;
  • 修改窗口的flags,由mWindowFlags成员指定。对壁纸窗口来说,窗口flags的变化是由于Engine.setTouchEventsEnabled()方法增加或删除了FLAG_NOT_TOUCHABLE标记。
在updateSurface()后续代码中将会看到这些变量如何对Surface产生影响的。
            if (forceRelayout || creating || surfaceCreating || formatChanged || sizeChanged
                    || typeChanged || flagsChanged || redrawNeeded
                    || !mIWallpaperEngine.mShownReported) {

                if (DEBUG) Log.v(TAG, "Changes: creating=" + creating
                        + " format=" + formatChanged + " size=" + sizeChanged);

                try {
                    // 将SurfaceHolder中的设置转储到Engine的成员变量中,用于在下次updateSurface()的调用中检查他们是否发生变化。
                    mWidth = myWidth;
                    mHeight = myHeight;
                    mFormat = mSurfaceHolder.getRequestedFormat();
                    mType = mSurfaceHolder.getRequestedType();

                    // 更新窗口的LayoutParam。上述的像素格式、尺寸、内存类型以及窗口flags会使用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;
                    //mWindowToken来自WallpaperManagerService的WallpaperConnection。
                    mLayout.token = mWindowToken;

                    // 偿若壁纸窗口尚未创建,则进行窗口创建
                    if (!mCreated) {
                        // Retrieve watch round info
                        TypedArray windowStyle = obtainStyledAttributes(
                                com.android.internal.R.styleable.Window);
                        windowStyle.recycle();

                        // Add window
                        // 窗口的类型来自IWallpaperEngineWrapper.attach()方法
                        mLayout.type = mIWallpaperEngine.mWindowType;
                        mLayout.gravity = Gravity.START|Gravity.TOP;
                        mLayout.setTitle(WallpaperService.this.getClass().getName());
                        mLayout.windowAnimations =
                                com.android.internal.R.style.Animation_Wallpaper;
                        mInputChannel = new InputChannel();
                        // 创建窗口,注意壁纸窗口会被添加到DEFAULT_DISPLAY中
                        if (mSession.addToDisplay(mWindow, mWindow.mSeq, mLayout, View.VISIBLE,
                            Display.DEFAULT_DISPLAY, mContentInsets, mStableInsets, mOutsets,
                                mInputChannel) < 0) {
                            Log.w(TAG, "Failed to add window while updating wallpaper surface.");
                            return;//创建窗口失败,直接返回
                        }
                        mCreated = true;//标记窗口已经创建完成

                        // 创建WallpaperInputEventReceiver对象,用于接收触摸事件
                        mInputEventReceiver = new WallpaperInputEventReceiver(
                                mInputChannel, Looper.myLooper());
                    }

                    // 接下来的操作会修改Surface,因此必须将SurfaceHolder锁住,
                    //以免其他线程在这个过程中尝试通过Surface.lockCanvas()修改Surface的内容。
                    mSurfaceHolder.mSurfaceLock.lock();
                    mDrawingAllowed = true;

                    if (!fixedSize) {
                        mLayout.surfaceInsets.set(mIWallpaperEngine.mDisplayPadding);
                        mLayout.surfaceInsets.left += mOutsets.left;
                        mLayout.surfaceInsets.top += mOutsets.top;
                        mLayout.surfaceInsets.right += mOutsets.right;
                        mLayout.surfaceInsets.bottom += mOutsets.bottom;
                    } else {
                        mLayout.surfaceInsets.set(0, 0, 0, 0);
                    }
                    // 重新布局窗口。它将SurfaceHolder中的设置以及窗口属性同步到Surface及其窗口值中。
                    // 倘若壁纸窗口刚刚完成创建,则经过重新布局后其Surface也会变得有效。
                    final int relayoutResult = mSession.relayout(
                        mWindow, mWindow.mSeq, mLayout, mWidth, mHeight,
                            View.VISIBLE, 0, mWinFrame, mOverscanInsets, mContentInsets,
                            mVisibleInsets, mStableInsets, mOutsets, mBackdropFrame,
                            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 + mOutsets.left + mOutsets.right;
                        h += padding.top + padding.bottom + mOutsets.top + mOutsets.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;
                    }

                    // 尽管SurfaceHolder的设置给出期望的尺寸,但是WMS拥有决定窗口最终尺寸的权利。
                    // updateSurface()将WMS的布局结果设置给SurfaceHolder。
                    if (mCurWidth != w) {
                        sizeChanged = true;
                        mCurWidth = w;
                    }
                    if (mCurHeight != h) {
                        sizeChanged = true;
                        mCurHeight = h;
                    }

                    if (DEBUG) {
                        Log.v(TAG, "Wallpaper size has changed: (" + mCurWidth + ", " + mCurHeight);
                    }

                    insetsChanged |= !mDispatchedOverscanInsets.equals(mOverscanInsets);
                    insetsChanged |= !mDispatchedContentInsets.equals(mContentInsets);
                    insetsChanged |= !mDispatchedStableInsets.equals(mStableInsets);
                    insetsChanged |= !mDispatchedOutsets.equals(mOutsets);

                    mSurfaceHolder.setSurfaceFrameSize(w, h);
                    //surface更新成功,解除对SurfaceHolder的锁定
                    mSurfaceHolder.mSurfaceLock.unlock();
这部分updateSurface()的代码完成Surface更新。
  • 倘若窗口尚未创建,则通过WMS.addWindow() 完成窗口的创建。
  • 通过WMS.relayoutWindow()对窗口进行重新布局。重新布局的结果是倘若窗口尚没有一块可用的surface,Engine将会拥有一块可用的Surface。另外,存储在layoutParams中与Surface或窗口有关的参数都会被WMS接纳并据此修改Surface的属性。












  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值