SystemUI 開發(fā)總結(jié)

SystemUI 開發(fā)總結(jié)

| 目錄-
SystemUI 有哪內(nèi)容

初次開發(fā) SystemUI 有哪些彎路?

SystemUI 創(chuàng)建流程?

應(yīng)用通知視圖是如何跨進(jìn)程顯示的?

應(yīng)用窗口的 flag 是如何影響狀態(tài)欄的?

后續(xù):SystemUI 能否脫離對(duì)系統(tǒng)源碼依賴?

SystemUI 有哪內(nèi)容

從表面上看, 我們看到的狀態(tài)欄、通知欄、下拉菜單、導(dǎo)航欄、鎖屏、最近任務(wù)、低電提示等系統(tǒng)頁(yè)面都是 SystemUI 的。SystemUI,在源碼目錄中位于: framework/base/packages 目錄下, 可見(jiàn) SystemUI 和 framework 是關(guān)聯(lián)的, SystemUI 依賴了很多內(nèi)部 API , 系統(tǒng)資源, SystemUI 編譯是要依賴系統(tǒng)源碼的。

SystemUI 也是一個(gè)應(yīng)用,不過(guò)這個(gè)應(yīng)用特殊之處在于他沒(méi)有啟動(dòng)圖標(biāo)、也沒(méi)有入口 Activity 。他的入口程序是一個(gè)服務(wù):SystemUIService。 這個(gè)服務(wù)會(huì)被系統(tǒng)服務(wù)拉起來(lái), 這個(gè)服務(wù)起來(lái), SystemUI 應(yīng)用進(jìn)程就創(chuàng)建起來(lái)了,具體啟動(dòng)過(guò)程后面會(huì)分析。除了 SystemUIService , SystemUI 還有很多服務(wù), 例如: 負(fù)責(zé)鎖屏的KeyguardService、負(fù)責(zé)最近任務(wù)的 RecentsSystemUserService、負(fù)責(zé)壁紙的 ImageWallpaper 、負(fù)責(zé)截屏的TakeScreenshotService 等。

系統(tǒng)移植 、UI 改造

如果要做系統(tǒng)移植, SystemUI 改造這塊的資料還是挺少,大部分情況下都是啃源碼,連蒙帶猜的修改,然后再編譯出來(lái)驗(yàn)證。通常我們會(huì)從布局著手看看哪個(gè)布局長(zhǎng)得像就著手去改,不過(guò)這塊完全是可以沉淀一下經(jīng)驗(yàn)出來(lái)讓后人去節(jié)省時(shí)間的。這里我也不再贅述了, 有人已經(jīng)梳理過(guò)了, 我借花獻(xiàn)佛吧:https://blog.csdn.net/azhengye/article/details/50419409。

架構(gòu)關(guān)系

在系統(tǒng)服務(wù)中,有一個(gè)服務(wù)是專門為 SystemUI 的狀態(tài)欄服務(wù)的, 這個(gè)服務(wù)就是 StatusbarManagerService (簡(jiǎn)稱:SMS),和這個(gè)服務(wù)關(guān)系比較密切的服務(wù)是 WindowManagerService(簡(jiǎn)稱:WMS), SMS 主要管控的是狀態(tài)欄、導(dǎo)航欄, 例如:我們可以設(shè)置全屏、沉浸式狀態(tài)欄都是 SMS 在起作用。

初次開發(fā) SystemUI 有哪些彎路 (環(huán)境上的坑)

失敗方案1

IDE獨(dú)立編譯 SystemUI , 把 SystemUI 所依賴的系統(tǒng) jar 都拷貝帶 IDE 下,使用 provided 方式依賴。 在 6.0 以下版本還勉強(qiáng)可行 , 8.0 以后就基本不可能了, 8.0 以后 SystemUI 合入了鎖屏模塊,依賴了太多的系統(tǒng)資源, 編譯不過(guò)是一個(gè)問(wèn)題, 就算編譯過(guò)了, 所依賴的系統(tǒng)資源 ID 也會(huì)不一致。 經(jīng)過(guò) 1~2 兩天的嘗試, 這個(gè)方案失敗了。

失敗方案2

使用 Google 源碼編譯, 然后在源碼中修改 SystemUI , 將編譯的 SystemUI 安裝到 MTK 系統(tǒng)的版子上。 發(fā)現(xiàn)安裝到 MTK 的板子以后跑不起來(lái), 原因是某些服務(wù)啟動(dòng)不了, 同時(shí)也存在資源 ID 不一致的問(wèn)題。 經(jīng)過(guò) 2~3 天的這條這個(gè)方案失敗了。

最終方案


最終不得不麻煩系統(tǒng)同學(xué), 幫忙提供源碼: 在 MTK 源碼中編譯。

為了提高效率, 使用一臺(tái)昨晚編譯機(jī), 另一臺(tái)作為編輯機(jī), 通過(guò) ssh 搭建通道配合完成開發(fā)、編譯、安裝三個(gè)流程。

SystemUI 是如何啟動(dòng)的?

前面介紹過(guò) SystemUIService 是 SystemUI 的入庫(kù)程序。 SystemUIService 是在服務(wù)進(jìn)程中啟動(dòng)的,我們來(lái)看下源碼:

SystemServer.java 中 SystemServer 是 zygote 進(jìn)程起來(lái)的啟動(dòng)的第一個(gè)服務(wù), 然后在這個(gè)服務(wù)的 run 方法方法中會(huì)一次啟動(dòng) Android 系統(tǒng)服務(wù)。

private void run() {
    
         // ... 省略一堆代碼
         startBootstrapServices();
         startCoreServices();
         startOtherServices();
         // ... 省略一堆代碼
    
    }
 其中 AMS 是在 startOtherServices() 這個(gè)方法中啟動(dòng)的:
private void startOtherServices() {
    
         // ... 省略一堆代碼
         mActivityManagerService = mSystemServiceManager.startService(
         ActivityManagerService.Lifecycle.class).getService();
         // ... 省略一堆代碼
         mActivityManagerService.systemReady(() -> {
             
             // ... 省略一堆代碼
             try {
                startSystemUi(context, windowManagerF);
             } catch (Throwable e) {
                reportWtf("starting System UI", e);
             } 
             // ... 省略一堆代碼
         });
    
    }

在 AMS 啟動(dòng)啟動(dòng)完成之后,會(huì)回調(diào)一個(gè) systemReady() 傳遞進(jìn)去的方法, 在其中調(diào)用 startSystemUi() 方法啟動(dòng)了 SystemUI :

static final void startSystemUi(Context context, WindowManagerService windowManager) {
        Intent intent = new Intent();
        intent.setComponent(new ComponentName("com.android.systemui",
                    "com.android.systemui.SystemUIService"));
        intent.addFlags(Intent.FLAG_DEBUG_TRIAGED_MISSING);
        //Slog.d(TAG, "Starting service: " + intent);
        context.startServiceAsUser(intent, UserHandle.SYSTEM);
        windowManager.onSystemUiStarted();
    }

SystemUIService 邏輯也是相當(dāng)簡(jiǎn)單, 啟動(dòng)之后主要調(diào)用 SystemUIApplication 的 startServicesIfNeeded()|

@Override
public void onCreate() {
    super.onCreate();
    ((SystemUIApplication) 
   getApplication()).startServicesIfNeeded();

    // For debugging RescueParty
    if (Build.IS_DEBUGGABLE && 
        SystemProperties.getBoolean("debug.crash_sysui", false)) {
        throw new RuntimeException();
     }
 }           

在 SystemUIApplication 中啟動(dòng)了 SystemUI 的各個(gè) UI 模塊:

public void startServicesIfNeeded() {
        startServicesIfNeeded(SERVICES);
    }

例如 : SERVICES 包含了狀態(tài)欄、電量、畫中畫、 鎖屏等。

通知視圖是如何夸進(jìn)程顯示的?

跨進(jìn)程通訊的基礎(chǔ)是 IPC ,通知服務(wù)(NotificationManagerService, 簡(jiǎn)稱 NMS)也不離開 IPC ,核心架構(gòu)還是 IPC 架構(gòu)。

消息通道

  1. 應(yīng)用做作為通知的發(fā)送端, 需要調(diào)用 NMS ,發(fā)通知。例如:
String channelId = "channel_1";
          String tag = "ailabs";
          int id = 10086;
          int importance = NotificationManager.IMPORTANCE_LOW;
          NotificationChannel channel = new NotificationChannel(channelId, "123", importance);
          NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
          manager.createNotificationChannel(channel);
          Notification notification = new Notification.Builder(MainActivity.this, channelId)
                  .setCategory(Notification.CATEGORY_MESSAGE)
                  .setSmallIcon(R.mipmap.ic_launcher)
                  .setContentTitle("This is a content title")
                  .setContentText("This is a content text")
                  .setAutoCancel(true)
                  .build();
           // 通知欄要顯示的視圖布局
          RemoteViews remoteViews = new RemoteViews(getPackageName(), R.layout.layout_remoteviews);                 
          notification.contentView = remoteViews;
          manager.notify(tag, id , notification);
  1. SystemUI 作為通知的接收放需要注冊(cè)監(jiān)聽器 INotificationListener 是監(jiān)聽通通知的一個(gè) AIDL 接口,
    NotificationListenerService 是一個(gè)監(jiān)聽管理服務(wù),他的內(nèi)部類 NotificationListenerWrapper 實(shí)現(xiàn)了
    INotificationListener 接口。 例如:
/** @hide */
        protected class NotificationListenerWrapper extends INotificationListener.Stub {
            @Override
            public void onNotificationPosted(IStatusBarNotificationHolder sbnHolder,
                    NotificationRankingUpdate update) {
                     // 接收通知
                      ....
                     省略了很多代碼
            }
    
            @Override
            public void onNotificationRemoved(IStatusBarNotificationHolder sbnHolder,
                    NotificationRankingUpdate update, NotificationStats stats, int reason) {
                    // 刪除通知
                          ....
                     // 省略了很多代碼
            }

這個(gè)通知監(jiān)聽需要向 NMS 注冊(cè):

@SystemApi
          public void registerAsSystemService(Context context, ComponentName componentName,
                  int currentUser) throws RemoteException {
              if (mWrapper == null) {
                  mWrapper = new NotificationListenerWrapper();
              }
              mSystemContext = context;
              INotificationManager noMan = getNotificationInterface();
              mHandler = new MyHandler(context.getMainLooper());
              mCurrentUser = currentUser;
              noMan.registerListener(mWrapper, componentName, currentUser);
          }
 以上是 Android 為我們提供的通知接收管理服務(wù)類, SystemUI 有個(gè)NotificationListenerWithPlugins 類繼承了 NotificationListenerService

類。 并在 SystemUI 進(jìn)程起來(lái)的時(shí)候調(diào)用 registerAsSystemService() 方法完成了注冊(cè):

NotificationListenerWithPlugins mNotificationListener = new NotificationListenerWithPlugins();
    mNotificationListener.registerAsSystemService();

這樣通道就建立起來(lái)了。

消息傳遞過(guò)程,大家可以按照這個(gè)思路器走讀源碼

<a name="13yage"></a>

RemoteViews

以上只是講解了應(yīng)用怎么把一個(gè)消息傳遞到 SystemUI , 理解 IPC 通訊的不難理解。 而神奇之處在于顯示的視圖布局明明是定義在一個(gè)應(yīng)用中,為何能跨進(jìn)程顯示到 SystemUI 進(jìn)程中呢?

發(fā)送通知, 傳遞的通知實(shí)體是 Notification 的實(shí)例, Notification 實(shí)現(xiàn)了 Parcelable 接口。 Notification 有個(gè) RemoteViews 的成員變量

RemoteViews remoteViews = new RemoteViews(getPackageName(), R.layout.layout_remoteviews); notification.contentView = remoteViews;

RemoteViews 也實(shí)現(xiàn)了 Parcelable 接口, 主要是封裝了通知欄要展示的視圖信息, 例如, 應(yīng)用包名、布局ID。我們都知道實(shí)現(xiàn)了 Parcelable 這個(gè)接口就可以在 IPC 通道上夸進(jìn)程傳遞。 RemoteView 支持的布局類型也是有限的,例如在 8.0 上僅支持如下類型:

  • android.widget.AdapterViewFlipper
    *android.widget.FrameLayout
  • android.widget.GridLayout
  • android.widget.GridView
  • android.widget.LinearLayout
  • android.widget.ListView
  • android.widget.RelativeLayout
  • android.widget.StackView
  • android.widget.ViewFlipper

RemoteView 攜帶了視圖信息, 進(jìn)程間傳遞的并不是真實(shí)的視圖對(duì)象, 而主要是布局的 id ,那么顯示在通知欄上的視圖對(duì)象又是如何創(chuàng)建出來(lái)的呢?

通知視圖創(chuàng)建

在通知的接收端創(chuàng)建的,上文說(shuō)過(guò) NotificationManagerService 內(nèi)部類 NotificationListenerWrapper 監(jiān)聽通知消息, 在收到消息之后就在里面解析消息,并創(chuàng)建視圖了。

protected class NotificationListenerWrapper extends INotificationListener.Stub {
          
          @Override
          public void onNotificationPosted(IStatusBarNotificationHolder sbnHolder,
                  NotificationRankingUpdate update) {
              StatusBarNotification sbn;
              try {
                  sbn = sbnHolder.get();
              } catch (RemoteException e) {
                  Log.w(TAG, "onNotificationPosted: Error receiving StatusBarNotification", e);
                  return;
              }
  
              try {
                  // convert icon metadata to legacy format for older clients
                  createLegacyIconExtras(sbn.getNotification());
                  // 創(chuàng)建視圖
                  maybePopulateRemoteViews(sbn.getNotification());
                  
                  maybePopulatePeople(sbn.getNotification());
              } catch (IllegalArgumentException e) {
                  // warn and drop corrupt notification
                  Log.w(TAG, "onNotificationPosted: can't rebuild notification from " +
                          sbn.getPackageName());
                  sbn = null;
              }
  
              // ... 省略代碼
  
          }
  
          @Override
          public void onNotificationRemoved(IStatusBarNotificationHolder sbnHolder,
                  NotificationRankingUpdate update, NotificationStats stats, int reason) {
              StatusBarNotification sbn;
              //... 省略代碼
  
          }
      }
  在 maybePopulateRemoteViews  這個(gè)方法中會(huì)去檢查布局是否要加載, **其實(shí)我們比較好奇的是布局資源在應(yīng)用進(jìn)程中,

SystemUI 如何加載遠(yuǎn)程進(jìn)程的布局資源?**
有兩個(gè)關(guān)鍵的信息: 包名、布局ID。知道了包名 SystemUI 進(jìn)程是有權(quán)限創(chuàng)建對(duì)應(yīng)包名的上下文對(duì)象的,進(jìn)而可以拿到對(duì)應(yīng)應(yīng)用的
資源管理器, 然后就可以加載布局資源創(chuàng)建對(duì)象了。 maybePopulateRemoteViews 方法跟蹤下去, 會(huì)走到 RemoteViews 的

private View inflateView(Context context, RemoteViews rv, ViewGroup parent) {
         // RemoteViews may be built by an application installed in another
         // user. So build a context that loads resources from that user but
         // still returns the current users userId so settings like data / time formats
         // are loaded without requiring cross user persmissions.
         final Context contextForResources = getContextForResources(context);
         Context inflationContext = new RemoteViewsContextWrapper(context, contextForResources);
 
         // If mApplyThemeResId is not given, Theme.DeviceDefault will be used.
         if (mApplyThemeResId != 0) {
             inflationContext = new ContextThemeWrapper(inflationContext, mApplyThemeResId);
         }
         LayoutInflater inflater = (LayoutInflater)
                 context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
 
         // Clone inflater so we load resources from correct context and
         // we don't add a filter to the static version returned by getSystemService.
         inflater = inflater.cloneInContext(inflationContext);
         inflater.setFilter(this);
         View v = inflater.inflate(rv.getLayoutId(), parent, false);
         v.setTagInternal(R.id.widget_frame, rv.getLayoutId());
         return v;
     }

其中 getContextForResources 中的 context 對(duì)象就是通過(guò)應(yīng)用包名創(chuàng)建的上下文對(duì)象,創(chuàng)建過(guò)程:

private static ApplicationInfo getApplicationInfo(String packageName, int userId) {
          if (packageName == null) {
              return null;
          }
  
          // Get the application for the passed in package and user.
          Application application = ActivityThread.currentApplication();
          if (application == null) {
              throw new IllegalStateException("Cannot create remote views out of an aplication.");
          }
  
          ApplicationInfo applicationInfo = application.getApplicationInfo();
          if (UserHandle.getUserId(applicationInfo.uid) != userId
                  || !applicationInfo.packageName.equals(packageName)) {
              try {
                  Context context = application.getBaseContext().createPackageContextAsUser(
                          packageName, 0, new UserHandle(userId));
                  applicationInfo = context.getApplicationInfo();
              } catch (NameNotFoundException nnfe) {
                  throw new IllegalArgumentException("No such package " + packageName);
              }
          }
  
          return applicationInfo;
    }

只有 SystemUI 才能接收通知嗎?

答案是否定的, 只要有權(quán)限注冊(cè)通知監(jiān)聽的應(yīng)用都可以。 具體權(quán)限是: <uses-permission android:name="android.permission.STATUS_BAR_SERVICE"/>
只要應(yīng)用有這個(gè)權(quán)限就可以注冊(cè)通知監(jiān)聽了, 這個(gè)權(quán)限只有系統(tǒng)應(yīng)用才能申請(qǐng), 也就是說(shuō),只要是系統(tǒng)應(yīng)用都可以監(jiān)聽并顯示通知的。 可以寫一個(gè)簡(jiǎn)單的 demo 測(cè)試一下:
一、 申請(qǐng)權(quán)限
<uses-permission android:name="android.permission.STATUS_BAR_SERVICE"/>
二、 在布局中定義一個(gè)容器來(lái)裝遠(yuǎn)程通知視圖

...
     <FrameLayout
         android:layout_width="match_parent"
         android:layout_height="92px"
         android:id="@+id/notification">
 
     </FrameLayout>
     ...
 三、注冊(cè)監(jiān)聽并處理通知顯示邏輯。
protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        final ViewGroup notificationContainer = findViewById(R.id.notification);
        NotificationListenerService listenerService = new NotificationListenerService() {
            @SuppressLint("LongLogTag")
            @Override
            public void onNotificationPosted(StatusBarNotification sbn) {
                super.onNotificationPosted(sbn);
                Log.d("NotificationListenerService", "onNotificationPosted" + sbn);
                if (sbn.getNotification().contentView != null) {
                    View view =  sbn.getNotification().contentView.apply(MainActivity.this, null);
                    notificationContainer.addView(view);
                    view.setVisibility(View.VISIBLE);
                    Log.d("NotificationListenerService", "add contentView");
                }

                if (sbn.getNotification().bigContentView != null) {
                    View view =  sbn.getNotification().bigContentView.apply(MainActivity.this, null);
                    notificationContainer.addView(view);
                    view.setVisibility(View.VISIBLE);
                    Log.d("NotificationListenerService", "add bigContentView");
                }

                if (sbn.getNotification().headsUpContentView != null) {
                    sbn.getNotification().headsUpContentView.apply(MainActivity.this, null);
                    Log.d("NotificationListenerService", "add headsUpContentView");
                }

            }
            @SuppressLint("LongLogTag")
            @Override
            public void onNotificationRemoved(StatusBarNotification sbn) {
                super.onNotificationRemoved(sbn);
                Log.d("NotificationListenerService", "onNotificationRemoved" + sbn);
            }

            @SuppressLint("LongLogTag")
            @Override
            public void onListenerConnected() {
                super.onListenerConnected();
                Log.d("NotificationListenerService", "onNotificationRemoved");
            }

            @Override
            public void onListenerDisconnected() {
                super.onListenerDisconnected();
            }
        };
    // 調(diào)用注冊(cè)方法 registerAsSystemService 不是公開的 API 反射
try {
            Method method =
                    NotificationListenerService.class.getMethod("registerAsSystemService", Context.class, ComponentName.class, int.class);

            method.setAccessible(true);
            method.invoke(listenerService, this,
                    new ComponentName(getPackageName(), getClass().getCanonicalName()),
                    -1);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }

運(yùn)行起來(lái)后,注冊(cè)成功, 然后任意應(yīng)用發(fā)通知, 這里就能顯示出來(lái)了。

應(yīng)用窗口的 flag 是如何狀態(tài)欄?


在系統(tǒng)服務(wù)中,有一個(gè)服務(wù)是專門為 SystemUI 的狀態(tài)欄服務(wù)的, 這個(gè)服務(wù)就是 StatusbarManagerService (簡(jiǎn)稱:SMS),和這個(gè)服務(wù)關(guān)系比較密切的服務(wù)是 WindowManagerService(簡(jiǎn)稱:WMS), SMS 主要管控的是狀態(tài)欄、導(dǎo)航欄, 例如:我們可以設(shè)置全屏、沉浸式狀態(tài)欄都是 SMS 在起作用。 我們看一下 window flag 是如何一步一步的影響系統(tǒng)狀態(tài)欄的。

通常我們這樣添加窗口屬性,例如設(shè)置 flag 讓 SystemUI 狀態(tài)欄支持繪制背景:

Window window = activity.getWindow();
        window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);

我們都知道 Android 系統(tǒng)為我們提供的 window 實(shí)現(xiàn)類是 PhoneWindow (不清楚的可以參考文章:http://www.itdecent.cn/p/b4c23dee9206), flag 其實(shí)被僅僅是 WindowManager.LayoutParams 的一個(gè)標(biāo)記而已。

public void setFlags(int flags, int mask) {
        final WindowManager.LayoutParams attrs = getAttributes();
        attrs.flags = (attrs.flags&~mask) | (flags&mask);
        mForcedWindowFlags |= mask;
        dispatchWindowAttributesChanged(attrs);
    }

所有窗口的 View 和 LayoutParams 最終會(huì)被添加到 WindowManagerService 中,WindowManagerService 會(huì)記錄著窗口信息,包括 flag 屬性 。 (View、Window 和 ViewRootImpl 的關(guān)系:參考:http://www.itdecent.cn/p/47421ec56795
每次窗口布局、焦點(diǎn)發(fā)生變化的時(shí)候,都會(huì)去重新計(jì)算當(dāng)前窗口的屬性, 包括 flag。

public int addWindow(Session session, IWindow client, int seq,
            WindowManager.LayoutParams attrs, int viewVisibility, int displayId,
            Rect outContentInsets, Rect outStableInsets, Rect outOutsets,
            InputChannel outInputChannel) {
            //...省略一堆代碼
            // 計(jì)算屬性
            mPolicy.adjustWindowParamsLw(win.mAttrs);
            //...省略一堆代碼
            updateFocusedWindowLocked(UPDATE_FOCUS_WILL_ASSIGN_LAYERS,
                                    false /*updateInputWindows*/);
        }

mPolicy 是 PhoneWindowManager 的一個(gè)實(shí)例, adjustWindowParamsLw 主要是根據(jù)窗口的屬性來(lái)決定接下來(lái)要展示什么樣的
SystemUI。例如:

@Override
      public void adjustWindowParamsLw(WindowManager.LayoutParams attrs) {
          // 省略一堆代碼
          if ((attrs.flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0
                  || forceWindowDrawsStatusBarBackground
                          && attrs.height == MATCH_PARENT && attrs.width == MATCH_PARENT) {
              attrs.subtreeSystemUiVisibility |= View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
          }
      }

接下來(lái), 會(huì)調(diào)用 PhoneWindowManager 的 focusChangedLw(), 在這里調(diào)用了更新 SystemUI 樣式的方法 updateSystemUiVisibilityLw。

@Override
      public int focusChangedLw(WindowState lastFocus, WindowState newFocus) {
          mFocusedWindow = newFocus;
          if ((updateSystemUiVisibilityLw()&SYSTEM_UI_CHANGING_LAYOUT) != 0) {
              // If the navigation bar has been hidden or shown, we need to do another
              // layout pass to update that window.
              return FINISH_LAYOUT_REDO_LAYOUT;
          }
          return 0;
      }
private int updateSystemUiVisibilityLw() {
          
          mHandler.post(new Runnable() {
                  @Override
                  public void run() {
                      StatusBarManagerInternal statusbar = getStatusBarManagerInternal();
                      if (statusbar != null) {
                          statusbar.setSystemUiVisibility(visibility, fullscreenVisibility,
                                  dockedVisibility, 0xffffffff, fullscreenStackBounds,
                                  dockedStackBounds, win.toString());
                          statusbar.topAppWindowChanged(needsMenu);
                      }
                  }
              });
          return diff;
      }

statusbar 就是 StatusbarManagerService 的一個(gè)實(shí)例。 在 SystemUI 的啟動(dòng)過(guò)程中, SystemUI 會(huì)向 StatusbarManagerService
服務(wù)注冊(cè)一個(gè)回調(diào), 專門用來(lái)接收 StatusbarManagerService 的調(diào)用, 這個(gè)回調(diào)器就是 CommandQueue。

public void setSystemUiVisibility(int vis, int fullscreenStackVis, int dockedStackVis,
              int mask, Rect fullscreenStackBounds, Rect dockedStackBounds) {
          synchronized (mLock) {
              // Don't coalesce these, since it might have one time flags set such as
              // STATUS_BAR_UNHIDE which might get lost.
              SomeArgs args = SomeArgs.obtain();
              args.argi1 = vis;
              args.argi2 = fullscreenStackVis;
              args.argi3 = dockedStackVis;
              args.argi4 = mask;
              args.arg1 = fullscreenStackBounds;
              args.arg2 = dockedStackBounds;
              mHandler.obtainMessage(MSG_SET_SYSTEMUI_VISIBILITY, args).sendToTarget();
          }
      }

CommandQueue 收到調(diào)用之后就會(huì)將消息發(fā)送到 SystemUI 的視圖, 視圖再根據(jù)收到的 vis 屬性改變樣式。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • RemoteViews詳細(xì)解釋 原載于:RemoteViews詳細(xì)解釋 說(shuō)明 想要完全的理解RetmoteView...
    simaxiaochen閱讀 3,691評(píng)論 0 6
  • 自己總結(jié)的Android開源項(xiàng)目及庫(kù)。 github排名https://github.com/trending,g...
    passiontim閱讀 2,765評(píng)論 1 26
  • 那一年 臉龐青澀,星目如電 背上行囊第一次遠(yuǎn)離家鄉(xiāng) 三步一回頭的少年 淚水忍不住奪眶而出 他鄉(xiāng)的街道上我腳步踉蹌 ...
    豫西笑笑生閱讀 403評(píng)論 0 1
  • 課題:水果靜物組合練習(xí) [太陽(yáng)]授課老師:何楠楠 老師電話:18180473126 班型:素描綜合創(chuàng)意 [太陽(yáng)]虹...
    楠楠_3600閱讀 963評(píng)論 0 0
  • 一名孤獨(dú)的母親 留守在老屋 土地被流轉(zhuǎn)租賃 子女老伴務(wù)工千里之外 只有貓相伴 蹣跚的身影踱步在田間 背回青菜 邊喂...
    如影悠然閱讀 255評(píng)論 0 0

友情鏈接更多精彩內(nèi)容