第二部分:html
PopWindow實質就是彈出式菜單,它與Dialag不一樣的地方是不會使依賴的Activity組件失去焦點(PopupWindow彈出後可 以繼續與依賴的Activity進行交互),Dialog卻不能這樣。同時PopupWindow與Dialog另外一個不一樣點是PopupWindow是 一個阻塞的對話框,若是你直接在Activity的onCreate等方法中顯示它則會報錯,因此PopupWindow必須在某個事件中顯示地或者是開 啓一個新線程去調用。java
說這麼多仍是直接看代碼吧。android
依據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的窗口加載顯示機制就分析完畢了,接下來進行總結與應用開發技巧提示。
經過上面分析能夠發現總結以下圖:
能夠看見,PopWindow徹底使用了Activity的Window與WindowManager,相對來講比較簡單容易記理解。
再來看一個開發技巧:
若是設置了PopupWindow的background,則點擊Back鍵或者點擊PopupWindow之外的區域時PopupWindow就 會dismiss;若是不設置PopupWindow的background,則點擊Back鍵或者點擊PopupWindow之外的區域 PopupWindow不會消失。
在開始分析這幾個窗口以前須要腦補一點東東,咱們從應用層開發來直觀腦補,這樣下面分析源碼時就不蛋疼了。以下是一個咱們寫的兩個應用實現 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的機制咱們後面會寫文章一步一步往下分析。到此的準備知識已經足夠用來理解下面咱們的源碼分析了。
咱們經常使用的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;
|