Android 帶你徹底理解 Window 和 WindowManager
有時候我們需要在桌面上顯示一個類似懸浮窗的東西,這種效果就需要用 Window 來實現,Window 是一個抽象類,表示一個視窗,它的具體實現類是 PhoneWindow,實現位於 WindowManagerService 中。相信看到 WindowManagerService 你會有點眼熟,剛接觸 Android 時幾乎所有人都看到過這樣一張圖:
WindowManagerService
WindowManagerService 就是位於 Framework 層的視窗管理服務,它的職責就是管理系統中的所有視窗。視窗的本質是什麼呢?其實就是一塊顯示區域,在 Android 中就是繪製的畫布:Surface,當一塊 Surface 顯示在螢幕上時,就是使用者所看到的視窗了。WindowManagerService 新增一個視窗的過程,其實就是 WindowManagerService 為其分配一塊 Surface 的過程,一塊塊的 Surface 在 WindowManagerService 的管理下有序的排列在螢幕上,Android 才得以呈現出多姿多彩的介面。於是根據對 Surface 的操作型別可以將 Android 的顯示系統分為三個層次,如下圖:
一般的開發過程中,我們操作的是 UI 框架層,對 Window 的操作通過 WindowManager 即可完成,而 WindowManagerService 作為系統級服務執行在一個單獨的程序,所以 WindowManager 和 WindowManagerService 的互動是一個 IPC 過程。
Window 分類
Window 有三種類型,分別是應用 Window、子 Window 和系統 Window。應用類 Window 對應一個 Acitivity,子 Window 不能單獨存在,需要依附在特定的父 Window 中,比如常見的一些 Dialog 就是一個子 Window。系統 Window是需要宣告許可權才能建立的 Window,比如 Toast 和系統狀態列都是系統 Window。
Window 是分層的,每個 Window 都有對應的 z-ordered,層級大的會覆蓋在層級小的 Window 上面,這和 HTML 中的 z-index 概念是完全一致的。在三種 Window 中,應用 Window 層級範圍是 1~99,子 Window 層級範圍是 1000~1999,系統 Window 層級範圍是 2000~2999,我們可以用一個表格來直觀的表示:
Window | 層級 |
---|---|
應用 Window | 1~99 |
子 Window | 1000~1999 |
系統 Window | 2000~2999 |
這些層級範圍對應著 WindowManager.LayoutParams 的 type 引數,如果想要 Window 位於所有 Window 的最頂層,那麼採用較大的層級即可,很顯然系統 Window 的層級是最大的,當我們採用系統層級時,需要宣告許可權。
WindowManager 使用
我們對 Window 的操作是通過 WindowManager 來完成的,WindowManager 是一個介面,它繼承自只有三個方法的 ViewManager 介面:
public interface ViewManager{
public void addView(View view, ViewGroup.LayoutParams params);
public void updateViewLayout(View view, ViewGroup.LayoutParams params);
public void removeView(View view);
}
這三個方法其實就是 WindowManager 對外提供的主要功能,即新增 View、更新 View 和刪除 View。接下來來看一個通過 WindowManager 新增 Window 的例子,程式碼如下:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Button floatingButton = new Button(this);
floatingButton.setText("button");
WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(
WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.WRAP_CONTENT,
0, 0,
PixelFormat.TRANSPARENT
);
// flag 設定 Window 屬性
layoutParams.flags= WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
// type 設定 Window 類別(層級)
layoutParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY;
layoutParams.gravity = Gravity.CENTER;
WindowManager windowManager = getWindowManager();
windowManager.addView(floatingButton, layoutParams);
}
}
程式碼中並沒有呼叫 Activity 的 setContentView 方法,而是直接通過 WindowManager 新增 Window,其中設定為系統 Window,所以應該新增許可權:
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
效果如下:
第二個介面是鎖屏介面,由於按鈕是處於較大層級的系統 Window 中的,所以可以看到 button。
WindowManager 的內部機制
在實際使用中無法直接訪問 Window,對 Window 的訪問必須通過 WindowManager。WindowManager 提供的三個介面方法 addView、updateViewLayout 以及 removeView 都是針對 View 的,這說明 View 才是 Window 存在的實體,上面例子實現了 Window 的新增,WindowManager 是一個介面,它的真正實現是 WindowManagerImpl 類:
@Override
public void addView(View view, ViewGroup.LayoutParams params){
mGlobal.addView(view, params, mDisplay, mParentWindow);
}
@Override
public void updateViewLayout(View view, ViewGroup.LayoutParams params){
mGlobal.updateViewLayout(view, params);
}
@Override
public void removeView(View view){
mGlobal.removeView(view, false);
}
可以看到,WindowManagerImpl 並沒有直接實現 Window 的三大操作,而是交給了 WindowManagerGlobal 來處理,下面以 addView 為例,分析一下 WindowManagerGlobal 中的實現過程:
1、檢查引數合法性,如果是子 Window 做適當調整
if(view == null){
throw new IllegalArgumentException("view must not be null");
}
if(display == null){
throw new IllegalArgumentException("display must not be null");
}
if(!(params instanceof WindowManager.LayoutParams)){
throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
}
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;
if(parentWindow != null){
parentWindow.adjustLayoutParamsForSubWindow(wparams);
}
2、建立 ViewRootImpl 並將 View 新增到集合中
在 WindowManagerGlobal 內部有如下幾個集合比較重要:
private final ArrayList<View> mViews = new ArrayList<View>();
private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();
private final ArrayList<WindowManager.LayoutParams> mParams = new ArrayList<WindowManager.LayoutParams>();
private final ArraySet<View> mDyingViews = new ArraySet<View>();
其中 mViews 儲存的是所有 Window 所對應的 View,mRoots 儲存的是所有 Window 所對應的 ViewRootImpl,mParams 儲存的是所有 Window 所對應的佈局引數,mDyingViews 儲存了那些正在被刪除的 View 物件,或者說是那些已經呼叫了 removeView 方法但是操作刪除還未完成的 Window 物件,可以通過表格直觀的表示:
集合 | 儲存內容 |
---|---|
mViews | Window 所對應的 View |
mRoots | Window 所對應的 ViewRootImpl |
mParams | Window 所對應的佈局引數 |
mDyingViews | 正在被刪除的 View 物件 |
addView 操作時會將相關物件新增到對應集合中:
root = new ViewRootImpl(view.getContext(),display);
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
3、通過 ViewRootImpl 來更新介面並完成 Window 的新增過程
在學習 View 的工作原理時,我們知道 View 的繪製過程是由 ViewRootImpl 來完成的,這裡當然也不例外,具體是通過 ViewRootImpl 的 setView 方法來實現的。在 setView 內部會通過 requestLayout 來完成非同步重新整理請求,如下:
public void requestLayout(){
if(!mHandingLayoutInLayoutRequest){
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
可以看到 scheduleTraversals 方法是 View 繪製的入口,繼續檢視它的實現:
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes, getHostVisibility(),
mDisplay.getDisplayId(),mAttachInfo.mContentInsets, mInputChannel);
mWindowSession 的型別是 IWindowSession,它是一個 Binder 物件,真正的實現類是 Session,這也就是之前提到的 IPC 呼叫的位置。在 Session 內部會通過 WindowManagerService 來實現 Window 的新增,程式碼如下:
public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams, attrs, int viewVisibility,
int displayId, Rect outContentInsets, InputChannel outInputChannel){
return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId, outContentInsets, outInputChannel);
}
終於,Window 的新增請求移交給 WindowManagerService 手上了,在 WindowManagerService 內部會為每一個應用保留一個單獨的 Session,具體 Window 在 WindowManagerService 內部是怎麼新增的,就不對其進一步的分析,因為到此為止我們對 Window 的新增這一從應用層到 Framework 的流程已經清楚了,下面通過圖示總結一下:
理解了 Window 的新增過程,Window 的刪除過程和更新過程都是類似的,也就容易理解了,它們最終都會通過一個 IPC 過程將操作移交給 WindowManagerService 這個位於 Framework 層的視窗管理服務來處理。
Window 的建立過程
View 是 Android 中的檢視的呈現方式,但是 View 不能單獨存在,它必須附著在 Window 這個抽象的概念上面,因此有檢視的地方就有 Window。哪些地方有檢視呢?Android 可以提供檢視的地方有 Activity、Dialog、Toast,除此之外,還有一些依託 Window 而實現的檢視,比如 PopUpWindow(自定義彈出視窗)、選單,它們也是檢視,有檢視的地方就有 Window,因此 Activity、Dialog、Toast 等檢視都對應著一個 Window。這也是面試中常問到的一個知識點:一個應用中有多少個 Window?下面分別分析 Activity、Dialog以及 Toast 的 Window 建立過程。
1、 Activity 的 Window 建立過程
在瞭解了 Window 的概念及意義後,我們自然就清楚 Activity 的 Window 建立時機,Window 本質就是一塊顯示區域,所以關於 Activity 的 Window 建立應該發生在 Activity 的啟動過程,Activity 的啟動過程很複雜,最終會由 ActivityThread 中的 performLaunchActivity() 來完成整個啟動過程,在這個方法內部會通過類載入器建立 Activity 的例項物件,並呼叫其 attach 方法為其關聯執行過程中所依賴的一系列上下文環境變數。
Activity 的 Window 建立就發生在 attach 方法裡,系統會建立 Activity 所屬的 Window 物件併為其設定回撥介面,程式碼如下:
mWindow = PolicyManager.makeNewWindow(this);
mWindow.setCallback(this);
mWindow.setOnWindowDismissedCallback(this);
mWindow.getLayoutInflater().setPrivateFactory(this);
...
可以看到, Window 物件的建立是通過 PolicyManager 的 makeNewWindow 方法實現的,由於 Activity 實現了 Window 的 Callback 介面,因此當 Window 接受到外界的狀態改變時就會回撥 Activity 的方法。Callback 介面中的方法很多,有幾個是我們非常熟悉的,如 onAttachedToWindow、onDetachedFromWindow、dispatchTouchEvent 等等。
再回到 Window 的建立,可以看到 Activity 的 Window 是通過 PolicyManager 的一個工廠方法來建立的,但是在 PolicyManager 的實際呼叫中,PolicyManager 的真正實現是 Policy 類,Policy 類中的 makeNewWindow 方法的實現如下:
public Window makeNewWindow(Context context){
return new PhoneWindow(context);
}
可以看出,Window 的具體實現類的確是 PhoneWindow。到這裡 Window 以及建立完成了,下面分析 Activity 的檢視是怎麼附屬到 Window 上的,而 Activity 的檢視由 setContentView 提供,所以從 setContentView 入手,它的原始碼如下:
public void setContentView(int layoutResID){
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
可以看到,Activity 將具體實現交給了 Window,而 Window 的具體實現是 PhoneWindow,所以只需要看 PhoneWindow 的相關邏輯即可,它的處理步驟如下:
(1)、如果沒有 DecorView 就建立一個
DecorView 是 Activity 中的頂級 View,是一個 FrameLayout,一般來說它的內部包含標題欄和內容欄,但是這個會隨著主題的變化而改變,不管怎麼樣,內容欄是一定存在的,並且有固定的 id:”android.R.id.content”,在 PhoneWindow 中,通過 generateDecor 方法建立 DecorView,通過 generateLayout 初始化主題有關佈局。
(2)、將 View 新增到 DecorView 的 mContentParent 中
這一步較為簡單,直接將 Activity 的檢視新增到 DecorView 的 mContentParent 中即可,由此可以理解 Activity 的 setContentView 這個方法的來歷了,為什麼不叫 setView 呢?因為 Activity 的佈局檔案只是被新增到 DecorView 的 mContentParent 中,因此叫 setContentView 更加具體準確。
(3)、回撥 Activity 的 onContentChanged 方法通知 Activity 檢視已經發生改變
前面分析到 Activity 實現了 Window 的 Callback 介面,這裡當 Activity 的檢視已經被新增到 DecorView 的 mContentParent 中了,需要通知 Activity,使其方便做相關的處理。
經過上面的三個步驟,DecorView 已經被建立並初始化完畢,Activity 的佈局檔案也已經成功新增到了 DecorView 的 mContentParent 中,但是這個時候 DecorView 還沒有被 WindowManager 正式新增到 Window 中。在 ActivityThread 的 handleResumeActivity 方法中,首先會呼叫 Acitivy 的 onResume 方法,接著會呼叫 Acitivy 的 makeVisible() 方法,正是在 makeVisible 方法中,DecorView 才真正的完成了顯示過程,到這裡 Activity 的檢視才能被使用者看到,如下:
void makeVisible(){
if(!mWindowAdded){
ViewManager wm = getWindowManager();
wm.addView(mDecor, getWindow().getAttributes());
mWindowAdded = true;
}
mDecor.setVisibility(View.VISIBLE);
}
2、 Dialog 的 Window 建立過程
Dialog 的 Window 的建立過程與 Activity 類似,步驟如下:
(1)、建立 Window
Dialog 中 Window 同樣是通過 PolicyManager 的 makeNewWindow 方法來完成的,建立後的物件也是 PhoneWindow。
(2)、初始化 DecorView 並將 Dialog 的檢視新增到 DecorView 中
這個過程也和 Activity 類似,都是通過 Window 去新增指定佈局檔案:
public void setContentView(int layoutResID){
mWindow.setContentView(layoutResID);
}
(3)、將 DecorView 新增到 Window 中並顯示
在 Dialog 的 show 方法中,會通過 WindowManager 將 DecorView 新增到 Window 中,如下:
mWindowManager.addView(mDecor, 1);
mShowing = true;
從上面三個步驟可以發現,Dialog 的 Window 建立過程和 Activity 建立過程很類似,當 Dialog 關閉時,它會通過 WindowManager 來移除 DecorView。普通的 Dialog 必須採用 Activity 的 Context,如果採用 Application 的 Context 就會報錯。這是因為沒有應用 token 導致的,而應用 token 一般只有 Activity 擁有,另外,系統 Window 比較特殊,可以不需要 token。
3、 Toast 的 Window 建立過程
Toast 與 Dialog 不同,它的工作過程稍顯複雜,首先 Toast 也是基於 Window 來實現的,但是由於 Toast 具有定時取消這一功能,所以系統採用了 Handler。在 Toast 內部有兩類 IPC 過程,一是 Toast 訪問 NotificationManagerService,第二類是 NotificationManagerService 回撥 Toast 裡的 TN 介面。NotificationManagerService 同 WindowManagerService 一樣,都是位於 Framework 層的服務,下面簡稱 NotificationManagerService 為 NMS。
Toast 屬於系統 Window,它內部的檢視可以是系統預設樣式也可以通過 setView 方法自定義 View,不管如何,它們都對應 Toast 的內部成員 mNextView,Toast 提供 show 和 cancel 分別用於顯示和隱藏 Toast,它們內部是一個 IPC 過程,程式碼如下:
public void show() {
if (mNextView == null) {
throw new RuntimeException("setView must have been called");
}
INotificationManager service = getService();
String pkg = mContext.getOpPackageName();
TN tn = mTN;
tn.mNextView = mNextView;
try {
service.enqueueToast(pkg, tn, mDuration);
} catch (RemoteException e) {
// Empty
}
}
public void cancel() {
mTN.hide();
try {
getService().cancelToast(mContext.getPackageName(), mTN);
} catch (RemoteException e) {
// Empty
}
}
可以看到,顯示和隱藏 Toast 都需要通過 NMS 來實現,TN 是一個 Binder 類,當 NMS 處理 Toast 的顯示或隱藏請求時會跨程序回撥 TN 中的方法。由於 TN 執行在 Binder 執行緒池中,所以需要通過 Handler 將其切換到當前執行緒中,這裡的當前執行緒指的是傳送 Toast 請求所在的執行緒。
程式碼在顯示 Toast 中呼叫了 NMS 的 enqueueToast 方法, enqueueToast 方法內部將 Toast 請求封裝為 ToastRecord 物件並將其新增到一個名為 mToastQueue 的佇列中,對於非系統應用來說,mToastQueue 中最多同時存在 50 個 ToastRecord,用於防止 DOS (Denial of Service 拒絕服務)。
當 ToastRecord 新增到 mToastQueue 中後,NMS 就會通過 showNextToastLocked 方法來順序顯示 Toast,但是 Toast 真正的顯示並不是在 NMS 中完成的,而是由 ToastRecord 的 callback 來完成的:
void showNextToastLocked (){
ToastRecord record = mToastQueue.get(0);
while(record != null){
if(DBG)
Slog.d(TAG,"show pkg=" + record.pkg + "callback=" + record.callback);
try{
record.callback.show();
scheduleTimeoutLocked(record);
return;
}
...
}
這個 callback 就是 Toast 中的 TN 物件的遠端 Binder,最終被呼叫的 TN 中的方法會執行在發起 Toast 請求的應用的 Binder 執行緒池中,從以上程式碼可以看出,Toast 顯示以後,NMS 還呼叫了 sheduleTimeoutLocked 方法,此方法中首先進行延時,具體的延時時長取決於 Toast 的顯示時長,延遲相應時間後,NMS 會通過 cancelToastLocked 方法來隱藏 Toast 並將它從 mToastQueue 中移除,這時如果 mToastQueue 中還有其他 Toast,那麼 NMS 就繼續顯示其他 Toast。Toast 的隱藏也是通過 ToastRecord 的 callback 來完成的,同樣也是一次 IPC 過程。
從上面的分析,可以知道 NMS 只是起到了管理 Toast 佇列及其延時的效果,Toast 的顯示和隱藏過程實際上是通過 Toast 的 TN 類來實現的,TN 類的兩個方法 show 和 hide,是被 NMS 以跨程序的方式呼叫的,因此它們執行在 Binder 執行緒池中,為了將執行環境切換到 Toast 請求所在的執行緒,在它們內部使用了 Handler。
Toast 畢竟是要在 Window 中實現的,因此它最終還是要依附於 WindowManager,TN 的 handleShow 中程式碼如下:
mWM = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
mWM.addView(mView, mParams);
TN 的 handleHide 方法同樣需要通過 WindowManager 來實現檢視的移除,這裡就不再貼出。
總結
下面讓我們再次認清一些概念:任何 View 都是附屬在一個 Window 上面的,Window 表示一個視窗的概念,也是一個抽象的概念,Window 並不是實際存在的,它是以 View 的形式存在的。WindowManager 是外界也就是我們訪問 Window 的入口,Window 的具體實現位於 WindowManagerService 中,WindowManagerService 和 WindowManager 的互動是一個 IPC 過程。
相信讀完本文後,對 Window 會有一個更加清晰的認識,同時能夠深刻理解 Window 和 View 的依賴關係。
參考文章:
《Android 開發藝術探索》
相關推薦
Android 帶你徹底理解 Window 和 WindowManager
有時候我們需要在桌面上顯示一個類似懸浮窗的東西,這種效果就需要用 Window 來實現,Window 是一個抽象類,表示一個視窗,它的具體實現類是 PhoneWindow,實現位於 WindowManagerService 中。相信看到 WindowManage
帶你徹底理解RSA演算法原理
1. 什麼是RSARSA演算法是現今使用最廣泛的公鑰密碼演算法,也是號稱地球上最安全的加密演算法。在瞭解RSA演算法之前,先熟悉下幾個術語 根據金鑰的使用方法,可以將密碼分為對稱密碼和公鑰密碼 對稱密碼:加密和解密使用同一種金鑰的方式 公鑰密碼:加密和解密使用不同的密碼的方式,因此公鑰密碼通常也稱為非對稱密碼
圖文並茂的帶你徹底理解悲觀鎖與樂觀鎖
這是一篇介紹悲觀鎖和樂觀鎖的入門文章。旨在讓那些不瞭解悲觀鎖和樂觀鎖的小白們弄清楚什麼是悲觀鎖,什麼是樂觀鎖。不同於其他文章,本文
一文帶你徹底理解 JavaScript 原型物件
一、什麼是原型 原型是Javascript中的繼承的基礎,JavaScript的繼承就是基於原型的繼承。 1.1 函式的原型物件 在JavaScript中,我們建立一個函式A(就是宣告一個函式), 那麼瀏覽器就會在記憶體中建立一個物件B,而且每個函式都預設會有一個屬性 prototype 指向了這個物件( 即
簡直不要太硬了!一文帶你徹底理解檔案系統
所有的應用程式都需要儲存和檢索資訊。程序執行時,它能夠在自己的儲存空間記憶體儲一定量的資訊。然而,儲存容量受虛擬地址空間大小的限制。對於一些應用程式來說,儲存空間的大小是充足的,但是對於其他一些應用程式,比如航空訂票系統、銀行系統、企業記賬系統來說,這些容量又顯得太小了。 第二個問題是,當程序終止時資訊會
吐血整理!這篇帶你徹底理解主存中儲存單元地址的分配
在閱讀本文之前,建議沒有基礎的讀者先閱讀下主存的基本組成結構: [五分鐘理解主儲存器的基本組成結構](https://blog.csdn.net/weixin_41695995/article/details/105009429) ## 儲存單元的字地址: 我們來看張圖: ![在這裡插入圖片描述](ht
帶你徹底看懂React Native和Android原生控制元件之間的對映關係
此文基於react natve的 September 2018 - revision 5 版本 本人學校畢業後就當了安卓爬坑專業戶,3年來總算爬習慣了,不料今年掉進了RN這個天坑,從此開始了我的悲慘人生。。。Anyway,RN的思想還是值得學習的,今天就從Android的角度開始分析一下react nati
看完讓你徹底理解 WebSocket 原理,附完整的實戰代碼(包含前端和後端)
tcp 協議 learn php 握手 live 雙向 簡單 再次 註意 1、前言 最近有同學問我有沒有做過在線咨詢功能。同時,公司也剛好讓我接手一個 IM 項目。所以今天抽時間記錄一下最近學習的內容。本文主要剖析了 WebSocket 的原理,以及附上一個完整的聊天室實戰
全方位帶你徹底搞懂Android記憶體洩露
1Java記憶體回收方式 Java判斷物件是否可以回收使用的而是可達性分析演算法。 在主流的商用程式語言中(Java和C#),都是使用可達性分析演算法判斷物件是否存活的。
帶你深入理解STL之Stack和Queue
上一篇部落格,帶你深入理解STL之Deque容器中詳細介紹了deque容器的原始碼實現方式。結合前面介紹的兩個容器vector和list,在使用的過程中,我們確實要知道在什麼情況下需要選擇恰當的容器來滿足需求和提升效率。一般選擇的準則有如下幾條: 如果需要隨
Android冷啟動白屏解析,帶你一步步分析和解決問題
本文同步發表於我的微信公眾號,掃一掃文章底部的二維碼或在微信搜尋 郭霖 即可關注,每天都有文章更新。 寫在前面 記得在本月初,我發表了一篇文章叫《 Android Studio新功能解析,你真的瞭解Instant Run嗎?》,裡面詳細講解了
Window和WindowManager(Android開發藝術探索學習筆記)
概述 Window表示一個視窗的概念,它是一個抽象類,它的具體實現類是PhoneWindow。 WindowManager是外界訪問Window的入口,Window的具體實現位於WindowManagerService中。 WindowManager和Wi
帶你徹底明白 Android Studio 打包混淆
前言在使用Android Studio混淆打包時,該IDE自身集成了Java語言的ProGuard作為壓縮,優化和混淆工具,配合Gradle構建工具使用很簡單。只需要在工程應用目錄的gradle檔案中設定minifyEnabled為true即可。然後我們就可以到proguar
Android狀態列微技巧,帶你真正理解沉浸式模式
本文同步發表於我的微信公眾號,掃一掃文章底部的二維碼或在微信搜尋 郭霖 即可關注,每天都有文章更新。 記得之前有朋友在留言裡讓我寫一篇關於沉浸式狀態列的文章,正巧我確實有這個打算,那麼本篇就給大家帶來一次沉浸式狀態列的微技巧講解。 其實說到沉浸式狀態列這個名字我也是感到很無奈,真不知道這種叫法是誰先發
android中window和windowManager原始碼分析(android-api-23)
一、前言 在android中window無處不在,如activity、dialog、toast等。它是view所依附的載體,每個window都對應於一個View和一個ViewRootImpl。ViewRootImpl就是Window和view的連線紐帶。windowMana
Android視窗機制(三)Window和WindowManager的建立與Activity
Android視窗機制系列 Android視窗機制(一)初識Android的視
帶你深入理解Android中的自定義屬性!!!
att omv world 過程 參數 and pla 開發 dimen 引言 對於自定義屬性,大家肯定都不陌生,遵循以下幾步,就可以實現: 1.自定義一個CustomView(extends View )類 2.編寫values/attrs.xml,在其中編寫styl
一篇文章讓你徹底理解java中抽象類和介面
目錄 1、我所理解的抽象類 2、我所理解的介面 3、抽象類和介面本質區別 相信大家都有這種感覺:抽象類與介面這兩者有太多相似的地方,又有太多不同的地方。往往這二者可以讓初學者摸不著頭腦,無論是在
setTimeout和setImmediate到底誰先執行,本文讓你徹底理解Event Loop
筆者以前面試的時候經常遇到寫一堆setTimeout,setImmediate來問哪個先執行。本文主要就是來講這個問題的,但是不是簡單的講講哪個先,哪個後。籠統的知道setImmediate比setTimeout(fn, 0)先執行是不夠的,因為有些情況下setTimeout(fn, 0)是會比setImme
教你寫Http框架(二)——三個樣例帶你深入理解AsyncTask
func implement oncreate 其它 層疊 worker dcl 例如 人員 這個標題大家不要奇怪,扯Http框架怎麽扯到AsyncTask去了,有兩個原因:首先是Http框架除了核心http理論外。其技術實現核心也是線程池 + 模板 +