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

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

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

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

SystemUI 創(chuàng)建流程漱挎?

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

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

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

SystemUI 有哪內(nèi)容

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

SystemUI 也是一個應(yīng)用抽活,不過這個應(yīng)用特殊之處在于他沒有啟動圖標(biāo)硫戈、也沒有入口 Activity 。他的入口程序是一個服務(wù):SystemUIService下硕。 這個服務(wù)會被系統(tǒng)服務(wù)拉起來丁逝, 這個服務(wù)起來汁胆, SystemUI 應(yīng)用進程就創(chuàng)建起來了,具體啟動過程后面會分析霜幼。除了 SystemUIService 嫩码, SystemUI 還有很多服務(wù), 例如: 負(fù)責(zé)鎖屏的KeyguardService罪既、負(fù)責(zé)最近任務(wù)的 RecentsSystemUserService铸题、負(fù)責(zé)壁紙的 ImageWallpaper 、負(fù)責(zé)截屏的TakeScreenshotService 等琢感。

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

如果要做系統(tǒng)移植, SystemUI 改造這塊的資料還是挺少驹针,大部分情況下都是啃源碼烘挫,連蒙帶猜的修改,然后再編譯出來驗證柬甥。通常我們會從布局著手看看哪個布局長得像就著手去改饮六,不過這塊完全是可以沉淀一下經(jīng)驗出來讓后人去節(jié)省時間的。這里我也不再贅述了苛蒲, 有人已經(jīng)梳理過了喜滨, 我借花獻佛吧:https://blog.csdn.net/azhengye/article/details/50419409

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

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

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

失敗方案1

IDE獨立編譯 SystemUI 茎毁, 把 SystemUI 所依賴的系統(tǒng) jar 都拷貝帶 IDE 下克懊,使用 provided 方式依賴。 在 6.0 以下版本還勉強可行 七蜘, 8.0 以后就基本不可能了谭溉, 8.0 以后 SystemUI 合入了鎖屏模塊,依賴了太多的系統(tǒng)資源橡卤, 編譯不過是一個問題扮念, 就算編譯過了, 所依賴的系統(tǒng)資源 ID 也會不一致碧库。 經(jīng)過 1~2 兩天的嘗試柜与, 這個方案失敗了巧勤。

失敗方案2

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

最終方案


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

為了提高效率儒搭, 使用一臺昨晚編譯機, 另一臺作為編輯機芙贫, 通過 ssh 搭建通道配合完成開發(fā)搂鲫、編譯、安裝三個流程磺平。

SystemUI 是如何啟動的魂仍?

前面介紹過 SystemUIService 是 SystemUI 的入庫程序。 SystemUIService 是在服務(wù)進程中啟動的拣挪,我們來看下源碼:

SystemServer.java 中 SystemServer 是 zygote 進程起來的啟動的第一個服務(wù)擦酌, 然后在這個服務(wù)的 run 方法方法中會一次啟動 Android 系統(tǒng)服務(wù)。

private void run() {
    
         // ... 省略一堆代碼
         startBootstrapServices();
         startCoreServices();
         startOtherServices();
         // ... 省略一堆代碼
    
    }
 其中 AMS 是在 startOtherServices() 這個方法中啟動的:
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 啟動啟動完成之后菠劝,會回調(diào)一個 systemReady() 傳遞進去的方法赊舶, 在其中調(diào)用 startSystemUi() 方法啟動了 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)簡單, 啟動之后主要調(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 中啟動了 SystemUI 的各個 UI 模塊:

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

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

通知視圖是如何夸進程顯示的寓调?

跨進程通訊的基礎(chǔ)是 IPC ,通知服務(wù)(NotificationManagerService, 簡稱 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 作為通知的接收放需要注冊監(jiān)聽器 INotificationListener 是監(jiān)聽通通知的一個 AIDL 接口颖侄,
    NotificationListenerService 是一個監(jiān)聽管理服務(wù)炬太,他的內(nèi)部類 NotificationListenerWrapper 實現(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) {
                    // 刪除通知
                          ....
                     // 省略了很多代碼
            }

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

@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 有個NotificationListenerWithPlugins 類繼承了 NotificationListenerService

類。 并在 SystemUI 進程起來的時候調(diào)用 registerAsSystemService() 方法完成了注冊:

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

這樣通道就建立起來了炒考。

消息傳遞過程可缚,大家可以按照這個思路器走讀源碼

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

RemoteViews

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

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

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

RemoteViews 也實現(xiàn)了 Parcelable 接口, 主要是封裝了通知欄要展示的視圖信息戈次, 例如轩勘, 應(yīng)用包名、布局ID怯邪。我們都知道實現(xiàn)了 Parcelable 這個接口就可以在 IPC 通道上夸進程傳遞绊寻。 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 攜帶了視圖信息悬秉, 進程間傳遞的并不是真實的視圖對象澄步, 而主要是布局的 id ,那么顯示在通知欄上的視圖對象又是如何創(chuàng)建出來的呢和泌?

通知視圖創(chuàng)建

在通知的接收端創(chuàng)建的村缸,上文說過 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  這個方法中會去檢查布局是否要加載, **其實我們比較好奇的是布局資源在應(yīng)用進程中县恕,

SystemUI 如何加載遠程進程的布局資源索烹?**
有兩個關(guān)鍵的信息: 包名、布局ID弱睦。知道了包名 SystemUI 進程是有權(quán)限創(chuàng)建對應(yīng)包名的上下文對象的,進而可以拿到對應(yīng)應(yīng)用的
資源管理器渊额, 然后就可以加載布局資源創(chuàng)建對象了况木。 maybePopulateRemoteViews 方法跟蹤下去, 會走到 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 對象就是通過應(yīng)用包名創(chuàng)建的上下文對象旬迹,創(chuàng)建過程:

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)限注冊通知監(jiān)聽的應(yīng)用都可以奔垦。 具體權(quán)限是: <uses-permission android:name="android.permission.STATUS_BAR_SERVICE"/>
只要應(yīng)用有這個權(quán)限就可以注冊通知監(jiān)聽了屹耐, 這個權(quán)限只有系統(tǒng)應(yīng)用才能申請, 也就是說椿猎,只要是系統(tǒng)應(yīng)用都可以監(jiān)聽并顯示通知的惶岭。 可以寫一個簡單的 demo 測試一下:
一寿弱、 申請權(quán)限
<uses-permission android:name="android.permission.STATUS_BAR_SERVICE"/>
二、 在布局中定義一個容器來裝遠程通知視圖

...
     <FrameLayout
         android:layout_width="match_parent"
         android:layout_height="92px"
         android:id="@+id/notification">
 
     </FrameLayout>
     ...
 三按灶、注冊監(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)用注冊方法 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īng)用發(fā)通知噪矛, 這里就能顯示出來了。

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


在系統(tǒng)服務(wù)中艇挨,有一個服務(wù)是專門為 SystemUI 的狀態(tài)欄服務(wù)的, 這個服務(wù)就是 StatusbarManagerService (簡稱:SMS)韭赘,和這個服務(wù)關(guān)系比較密切的服務(wù)是 WindowManagerService(簡稱: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 實現(xiàn)類是 PhoneWindow (不清楚的可以參考文章:http://www.reibang.com/p/b4c23dee9206), flag 其實被僅僅是 WindowManager.LayoutParams 的一個標(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 最終會被添加到 WindowManagerService 中刃泡,WindowManagerService 會記錄著窗口信息,包括 flag 屬性 碉怔。 (View烘贴、Window 和 ViewRootImpl 的關(guān)系:參考:http://www.reibang.com/p/47421ec56795
每次窗口布局、焦點發(fā)生變化的時候撮胧,都會去重新計算當(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) {
            //...省略一堆代碼
            // 計算屬性
            mPolicy.adjustWindowParamsLw(win.mAttrs);
            //...省略一堆代碼
            updateFocusedWindowLocked(UPDATE_FOCUS_WILL_ASSIGN_LAYERS,
                                    false /*updateInputWindows*/);
        }

mPolicy 是 PhoneWindowManager 的一個實例芹啥, adjustWindowParamsLw 主要是根據(jù)窗口的屬性來決定接下來要展示什么樣的
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;
          }
      }

接下來, 會調(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 的一個實例。 在 SystemUI 的啟動過程中傀履, SystemUI 會向 StatusbarManagerService
服務(wù)注冊一個回調(diào)虱朵, 專門用來接收 StatusbarManagerService 的調(diào)用, 這個回調(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)用之后就會將消息發(fā)送到 SystemUI 的視圖碴犬, 視圖再根據(jù)收到的 vis 屬性改變樣式絮宁。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市翅敌,隨后出現(xiàn)的幾起案子羞福,更是在濱河造成了極大的恐慌,老刑警劉巖蚯涮,帶你破解...
    沈念sama閱讀 218,858評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件治专,死亡現(xiàn)場離奇詭異,居然都是意外死亡遭顶,警方通過查閱死者的電腦和手機张峰,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來棒旗,“玉大人喘批,你說我怎么就攤上這事∠橙啵” “怎么了饶深?”我有些...
    開封第一講書人閱讀 165,282評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長逛拱。 經(jīng)常有香客問我敌厘,道長,這世上最難降的妖魔是什么朽合? 我笑而不...
    開封第一講書人閱讀 58,842評論 1 295
  • 正文 為了忘掉前任俱两,我火速辦了婚禮,結(jié)果婚禮上曹步,老公的妹妹穿的比我還像新娘宪彩。我一直安慰自己,他們只是感情好讲婚,可當(dāng)我...
    茶點故事閱讀 67,857評論 6 392
  • 文/花漫 我一把揭開白布尿孔。 她就那樣靜靜地躺著,像睡著了一般筹麸。 火紅的嫁衣襯著肌膚如雪纳猫。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,679評論 1 305
  • 那天竹捉,我揣著相機與錄音,去河邊找鬼尚骄。 笑死块差,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播憨闰,決...
    沈念sama閱讀 40,406評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼状蜗,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了鹉动?” 一聲冷哼從身側(cè)響起轧坎,我...
    開封第一講書人閱讀 39,311評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎泽示,沒想到半個月后缸血,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,767評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡械筛,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年捎泻,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片埋哟。...
    茶點故事閱讀 40,090評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡笆豁,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出赤赊,到底是詐尸還是另有隱情闯狱,我是刑警寧澤,帶...
    沈念sama閱讀 35,785評論 5 346
  • 正文 年R本政府宣布抛计,位于F島的核電站哄孤,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏爷辱。R本人自食惡果不足惜录豺,卻給世界環(huán)境...
    茶點故事閱讀 41,420評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望饭弓。 院中可真熱鬧双饥,春花似錦、人聲如沸弟断。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽阀趴。三九已至昏翰,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間刘急,已是汗流浹背棚菊。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評論 1 271
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留叔汁,地道東北人统求。 一個月前我還...
    沈念sama閱讀 48,298評論 3 372
  • 正文 我出身青樓检碗,卻偏偏與公主長得像,于是被迫代替她去往敵國和親码邻。 傳聞我的和親對象是個殘疾皇子折剃,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,033評論 2 355

推薦閱讀更多精彩內(nèi)容

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