• “系统的UI”——SystemUI


    SystemUI的实现

    以StatusBar为例,来分析下Android系统具体是如何实现它们的。
    相关代码分为两部分,即:

    • Service部分

    代码路径:frameworks/base/services/java/com/android/server。

    • 应用部分

    代码路径:frameworks/base/packages/SystemUI。

    下面来看看SystemUI的“目录”:

    
     
              /*SystemUIService是我们分析的重点,状态栏等系统UI实现都是在这里完成的*/
     /*由此可知,SystemUI提供了
                                            截屏操作。有兴趣的读者可以自己研究下是如何实现的*/
     /*开机自启动,
                                不过这里启动的是LoadAverageService,而不是SystemUIService*/
      
       
      
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    通过AndroidManfiest我们知道SystemUIService是整个系统UI的“载体”,所以接下来将根据这一线索来把整个代码流程“串”起来。和其他很多系统服务一样,SystemUIService也是在SystemServer中启动的。具体而言,SystemServer会在适当的时机通知ActivityManagerService“系统已经就绪(systemReady),可以进一步运行第三方模块了”——这其中就包括将由startServiceAsUser启动的SystemUIService

    SystemUIService继承了标准的Service组件,因而必须重载onCreate接口:

    /*frameworks/base/packages/systemui/src/com/android/systemui/SystemUIService.java*/
        public void onCreate() {… 
            IWindowManager wm = WindowManagerGlobal.getWindowManagerService();//获取WMS服务
            try {
               SERVICES[0] = wm.hasSystemNavBar()? R.string.config_systemBarComponent
               : R.string.config_statusBarComponent;//是StatusBar还是SystemBar?
            } catch (RemoteException e) {
                Slog.w(TAG, "Failing checking whether status bar can hide", e);
            }
            final int N = SERVICES.length;
            mServices = new SystemUI[N];
            for (int i=0; i
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    SERVICES是一个object数组,它的初始值如下所示:

    final Object[] SERVICES = new Object[] {
            0, // system bar or status bar, filled in below.
            com.android.systemui.power.PowerUI.class,
            com.android.systemui.media.RingtonePlayer.class,
            com.android.systemui.settings.SettingsUI.class,
        };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    其中,SERVICES[0]在初始化时没有赋值。它将根据hasSystemNavBar的执行结果来决定是用systemBar还是statusBar。上面这段代码首先取出SERVICES数组中的class名,然后分别实例化它们,最后调用start接口统一启动。因此,每一个系统ui元素(包括statusBar,PowerUI等)都必须继承自SystemUI这个抽象类,并重载其中的start方法。

    函数hasSystemNavBar做了哪些判断来对statusBar和systemBar进行取舍呢?WindowManager的真正实现体是WindowManagerService。

      /*frameworks/base/services/java/com/android/server/wm/WindowManagerService.java*/
         public boolean hasSystemNavBar() {
            return mPolicy.hasSystemNavBar();
        }
    
    • 1
    • 2
    • 3
    • 4

    **Policy是Android中定义UI行为的一个“规范”。**比如有没有Navigation Bar,WindowLayer如何排布等。以PhoneWindowManager为例,它判断当前系统是否需要导航条。

    我们假设设备的分辨率是800*480,屏幕密度为ldpi:

    /*frameworks/base/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java*/
           int shortSizeDp = shortSize*DisplayMetrics.DENSITY_DEFAULT/ density;
            /*在这个场景中,shortSize=480,DENSITY_DEFAULT=160,density =120, 所以最终
            shortSizeDp = 640*/
    
            if (shortSizeDp < 600) {//在这个场景中不成立
                mHasSystemNavBar = false;
                mNavigationBarCanMove = true;
            } else if (shortSizeDp < 720) {/*本场景属于这一分支*/
                mHasSystemNavBar = false;
                mNavigationBarCanMove = false;
            } 
            if (!mHasSystemNavBar) {//进一步判断是否有Navigation Bar
                …
            } else {
                mHasNavigationBar = false;
            }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    所以在这个场景中,经过上面的判决后mHasSystemNavBar为false。换句话说,对于分辨率800*480且密度为ldpi的屏幕,它的SERVICES[0]对应的class类名是R.string.config_statusBar Component即“com.android.systemui.statusbar.phone.PhoneStatusBar”。下面以PhoneStatusBar为例来看看它的创建过程及具体样式:

    /*frameworks/base/packages/systemui/src/com/android/systemui/statusbar/phone/  
         PhoneStatusBar.java*/
        public void start() {
            mDisplay = ((WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE))
                    .getDefaultDisplay();/*mDisplay记录了当前默认显示屏的大小,密度等等信息*/
            …
            super.start();// 关键语句,下面我们会重点介绍
            addNavigationBar();/*不是所有Phone都需要Navigation Bar。比如设备本身已经配备了物理按
                                 键,这种情况下如果一直在屏幕上显示导航条反而是一种累赘*/
            …
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    PhoneStatusBar的“父类”是BaseStatusBar,很多框架性的操作都是在这里面完成的(但UI界面的具体描述还是会通过回调PhoneStatusBar中的方法来确定):

     /*frameworks/base/packages/systemui/src/com/android/systemui/statusbar/BaseStatusBar.java*/
       public void start() {…
          mBarService = IStatusBarService.Stub.asInterface(
                  ServiceManager.getService(Context.STATUS_BAR_SERVICE));
          // Connect in to the status bar manager service
          StatusBarIconList iconList = new StatusBarIconList();//状态栏图标列表
          ArrayList notificationKeys = new ArrayList();
          ArrayList notifications = new ArrayList();
          mCommandQueue = new CommandQueue(this, iconList);
          int[] switches = new int[7];
          ArrayList binders = new ArrayList();
          try {
              mBarService.registerStatusBar(mCommandQueue,iconList,notificationKeys,
                     notifications,switches, binders); /*经过一系列对象的创建与初始化后,开始向
                                   StatusBarService进行注册。这里涉及跨进程操作,因而传递的
                                   参数都是继承自Parcelable的*/
          } catch (RemoteException ex) {
              // If the system process isn't there we're doomed anyway.
          }
     createAndAddWindows(); /*这是真正将Status Bar显示出来的地方*/
          …
       }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    好不容易快到“水落石出”的时候了,但是上面这段代码却又杀出一个“程咬金”——StatusBarService。

    既然SystemUI这个应用程序中已经有StatusBar了,为什么又需要StatusBarService?

    先来看看StatusBarService是在哪里启动的。

       /*frameworks/base/services/java/com/android/server/SystemServer.java*/
         try {
               Slog.i(TAG, "Status Bar");
               statusBar = new StatusBarManagerService(context, wm); /*确实在这里。而且具体的实现类叫做StatusBarManagerService*/
               ServiceManager.addService(Context.STATUS_BAR_SERVICE, statusBar);
            } catch (Throwable e) {
                reportWtf("starting StatusBarManagerService", e);
            }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    现在可以进一步分析StatusBarManagerService的实现了。针对上面BaseStatusBar中调用的注册操作:

    public void registerStatusBar(IStatusBar bar, StatusBarIconList iconList,List
                        notificationKeys,List notifications,
                                          int switches[], List binders) {
            enforceStatusBarService();
            mBar = bar;
            synchronized (mIcons) {
               iconList.copyFrom(mIcons); /*复制Icon列表,注意方向是从StatusBarManager->  
                                          BaseStatusBar*/
            }
            synchronized (mNotifications) {
               for (Map.Entry e: mNotifications.entrySet()) 
               {
                   notificationKeys.add(e.getKey());
                   notifications.add(e.getValue());/*和Icon列表类似,方向也是从StatusBarManager
                                          到BaseStatusBar*/
                }
            }
            …
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    由上面这段代码可以看出,registerStatusBar有两个作用:
    其一,为新启动的SystemUI应用中的StatusBar赋予当前系统的真实值(比如有多少需要显示的图标)。其二,通过成员变量mBar记录下IStatusBar对象——它在SystemUI中对应的是CommandQueue。

    我们再回到BaseStatusBar。向StatusBarManagerService注册完成后,它会执行如下语句。

    createAndAddWindows();
    
    • 1

    BaseStatusBar中的这个方法是抽象的,因而其子类PhoneStatusBar必须要重载它:

      /*frameworks/base/packages/systemui/src/com/android/systemui/statusbar/phone/Ph-  
         oneStatusBar.java*/
        public void createAndAddWindows() {
            addStatusBarWindow();
         }
         private void addStatusBarWindow() {
            final int height = getStatusBarHeight();/*首先获取StatusBar的高度。默认的高度值是通
                             过com.android.internal.R.dimen.status_bar_height来指定的,因而
                             开发人员如果需要更改StatusBar高度的话,可以考虑修改这个值*/ 
            final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
                    ViewGroup.LayoutParams.MATCH_PARENT, /*宽度是MATCH_PARENT*/
                    height, //高度值是可定制的
                    WindowManager.LayoutParams.TYPE_STATUS_BAR, /*指定窗口类型*/
                    WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE|
                      WindowManager.LayoutParams.FLAG_TOUCHABLE_WHEN_WAKING
                       | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH,
                       /*设置flag, 下面还会加上硬件加速属性*/
                    PixelFormat.TRANSLUCENT/*半透明的*/);
    
            lp.flags |=windowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
            lp.gravity = getStatusBarGravity();/*设置Gravity属性,默认值为Gravity.TOP  
            |Gravity.FILL_HORIZONTAL,所以StatusBar是在屏幕上方*/
            lp.setTitle("StatusBar"); //标题
            lp.packageName = mContext.getPackageName();
     makeStatusBarView(); //下面会详细介绍
            mWindowManager.addView(mStatusBarWindow, lp); /*将一切就绪的mStatusBarWindow加入
                                  WindowManager中。请参见本书显示系统章节的讲解*/
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    从makeStatusBarView这个函数名可以推断出,StatusBarView会被创建并且初始化。先来了解下两个重要的变量。

    • mStatusBarWindow
      这是一个StatusBarWindowView类对象,同时我们通过addView传给WindowManager的也是这个变量——说明它很可能包含了StatusBarView。

    • mStatusBarView
      这就是makeStatusBarView需要操作的对象。

    /*frameworks/base/packages/systemui/src/com/android/systemui/statusbar/phone/Ph-one  
    StatusBar.java*/
         protected PhoneStatusBarView makeStatusBarView() {…
            mStatusBarWindow = (StatusBarWindowView) View.inflate(context, R.layout.super_   
            status_bar, null);
            mStatusBarWindow.mService = this; //mService其实指的是PhoneStatusBar
            mStatusBarWindow.setOnTouchListener(new View.OnTouchListener() {//设置触摸事件
                @Override
                public boolean onTouch(View v, MotionEvent event) {
                    if (event.getAction() == MotionEvent.ACTION_DOWN) {//支持下拉手势
                        if (mExpandedVisible) {
                           animateCollapsePanels ();//通知栏的“下拉展开”需要动画效果,不然会很突兀
                        }
                    }
                    return mStatusBarWindow.onTouchEvent(event);
                }});
            mStatusBarView =(PhoneStatusBarView)mStatusBarWindow.findViewById(R.id.status_ bar);
            mStatusBarView.setBar(this); /*状态栏出场了*/
            …
            mNotificationPanel = (NotificationPanelView) mStatusBarWindow.
                                      findViewById(R.id. notification_panel);
          mNotificationPanel.setStatusBar(this); /*通知栏也很关键,只不过它只有在下拉后才会出现*/
            /*从下面开始将利用mStatusBarView为PhoneStatusBar中的众多内部变量赋值*/
            …
            try {
                boolean showNav = mWindowManagerService.hasNavigationBar();/*决定是否需要导航条*/
                if (showNav) {
                    mNavigationBarView = (NavigationBarView) View.inflate(context, 
                                         R.layout. navigation_ bar, null);
                                         /*Navigation Bar对应的layout。有兴趣的读者可以自己看一下*/
                    …
                }
            } catch (RemoteException ex) {
                /*Android中的不少代码在捕捉异常时,很常见的一种处理就是“听天由命”…*/
            }
            /*接下来通过findViewById从mStatusBarView中获取StatusIcons、NotificationIcons、
               ClearButton等一系列按键。我们将会在StatusBar布局文件中做统一分析。这里暂时略过*/
            …       
            /*最后动态注册需要接收的广播,比如系统设置改变,屏幕关闭等*/
            IntentFilter filter = new IntentFilter();
            filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
            filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
            …
            context.registerReceiver(mBroadcastReceiver, filter);…
            return mStatusBarView;//注意最终返回值是mStatusBarWindow的子View
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46

    变量mStatuBarWindow来源于super_status_bar布局。它本质上还是一个FrameLayout,包含的元素也很简单,就是status_barstatus_bar_expanded两个布局,前者对应的是状态栏mStatusBarView,后者其实就是通知栏mNotificationPanel。为status_bar中的众多元素(按键、背景等)进行初始化。

    最终的返回值是mStatusBarView。然后利用WindowManager的addView接口将mStatus- BarWindow(注意:不是mStatusBarView)添加进窗口系统中。接下来的主动权就转交给WindowManager。

    这样我们就把StatusBar,Notification和NavigationBar的调用流程“串”起来了。

    在这里插入图片描述

    Android壁纸资源——WallpaperService

    除了前面讲解的状态栏、通知栏外,壁纸也属于SystemUI管理的一个重点。在Android系统中,用户可以从设备内部或者外存储器(比如SD卡中)中选取图片资源作为壁纸。另外,系统还支持动态壁纸的显示。

    壁纸管理系统主要包括以下几个方面。

    • WallpaperManagerService(WPMS)
      它是壁纸机制的“大总管”,静态、动态壁纸都是在这里统一调度的。

    • WallpaperService(WPS)
      WPS继承了标准的Service组件,因而它一定会实现onCreate、onDestroy、onBind等一系列方法。此外它还包含了一个重要的嵌套类engine,我们在后面会做详细讲解。WPS是静态、动态壁纸的基类,代表了作为“壁纸”所应该具有的一切属性。

    • ImageWallpaper(IWP)
      从名称可以看出,它是静态壁纸的实现类,而且一定是继承自上面的WPS。

    在这里插入图片描述

    WallPaperManagerService

    WPMS既然是基于AIDL实现的,我们来看看它的接口描述:

    /*frameworks/base/core/java/android/app/IWallpaperManager.aidl*/
    interface IWallpaperManager {
        ParcelFileDescriptor setWallpaper(String name); /*设置壁纸*/
        void setWallpaperComponent(in ComponentName name); /*设置动态壁纸*/
        ParcelFileDescriptor getWallpaper(IWallpaperManagerCallback cb,out Bundle outParams);
        WallpaperInfo getWallpaperInfo();
        …
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    从上面的接口定义可以看出,WPMS的工作并不复杂——它提供了全局的壁纸注册、取消和查询功能,并在接收到事件时进行合理分配。

    和其他系统服务一样,WPMS是在SystemServer.java中启动并注册进ServiceManager中的,如下所示:

     /*frameworks/base/services/java/com/android/server/SystemServer.java*/
         try {
                Slog.i(TAG, "Wallpaper Service");
                if (!headless) {
                     wallpaper = new WallpaperManagerService(context);
                     ServiceManager.addService(Context.WALLPAPER_SERVICE, wallpaper);
                }
         } catch (Throwable e) {
            reportWtf("starting Wallpaper Service", e);
         }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    接下来的一个问题是:既然系统同时支持静态壁纸和动态壁纸,而且每种类型中还包含了N个实例(比如原生态系统就自带多个动态壁纸供用户选择),那么系统在显示时是如何选择的呢?我们很自然地会想到,在WPMS启动时它应该会去读取某个“配置文件”,这个文件记录了用户最近一次的选择:

     public WallpaperManagerService(Context context) {
            …
     loadSettingsLocked(UserHandle.USER_OWNER);//加载配置
        }
    
    • 1
    • 2
    • 3
    • 4

    当WPMS构造时,它调用了loadSettingsLocked

     private void loadSettingsLocked(int userId) {//这里传进来的userId=0
            …
            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.name = parser.getAttributeValue(null, "name");
                            String comp = parser.getAttributeValue(null, "component");
                            …
                        }
                    }
                } while (type != XmlPullParser.END_DOCUMENT);
                success = true;
            } 
         …        
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    上面这段代码会按照写入时的格式将wallpaper的配置信息读出来,并保存在WallpaperData结构中——专门用于描述壁纸信息的通用数据结构。不过这时壁纸还没有真正显示出来,而是要等到系统进入Ready状态(此时系统会回调SystemReady接口)后才会通知具体的壁纸程序进行绘制:

     public void systemReady() {
            WallpaperData wallpaper = mWallpaperMap.get(UserHandle.USER_OWNER);
            switchWallpaper(wallpaper, null);
            …
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    接着进入Wallpaper的具体处理中:

    void switchWallpaper(WallpaperData wallpaper, IRemoteCallback reply) {
            synchronized (mLock) {…
                try {
                    ComponentName cname = wallpaper.wallpaperComponent != null ?
                        wallpaper.wallpaperComponent : wallpaper.nextWallpaperComponent;
                    if (bindWallpaperComponentLocked(cname, true, false, wallpaper,reply)) {
                        return;
                    }
                } …
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    系统开机后,wallpaper.wallpaperComponent为空(除非上一次用户选择了其他方式);而wallpaper.nextWallpaperComponent则在loadSettingsLocked中被设置为wallpaper.imageWallpaper Component,即我们前面提到的ImageWallpaper这个Service。所以当调用bindWallpaperComponentLocked时,传入的cname就代表了ImageWallpaper。从bindWallpaperComponentLocked的函数名称可以看出,它将会以bindService的方式来启动目标壁纸Service(所以后期如果确认已经不再使用这个Service,还要主动执行unbind,然后这个壁纸服务就会自动销毁)。

    WPMS启动后就可以接收客户端的请求了,因为它属于实名的BinderServer,意味着所有人都可以自由地使用它所提供的服务。比如我们既可以在系统自带的Launcher应用程序中选择壁纸,也完全可以自己编写一个更改壁纸的应用程序。

    下面我们以设置壁纸这一场景为例来分析WPMS的内部实现:

     /*frameworks/base/services/java/com/android/server/WallpaperManagerService.java*/
        public ParcelFileDescriptor setWallpaper(String name) {
            checkPermission(android.Manifest.permission.SET_WALLPAPER);
            synchronized (mLock) {
                int userId = UserHandle.getCallingUserId();
                WallpaperData wallpaper = mWallpaperMap.get(userId);
                …
                final long ident = Binder.clearCallingIdentity();
                try {
                    ParcelFileDescriptor pfd = updateWallpaperBitmapLocked(name, wallpaper);
                    …
                    return pfd;
                } finally {
                    Binder.restoreCallingIdentity(ident);
                }
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    首先系统会做下权限检查,所以提供壁纸设置功能的应用程序一定要在AndroidManifest.xml中显式写上如下权限声明:

    
    
    • 1

    变量wallpaper是从mWallpaperMap取出来的,代表UserId为0时的壁纸——如果不为空就进入以下函数:

    ParcelFileDescriptor updateWallpaperBitmapLocked(String name, WallpaperData wallpaper) {
            if (name == null) name = "";
            try {
                File dir = getWallpaperDir(wallpaper.userId);//wallpaper的路径
                if (!dir.exists()) {//指定的路径不存在,需要创建
                    dir.mkdir();
                    FileUtils.setPermissions(dir.getPath(),
                            FileUtils.S_IRWXU|FileUtils.S_IRWXG|FileUtils.S_IXOTH, -1, -1);
                }
                File file = new File(dir, WALLPAPER);
               ParcelFileDescriptor=ParcelFileDescriptor.open(file,
                                                           MODE_CREATE|MODE_READ_WRITE);
                if (!SELinux.restorecon(file)) {
                    return null;
                }
                wallpaper.name = name;
                return fd;
            } catch (FileNotFoundException e) {
                Slog.w(TAG, "Error setting wallpaper", e);
            }
            return null;
        }    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    上面getWallpaperDir将得到一个WALLPAPER_BASE_DIR+“/”+userId的路径,其中WALLPAPER_BASE_DIR默认值是"/data/system/users"。

    ImageWallpaper

    前面讲过,当WPMS开机启动时,默认情况下会选择ImageWallpaper这个壁纸实现,并且以bindService的方式来启动它。在bindService中,WPMS同时传入名为newConn的Binder对象(WallpaperConnection)来使ImageWallpaper(其他WallpaperService也是一样的)可以访问到WPMS。而ImageWallpaper则响应onBind返回一个IWallpaperServiceWrapper的Binder对象,如图所示。

    在这里插入图片描述我们来看看当绑定成功后WPMS中的操作:

     public void onServiceConnected(ComponentName name, IBinder service) {
                synchronized (mLock) {
                    if (mWallpaper.connection == this) {…
                        attachServiceLocked(this, mWallpaper);
                        …
                        saveSettingsLocked(mWallpaper);
                    }
                }
            }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    WPMS除了要保存当前所选的壁纸外,还要调用attachServiceLocked(间接调用Iwallpaper ServiceWrapper.attach)来执行实际的工作。

    WPS这边的attach函数将生成一个IWallpaperEngineWrapper对象并给它发送一个DO_ATTACH,这个消息最终由IWallpaperEngineWrapper. executeMessage来处理:

     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;
                    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    上述代码段通过onCreateEngine生成了一个壁纸引擎——这也是各壁纸应用间最核心的差异。所以系统要求每一个WallpaperService实例必须要重载onCreateEngine来实现自己的engine。在ImageWallpaper中,它将产生一个DrawableEngine——这个engine随后会被加入mActiveEngines的全局list中,然后调用它提供的attach接口。如下所示:

    /*frameworks/base/core/java/android/service/wallpaper/WallpaperService.java*/
         public class Engine {…
              void attach(IWallpaperEngineWrapper wrapper) {…
                mSession = WindowManagerGlobal.getWindowSession();
                mWindow.setSession(mSession);
                …
                IntentFilter filter = new IntentFilter();
                filter.addAction(Intent.ACTION_SCREEN_ON);
                filter.addAction(Intent.ACTION_SCREEN_OFF);
                registerReceiver(mReceiver, filter);             
                …
     updateSurface(false, false, false);//更新Surface
    }…
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    Engine内部首先需要进行各重要变量的初始化,然后注册监听屏幕的开/关事件,最后调用updateSurface。

  • 相关阅读:
    (附源码)计算机毕业设计Java坝上长尾鸡养殖管理系统
    STM32SDIO外设详解
    docker启动fastdfs
    【EI会议征稿】第三届机械、建模与材料工程国际学术会议(I3ME 2023)
    《遥感学报》
    Protocol Buffers学习【Qt】
    第43天:python操作mysql、视图及触发器、事务及索引
    VLAN间路由课堂总结及园区网组网实验
    php实战案例记录(8)去除XSS的函数
    FFplay文档解读-26-视频过滤器一
  • 原文地址:https://blog.csdn.net/jxq1994/article/details/132735789