android 開發零起步學習筆記(二十二):ANDROID應用ACTIVITY、DIALOG、POPWINDOW、TOAST窗口添加機制及源碼分析(二)



第二部分:html


4  Android應用PopWindow窗口添加顯示機制源碼

PopWindow實質就是彈出式菜單,它與Dialag不一樣的地方是不會使依賴的Activity組件失去焦點(PopupWindow彈出後可 以繼續與依賴的Activity進行交互),Dialog卻不能這樣。同時PopupWindow與Dialog另外一個不一樣點是PopupWindow是 一個阻塞的對話框,若是你直接在Activity的onCreate等方法中顯示它則會報錯,因此PopupWindow必須在某個事件中顯示地或者是開 啓一個新線程去調用。java

說這麼多仍是直接看代碼吧。android

4-1  PopWindow窗口源碼分析

依據PopWindow的使用,咱們選擇最經常使用的方式來分析,以下先看其中經常使用的一種構造函數:git

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class PopupWindow {
     ......
     //咱們只分析最經常使用的一種構造函數
     public PopupWindow(View contentView, int width, int height, boolean focusable) {
         if  (contentView !=  null ) {
             //獲取mContext,contentView實質是View,View的mContext都是構造函數傳入的,View又層級傳遞,因此最終這個mContext實質是Activity!!!很重要
             mContext = contentView.getContext();
             //獲取Activity的getSystemService的WindowManager
             mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
         }
         //進行一些Window類的成員變量初始化賦值操做
         setContentView(contentView);
         setWidth(width);
         setHeight(height);
         setFocusable(focusable);
     }
     ......
}

能夠看見,構造函數只是初始化了一些變量,看完構造函數繼續看下PopWindow的展現函數,以下:github

1
2
3
4
5
6
7
8
9
10
11
     public void showAsDropDown(View anchor, int xoff, int yoff, int gravity) {
         ......
         //anchor是Activity中PopWindow準備依附的View,這個View的token實質也是Activity的Window中的token,也即Activity的token
         //第一步   初始化WindowManager.LayoutParams
         WindowManager.LayoutParams p = createPopupLayout(anchor.getWindowToken());
         //第二步
         preparePopup(p);
         ......
         //第三步
         invokePopup(p);
     }

能夠看見,當咱們想將PopWindow展現在anchor的下方向(Z軸是在anchor的上面)旁邊時經理了上面三步,咱們一步一步來分析,先看第一步,源碼以下:app

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
     private WindowManager.LayoutParams createPopupLayout(IBinder token) {
         //實例化一個默認的WindowManager.LayoutParams,其中type=TYPE_APPLICATION
         WindowManager.LayoutParams p =  new  WindowManager.LayoutParams();
         //設置Gravity
         p.gravity = Gravity.START | Gravity.TOP;
         //設置寬高
         p.width = mLastWidth = mWidth;
         p.height = mLastHeight = mHeight;
         //依據背景設置format
         if  (mBackground !=  null ) {
             p.format = mBackground.getOpacity();
         else  {
             p.format = PixelFormat.TRANSLUCENT;
         }
         //設置flags
         p.flags = computeFlags(p.flags);
         //修改type=WindowManager.LayoutParams.TYPE_APPLICATION_PANEL,mWindowLayoutType有初始值,type類型爲子窗口
         p.type = mWindowLayoutType;
         //設置token爲Activity的token
         p.token = token;
         ......
         return  p;
     }

接着回到showAsDropDown方法看看第二步,以下源碼:異步

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
     private void preparePopup(WindowManager.LayoutParams p) {
         ......
         //有無設置PopWindow的background區別
         if  (mBackground !=  null ) {
             ......
             //若是有背景則建立一個PopupViewContainer對象的ViewGroup
             PopupViewContainer popupViewContainer =  new  PopupViewContainer(mContext);
             PopupViewContainer.LayoutParams listParams =  new  PopupViewContainer.LayoutParams(
                     ViewGroup.LayoutParams.MATCH_PARENT, height
             );
             //把背景設置給PopupViewContainer的ViewGroup
             popupViewContainer.setBackground(mBackground);
             //把咱們構造函數傳入的View添加到這個ViewGroup
             popupViewContainer.addView(mContentView, listParams);
             //返回這個ViewGroup
             mPopupView = popupViewContainer;
         else  {
             //若是沒有經過PopWindow的setBackgroundDrawable設置背景則直接賦值當前傳入的View爲PopWindow的View
             mPopupView = mContentView;
         }
         ......
     }

能夠看見preparePopup方法的做用就是判斷設置View,若是有背景則會在傳入的contentView外面包一層 PopupViewContainer(實質是一個重寫了事件處理的FrameLayout)以後做爲mPopupView,若是沒有背景則直接用 contentView做爲mPopupView。咱們再來看下這裏的PopupViewContainer類,以下源碼:ide

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
     private class PopupViewContainer extends FrameLayout {
         ......
         @Override
         protected int[] onCreateDrawableState(int extraSpace) {
             ......
         }
 
         @Override
         public boolean dispatchKeyEvent(KeyEvent event) {
             ......
         }
 
         @Override
         public boolean dispatchTouchEvent(MotionEvent ev) {
             if  (mTouchInterceptor !=  null  && mTouchInterceptor.onTouch( this , ev)) {
                 return  true ;
             }
             return  super .dispatchTouchEvent(ev);
         }
 
         @Override
         public boolean onTouchEvent(MotionEvent event) {
             ......
             if (xxx) {
                 dismiss();
             }
             ......
         }
 
         @Override
         public void sendAccessibilityEvent(int eventType) {
             ......
         }
     }

能夠看見,這個PopupViewContainer是一個PopWindow的內部私有類,它繼承了FrameLayout,在其中重寫了Key 和Touch事件的分發處理邏輯。同時查閱PopupView能夠發現,PopupView類自身沒有重寫Key和Touch事件的處理,因此若是沒有將 傳入的View對象放入封裝的ViewGroup中,則點擊Back鍵或者PopWindow之外的區域PopWindow是不會消失的(其實PopWindow中沒有向Activity及Dialog同樣new新的Window,因此不會有新的callback設置,也就無法處理事件消費了)。函數

接着繼續回到showAsDropDown方法看看第三步,以下源碼:oop

1
2
3
4
5
6
7
8
     private void invokePopup(WindowManager.LayoutParams p) {
         if  (mContext !=  null ) {
             p.packageName = mContext.getPackageName();
         }
         mPopupView.setFitsSystemWindows(mLayoutInsetDecor);
         setLayoutDirectionFromAnchor();
         mWindowManager.addView(mPopupView, p);
     }

能夠看見,這裏使用了Activity的WindowManager將咱們的PopWindow進行了顯示。

到此能夠發現,PopWindow的實質無非也是使用WindowManager的addView、updateViewLayout、 removeView進行一些操做展現。與Dialog不一樣的地方是沒有新new Window而已(也就無法設置callback,沒法消費事件,也就是前面說的PopupWindow彈出後能夠繼續與依賴的Activity進行交互 的緣由)。

到此PopWindw的窗口加載顯示機制就分析完畢了,接下來進行總結與應用開發技巧提示。

4-2  PopWindow窗口源碼分析總結及應用開發技巧提示

經過上面分析能夠發現總結以下圖:

能夠看見,PopWindow徹底使用了Activity的Window與WindowManager,相對來講比較簡單容易記理解。

再來看一個開發技巧:

若是設置了PopupWindow的background,則點擊Back鍵或者點擊PopupWindow之外的區域時PopupWindow就 會dismiss;若是不設置PopupWindow的background,則點擊Back鍵或者點擊PopupWindow之外的區域 PopupWindow不會消失。

5  Android應用Toast窗口添加顯示機制源碼

5-1 基礎知識準備

在開始分析這幾個窗口以前須要腦補一點東東,咱們從應用層開發來直觀腦補,這樣下面分析源碼時就不蛋疼了。以下是一個咱們寫的兩個應用實現 Service跨進程調用服務ADIL的例子,客戶端調運遠程Service的start與stop方法控制遠程Service的操做。

Android系統中的應用程序都運行在各自的進程中,進程之間是沒法直接交換數據的,可是Android爲開發者提供了AIDL跨進程調用Service的功能。其實AIDL就至關於雙方約定的一個規則而已。

先看下在Android Studio中AIDL開發的工程目錄結構,以下:

因爲AIDL文件中不能出現訪問修飾符(如public),同時AIDL文件在兩個項目中要徹底一致並且只支持基本類型,因此咱們定義的AIDL文件以下:

ITestService.aidl

1
2
3
4
5
6
package io.github.yanbober.myapplication;
 
interface ITestService {
     void start(int id);
     void stop(int id);
}

再來看下依據aidl文件自動生成的ITestService.java文件吧,以下:

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
/*
  * This file is auto-generated.  DO NOT MODIFY.
  */
package io.github.yanbober.myapplication;
public interface ITestService extends android.os.IInterface
{
     //Stub類是ITestService接口的內部靜態抽象類,該類繼承了Binder類
     public static abstract class Stub extends android.os.Binder implements io.github.yanbober.myapplication.ITestService
     {
         ......
         //這是抽象靜態Stub類中的asInterface方法,該方法負責將service返回至client的對象轉換爲ITestService.Stub
         //把遠程Service的Binder對象傳遞進去,獲得的是遠程服務的本地代理
         public static io.github.yanbober.myapplication.ITestService asInterface(android.os.IBinder obj)
         {
             ......
         }
         ......
         //遠程服務的本地代理,也會繼承自ITestService
         private static class Proxy implements io.github.yanbober.myapplication.ITestService
         {
             ......
             @Override
             public void start(int id) throws android.os.RemoteException
             {
                 ......
             }
 
             @Override
             public void stop(int id) throws android.os.RemoteException
             {
                 ......
             }
         }
         ......
     }
     //兩個方法是aidl文件中定義的方法
     public void start(int id) throws android.os.RemoteException;
     public void stop(int id) throws android.os.RemoteException;
}

這就是自動生成的java文件,接下來咱們看看服務端的Service源碼,以下:

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
//記得在AndroidManifet.xml中註冊Service的<action android:name="io.github.yanbober.myapplication.aidl" />
 
public class TestService extends Service {
     private TestBinder mTestBinder;
 
     //該類繼承ITestService.Stub類而不是Binder類,由於ITestService.Stub是Binder的子類
     //進程內的Service定義TestBinder內部類是繼承Binder類
     public class TestBinder extends ITestService.Stub {
 
         @Override
         public void start(int id) throws RemoteException {
             Log.i( null "Server Service is start!" );
         }
 
         @Override
         public void stop(int id) throws RemoteException {
             Log.i( null "Server Service is stop!" );
         }
     }
 
     @Override
     public IBinder onBind(Intent intent) {
         //返回Binder
         return  mTestBinder;
     }
 
     @Override
     public void onCreate() {
         super .onCreate();
         //實例化Binder
         mTestBinder =  new  TestBinder();
     }
}

如今服務端App的代碼已經OK,咱們來看下客戶端的代碼。客戶端首先也要像上面的工程結構同樣,把AIDL文件放好,接着在客戶端使用遠程服務端的Service代碼以下:

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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
public class MainActivity extends Activity {
     private static final String REMOT_SERVICE_ACTION =  "io.github.yanbober.myapplication.aidl" ;
 
     private Button mStart, mStop;
 
     private ITestService mBinder;
 
     private ServiceConnection connection =  new  ServiceConnection() {
         @Override
         public void onServiceConnected(ComponentName name, IBinder service) {
             //得到另外一個進程中的Service傳遞過來的IBinder對象
             //用IMyService.Stub.asInterface方法轉換該對象
             mBinder = ITestService.Stub.asInterface(service);
         }
 
         @Override
         public void onServiceDisconnected(ComponentName name) {
         }
     };
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super .onCreate(savedInstanceState);
         setContentView(R.layout.activity_main);
 
         mStart = (Button)  this .findViewById(R.id.start);
         mStop = (Button)  this .findViewById(R.id.stop);
 
         mStart.setOnClickListener(clickListener);
         mStop.setOnClickListener(clickListener);
         //綁定遠程跨進程Service
         bindService( new  Intent(REMOT_SERVICE_ACTION), connection, BIND_AUTO_CREATE);
     }
 
     @Override
     protected void onDestroy() {
         super .onDestroy();
         //取消綁定遠程跨進程Service
         unbindService(connection);
     }
 
     private View.OnClickListener clickListener =  new  View.OnClickListener() {
         @Override
         public void onClick(View v) {
             ////調用遠程Service中的start與stop方法
             switch  (v.getId()) {
                 case  R.id.start:
                     try  {
                         mBinder.start(0x110);
                     catch  (RemoteException e) {
                         e.printStackTrace();
                     }
                     break ;
                 case  R.id.stop:
                     try  {
                         mBinder.stop(0x120);
                     catch  (RemoteException e) {
                         e.printStackTrace();
                     }
                     break ;
             }
         }
     };
}

到此你對應用層經過AIDL使用遠程Service的形式已經很熟悉了,至於實質的通訊使用Binder的機制咱們後面會寫文章一步一步往下分析。到此的準備知識已經足夠用來理解下面咱們的源碼分析了。

5-2 Toast窗口源碼分析

咱們經常使用的Toast窗口其實和前面分析的Activity、Dialog、PopWindow都是不一樣的,由於它和輸入法、牆紙相似,都是系統窗口。

咱們仍是按照最經常使用的方式來分析源碼吧。

咱們先看下Toast的靜態makeText方法吧,以下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
     public static Toast makeText(Context context, CharSequence text, @Duration int duration) {
         //new一個Toast對象
         Toast result =  new  Toast(context);
         //獲取前面有篇文章分析的LayoutInflater
         LayoutInflater inflate = (LayoutInflater)
                 context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
         //加載解析Toast的佈局,實質transient_notification.xml是一個LinearLayout中套了一個@android:id/message的TextView而已
         View v = inflate.inflate(com.android.internal.R.layout.transient_notification,  null );
         //取出佈局中的TextView
         TextView tv = (TextView)v.findViewById(com.android.internal.R.id.message);
         //把咱們的文字設置到TextView上
         tv.setText(text);
         //設置一些屬性
         result.mNextView = v;
         result.mDuration = duration;
         //返回新建的Toast
         return  result;
     }

能夠看見,這個方法構造了一個Toast,而後把要顯示的文本放到這個View的TextView中,而後初始化相關屬性後返回這個新的Toast對象。

當咱們有了這個Toast對象以後,

能夠經過show方法來顯示出來,以下看下show方法源碼:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
     public void show() {
         ......
         //經過AIDL(Binder)通訊拿到NotificationManagerService的服務訪問接口,當前Toast類至關於上面例子的客戶端!!!至關重要!!!
         INotificationManager service = getService();
         String pkg = mContext.getOpPackageName();
         TN tn = mTN;
         tn.mNextView = mNextView;
 
         try  {
             //把TN對象和一些參數傳遞到遠程NotificationManagerService中去
             service.enqueueToast(pkg, tn, mDuration);
         catch  (RemoteException e) {
             // Empty
         }
     }

咱們看看show方法中調運的getService方法,以下:

1
2
3
4
5
6
7
8
9
10
11
12
     //遠程NotificationManagerService的服務訪問接口
     private static INotificationManager sService;
 
     static private INotificationManager getService() {
         //單例模式
         if  (sService !=  null ) {
             return  sService;
         }
         //經過AIDL(Binder)通訊拿到NotificationManagerService的服務訪問接口
         sService = INotificationManager.Stub.asInterface(ServiceManager.getService( "notification" ));
         return  sService;
     }

經過上面咱們的基礎腦補實例你也能看懂這個getService方法了吧。那接着咱們來看mTN吧,好像mTN在Toast的構造函數裏見過一眼,咱們來看看,以下:

1
2
3
4
5
6
7
8
     public Toast(Context context) {
         mContext = context;
         mTN =  new  TN();
         mTN.mY = context.getResources().getDimensionPixelSize(
                 com.android.internal.R.dimen.toast_y_offset);
         mTN.mGravity = context.getResources().getInteger(
                 com.android.internal.R.integer.config_toastDefaultGravity);
     }

能夠看見mTN確實是在構造函數中實例化的,那咱們就來看看這個TN類,以下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
     //相似於上面例子的服務端實例化的Service內部類Binder
     private static class TN extends ITransientNotification.Stub {
         ......
         //實現了AIDL的show與hide方法
         @Override
         public void show() {
             if  (localLOGV) Log.v(TAG,  "SHOW: "  this );
             mHandler.post(mShow);
         }
 
         @Override
         public void hide() {
             if  (localLOGV) Log.v(TAG,  "HIDE: "  this );
             mHandler.post(mHide);
         }
         ......
     }

看見沒有,TN是Toast內部的一個私有靜態類,繼承自ITransientNotification.Stub。你這時指定好奇 ITransientNotification.Stub是個啥玩意,對吧?其實你在上面的腦補實例中見過它的,他出如今服務端實現的Service中, 就是一個Binder對象,也就是對一個aidl文件的實現而已,咱們看下這個ITransientNotification.aidl文件,以下:

1
2
3
4
5
6
7
package android.app;
 
/** @hide */
oneway interface ITransientNotification {
     void show();
     void hide();
}

看見沒有,和咱們上面的例子很相似吧。

再回到上面分析的show()方法中能夠看到,咱們的Toast是傳給遠程的NotificationManagerService管理的,爲了 NotificationManagerService回到咱們的應用程序(回調),咱們須要告訴NotificationManagerService 咱們當前程序的Binder引用是什麼(也就是TN)。是否是以爲和上面例子有些不一樣,這裏感受Toast又充當客戶端,又充當服務端的樣子,實質就是一 個回調過程而已。

繼續來看Toast中的show方法的service.enqueueToast(pkg, tn, mDuration);語句,service實質是遠程的NotificationManagerService,因此enqueueToast方法就是 NotificationManagerService類的,以下:

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
     private final IBinder mService =  new  INotificationManager.Stub() {
         // Toasts
         // ============================================================================
 
         @Override
         public void enqueueToast(String pkg, ITransientNotification callback, int duration)
         {
             ......
             synchronized (mToastQueue) {
                 int callingPid = Binder.getCallingPid();
                 long callingId = Binder.clearCallingIdentity();
                 try  {
                     ToastRecord record;
相關文章
相關標籤/搜索