第一章 四大組件 之 補充

文章目錄

第一章 四大組件

補充 Context

(一)Context是什麼?

1.1)廣義理解

Context:語境,上下文,可看做用戶與操作系統操作的一個場景;Context在加載資源、啓動Activity、獲取系統服務、創建View等操作都要參與。

1.2)官方註釋

Interface to global information about an application environment. This is an abstract class whose implementation is provided by the Android system. It allows access to application-specific resources and classes, as well as up-calls for application-level operations such as launching activities,broadcasting and receiving intents, etc.
Context提供了關於應用環境全局信息的接口。它是一個抽象類,它的執行被Android系統所提供。它允許獲取以應用爲特徵的資源和類型,是一個統領一些資源(應用程序環境變量等)的上下文。就是說,它描述一個應用程序環境的信息(即上下文);是一個抽象類,Android提供了該抽象類的具體實現類;通過它我們可以獲取應用程序的資源和類(包括應用級別操作,如啓動Activity,發廣播,接受Intent等)。

1.3)類關係

在這裏插入圖片描述
類圖
在這裏插入圖片描述
Context類本身是一個純abstract類,它有兩個具體的實現子類:ContextImpl和ContextWrapper。
Context源碼(關鍵函數)

public abstract class Context {

// 獲取應用程序包的AssetManager實例
public abstract AssetManager getAssets();

// 獲取應用程序包的Resources實例
public abstract Resources getResources();

// 獲取PackageManager實例,以查看全局package信息
public abstract PackageManager getPackageManager();

// 獲取應用程序包的ContentResolver實例
public abstract ContentResolver getContentResolver();

// 它返回當前進程的主線程的Looper,此線程分發調用給應用組件(activities, services等)
public abstract Looper getMainLooper();

// 返回當前進程的單實例全局Application對象的Context
public abstract Context getApplicationContext();

// 從string表中獲取本地化的、格式化的字符序列
public final CharSequence getText(int resId) {
return getResources().getText(resId);
}

// 從string表中獲取本地化的字符串
public final String getString(int resId) {
return getResources().getString(resId);
}

public final String getString(int resId, Object... formatArgs) {
return getResources().getString(resId, formatArgs);
}

// 返回一個可用於獲取包中類信息的class loader
public abstract ClassLoader getClassLoader();

// 返回應用程序包名
public abstract String getPackageName();

// 返回應用程序信息
public abstract ApplicationInfo getApplicationInfo();

// 根據文件名獲取SharedPreferences
public abstract SharedPreferences getSharedPreferences(String name,
int mode);

// 其根目錄爲: Environment.getExternalStorageDirectory()
/*
* @param type The type of files directory to return.  May be null for
* the root of the files directory or one of
* the following Environment constants for a subdirectory:
* {@link android.os.Environment#DIRECTORY_MUSIC},
* {@link android.os.Environment#DIRECTORY_PODCASTS},
* {@link android.os.Environment#DIRECTORY_RINGTONES},
* {@link android.os.Environment#DIRECTORY_ALARMS},
* {@link android.os.Environment#DIRECTORY_NOTIFICATIONS},
* {@link android.os.Environment#DIRECTORY_PICTURES}, or
* {@link android.os.Environment#DIRECTORY_MOVIES}.
*/
public abstract File getExternalFilesDir(String type);

// 返回應用程序obb文件路徑
public abstract File getObbDir();

// 啓動一個新的activity
public abstract void startActivity(Intent intent);

// 啓動一個新的activity
public void startActivityAsUser(Intent intent, UserHandle user) {
throw new RuntimeException("Not implemented. Must override in a subclass.");
}

// 啓動一個新的activity
// intent: 將被啓動的activity的描述信息
// options: 描述activity將如何被啓動
public abstract void startActivity(Intent intent, Bundle options);

// 啓動多個新的activity
public abstract void startActivities(Intent[] intents);

// 啓動多個新的activity
public abstract void startActivities(Intent[] intents, Bundle options);

// 廣播一個intent給所有感興趣的接收者,異步機制
public abstract void sendBroadcast(Intent intent);

// 廣播一個intent給所有感興趣的接收者,異步機制
public abstract void sendBroadcast(Intent intent,String receiverPermission);

public abstract void sendOrderedBroadcast(Intent intent,String receiverPermission);

public abstract void sendOrderedBroadcast(Intent intent,
String receiverPermission, BroadcastReceiver resultReceiver,
Handler scheduler, int initialCode, String initialData,
Bundle initialExtras);

public abstract void sendBroadcastAsUser(Intent intent, UserHandle user);

public abstract void sendBroadcastAsUser(Intent intent, UserHandle user,
String receiverPermission);

// 註冊一個BroadcastReceiver,且它將在主activity線程中運行
public abstract Intent registerReceiver(BroadcastReceiver receiver,
IntentFilter filter);

public abstract Intent registerReceiver(BroadcastReceiver receiver,
IntentFilter filter, String broadcastPermission, Handler scheduler);

public abstract void unregisterReceiver(BroadcastReceiver receiver);

// 請求啓動一個application service
public abstract ComponentName startService(Intent service);

// 請求停止一個application service
public abstract boolean stopService(Intent service);

// 連接一個應用服務,它定義了application和service間的依賴關係
public abstract boolean bindService(Intent service, ServiceConnection conn,
int flags);

// 斷開一個應用服務,當服務重新開始時,將不再接收到調用,
// 且服務允許隨時停止
public abstract void unbindService(ServiceConnection conn);

// 返回系統級service句柄
/*
* @see #WINDOW_SERVICE
* @see android.view.WindowManager
* @see #LAYOUT_INFLATER_SERVICE
* @see android.view.LayoutInflater
* @see #ACTIVITY_SERVICE
* @see android.app.ActivityManager
* @see #POWER_SERVICE
* @see android.os.PowerManager
* @see #ALARM_SERVICE
* @see android.app.AlarmManager
* @see #NOTIFICATION_SERVICE
* @see android.app.NotificationManager
* @see #KEYGUARD_SERVICE
* @see android.app.KeyguardManager
* @see #LOCATION_SERVICE
* @see android.location.LocationManager
* @see #SEARCH_SERVICE
* @see android.app.SearchManager
* @see #SENSOR_SERVICE
* @see android.hardware.SensorManager
* @see #STORAGE_SERVICE
* @see android.os.storage.StorageManager
* @see #VIBRATOR_SERVICE
* @see android.os.Vibrator
* @see #CONNECTIVITY_SERVICE
* @see android.net.ConnectivityManager
* @see #WIFI_SERVICE
* @see android.net.wifi.WifiManager
* @see #AUDIO_SERVICE
* @see android.media.AudioManager
* @see #MEDIA_ROUTER_SERVICE
* @see android.media.MediaRouter
* @see #TELEPHONY_SERVICE
* @see android.telephony.TelephonyManager
* @see #INPUT_METHOD_SERVICE
* @see android.view.inputmethod.InputMethodManager
* @see #UI_MODE_SERVICE
* @see android.app.UiModeManager
* @see #DOWNLOAD_SERVICE
* @see android.app.DownloadManager
*/
public abstract Object getSystemService(String name);

public abstract int checkPermission(String permission, int pid, int uid);

// 返回一個新的與application name對應的Context對象
public abstract Context createPackageContext(String packageName,
int flags) throws PackageManager.NameNotFoundException;

// 返回基於當前Context對象的新對象,其資源與display相匹配
public abstract Context createDisplayContext(Display display);
}
(1)ContextWrapper類

如其名所言,這只是一個包裝而已,是Context代理實現,簡單地把調用請求傳送給另外一個Context。ContextWrapper構造函數中必須包含一個真正的Context引用,同時ContextWrapper中提供了attachBaseContext()用於給ContextWrapper對象中指定真正的Context對象,調用ContextWrapper的方法都會被轉向其所包含的真正的Context對象。

/**
* Proxying implementation of Context that simply delegates all of its calls to
* another Context.  Can be subclassed to modify behavior without changing
* the original Context.
*/
public class ContextWrapper extends Context {
Context mBase; //該屬性指向一個ContextIml實例

public ContextWrapper(Context base) {
mBase = base;
}

/**
* Set the base context for this ContextWrapper.  All calls will then be
* delegated to the base context.  Throws
* IllegalStateException if a base context has already been set.
*
* @param base The new base context for this wrapper.
* 創建Application、Service、Activity,會調用該方法給mBase屬性賦值
*/
protected void attachBaseContext(Context base) {
if (mBase != null) {
throw new IllegalStateException("Base context already set");
}
mBase = base;
}

@Override
public Looper getMainLooper() {
return mBase.getMainLooper();
}

@Override
public Object getSystemService(String name) {
return mBase.getSystemService(name);
}

@Override
public void startActivity(Intent intent) {
mBase.startActivity(intent);
}
}
(2)ContextThemeWrapper類

如其名所言,其內部包含了與主題(Theme)相關的接口,這裏所說的主題就是指在AndroidManifest.xml中通過android:theme爲Application元素或者Activity元素指定的主題。當然,只有Activity才需要主題,Service是不需要主題的,因爲Service是沒有界面的後臺場景,所以Service直接繼承於ContextWrapper,Application同理。

Android Theme屬性
主題,主要用於對控件的屬性進行設置,包括應用的主要色調,actionBar默認使用該顏色,Toolbar導航欄的底色,狀態欄、頂部導航欄
相關,window區域、控件相關(Button樣式、文字大小)等等

ContextThemeWrapper源碼

/**
* A ContextWrapper that allows you to modify the theme from what is in the
* wrapped context.
*/
public class ContextThemeWrapper extends ContextWrapper {
private Context mBase;
private int mThemeResource;
private Resources.Theme mTheme;
private LayoutInflater mInflater;
private Configuration mOverrideConfiguration;
private Resources mResources;

public ContextThemeWrapper() {
super(null);
}

public ContextThemeWrapper(Context base, int themeres) {
super(base);
mBase = base;
mThemeResource = themeres;
}

@Override protected void attachBaseContext(Context newBase) {
super.attachBaseContext(newBase);
mBase = newBase;
}

@Override public void setTheme(int resid) {
mThemeResource = resid;
initializeTheme();
}

@Override public Resources.Theme getTheme() {
if (mTheme != null) {
return mTheme;
}

mThemeResource = Resources.selectDefaultTheme(mThemeResource,
getApplicationInfo().targetSdkVersion);
initializeTheme();

return mTheme;
}
}
(3)ContextImpl類

則真正實現了Context中的所有函數,應用程序中所調用的各種Context類的方法,其實現均來自於該類。(Context API的通用實現)
ContextImpl關鍵成員和函數

/**
* Common implementation of Context API, which provides the base
* context object for Activity and other application components.
*/
class ContextImpl extends Context {
private final static String TAG = "ContextImpl";
private final static boolean DEBUG = false;

private static final HashMap<String, SharedPreferencesImpl> sSharedPrefs =
new HashMap<String, SharedPreferencesImpl>();

/*package*/ LoadedApk mPackageInfo; // 關鍵數據成員
private String mBasePackageName;
private Resources mResources;
/*package*/ ActivityThread mMainThread; // 主線程

@Override
public AssetManager getAssets() {
return getResources().getAssets();
}

@Override
public Looper getMainLooper() {
return mMainThread.getLooper();
}

@Override
public Object getSystemService(String name) {
ServiceFetcher fetcher = SYSTEM_SERVICE_MAP.get(name);
return fetcher == null ? null : fetcher.getService(this);
}

@Override
public void startActivity(Intent intent, Bundle options) {
warnIfCallingFromSystemProcess();
if ((intent.getFlags()&Intent.FLAG_ACTIVITY_NEW_TASK) == 0) {
throw new AndroidRuntimeException(
"Calling startActivity() from outside of an Activity "
+ " context requires the FLAG_ACTIVITY_NEW_TASK flag."
+ " Is this really what you want?");
}
mMainThread.getInstrumentation().execStartActivity(
getOuterContext(), mMainThread.getApplicationThread(), null,
(Activity)null, intent, -1, options);
}
}
(3)總結

Context的兩個子類分工明確,其中ContextImpl是Context的具體實現類,ContextWrapper是Context的包裝類。Activity,Application,Service雖都繼承自ContextWrapper(Activity繼承自ContextWrapper的子類ContextThemeWrapper),但它們初始化的過程中都會創建ContextImpl對象,由ContextImpl實現Context中的方法。
面試題:一個應用程序有幾個Context?
從上面的關係圖我們已經可以得出答案了,在應用程序中Context的具體實現子類就是:Activity,Service,Application。那麼
Context數量=Activity數量+Service數量+1。
我們常說四大組件,這裏怎麼只有Activity,Service持有Context,那Broadcast Receiver,Content Provider呢?Broadcast Receiver,Content Provider並不是Context的子類,他們所持有的Context都是其他地方傳過去的,所以並不計入Context總數。

(二)Context能做什麼?

彈出Toast、啓動Activity、啓動Service、發送廣播、操作數據庫等等都需要用到Context。

//綁定控件的生命週期
TextView tv = new TextView(getContext());
ListAdapter adapter = new SimpleCursorAdapter(getApplicationContext(), ...);
//獲取服務
AudioManager am = (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);getApplicationContext().getSharedPreferences(name, mode);
//獲取ContentResolver
getApplicationContext().getContentResolver().query(uri, ...);
//獲取應用程序包的Resources實例
getContext().getResources().getDisplayMetrics().widthPixels * 5 / 8;
getContext().startActivity(intent);//開啓Activity
getContext().startService(intent);//開啓Service
getContext().sendBroadcast(intent);//開啓廣播

(三)Context作用域

由於Context的具體實例是由ContextImpl類去實現的,因此在絕大多數場景下,Activity、Service和Application這三種類型的Context都是可以通用的。
不過有幾種場景比較特殊,比如啓動Activity,還有彈出Dialog。出於安全原因的考慮,Android是不允許Activity或Dialog憑空出現的,一個Activity的啓動必須要建立在另一個Activity的基礎之上,也就是以此形成的返回棧。而Dialog則必須在一個Activity上面彈出(除非是System Alert類型的Dialog),因此在這種場景下,我們只能使用Activity類型的Context,否則將會出錯。
在這裏插入圖片描述

  • Activity所持有的Context的作用域最廣,無所不能
    因爲Activity繼承自ContextThemeWrapper,而Application和Service繼承自ContextWrapper,很顯然ContextThemeWrapper在ContextWrapper的基礎上又做了一些操作使得Activity變得更強大。
  • Start an Activity 不推薦使用 Application & Service
    如果我們用ApplicationContext去啓動一個LaunchMode爲standard的Activity的時候會報錯android.util.AndroidRuntimeException: Calling startActivity from outside of an Activity context requires the FLAG_ACTIVITY_NEW_TASK flag. Is this really what you want?這是因爲非Activity類型的Context並沒有所謂的任務棧,所以待啓動的Activity就找不到棧了。解決這個問題的方法就是爲待啓動的Activity指定FLAG_ACTIVITY_NEW_TASK標記位,這樣啓動的時候就爲它創建一個新的任務棧,而此時Activity是以singleTask模式啓動的。所有這種用Application啓動Activity的方式不推薦使用,Service同Application。
  • Layout Inflate 不推薦使用 Application & Service
    在Application和Service中去layout inflate也是合法的,但是會使用系統默認的主題樣式,如果你自定義了某些樣式可能不會被使用。所以這種方式也不推薦使用。

凡是跟UI相關的,都應該使用Activity做爲Context來處理;其他的一些操作,Service,Activity,Application等實例都可以,當然了,注意Context引用的持有,防止內存泄漏。

面試題:Application與Activity Context 區別?

  • 父類不同
    Activity直接繼承自ContextThemeWrapper,Application直接繼承自ContextWrapper。Activity相對於Application增加了UI界面的處理,如彈出Dialog。
  • 數量不同
    Application Context隨Application啓動而創建,Activity Context則隨Activity啓動而創建。故應用程序中只有一個Application Context但可以有多個Activity Context。
  • 生命週期不同
    Application Context的生命週期與Application相關,隨應用程序銷燬而銷燬;Activity Context的生命週期與Activity相關.故對於生命週期較長的對象應引用Application的Context放置內存泄露。

(四)Context的獲取方式

4.1)View.getContext

返回當前View對象的Context對象,通常是當前正在展示的Activity對象。

4.2)Activity.getApplicationContext

獲取當前Activity所在的(應用)進程Application的Context對象,通常我們使用Context對象時,要優先考慮這個全局的進程Context。

4.3)ContextWrapper.getBaseContext()

用來獲取一個ContextWrapper進行裝飾之前的Context,可以使用這個方法,這個方法在實際開發中使用並不多,也不建議使用。

4.4)Activity.this

返回當前的Activity實例,如果是UI控件需要使用Activity作爲Context對象,但是默認的Toast實際上使用ApplicationContext也可以。

(五)Context內存泄露

5.1)內存泄露情況

一般內存泄露原因都是:被引用的對象生命週期>Context生命週期

1、錯誤的單例模式

這是一個非線程安全的單例模式,instance作爲靜態對象,其生命週期要長於普通的對象,其中也包含Activity,假如Activity A去getInstance獲得instance對象,傳入this,常駐內存的Singleton保存了你傳入的Activity A對象,並一直持有,即使Activity被銷燬掉,但因爲它的引用還存在於一個Singleton中,就不可能被GC掉,這樣就導致了內存泄漏。

public class Singleton {
    private static Singleton instance;
    private Context mContext;

    private Singleton(Context context) {
        this.mContext = context;
    }

    public static Singleton getInstance(Context context) {
        if (instance == null) {
            instance = new Singleton(context);
        }
        return instance;
    }
}
2、View持有Activity引用

有一個靜態的Drawable對象當ImageView設置這個Drawable時,ImageView保存了mDrawable的引用,而ImageView傳入的this是MainActivity的mContext,因爲被static修飾的mDrawable是常駐內存的,MainActivity是它的間接引用,MainActivity被銷燬時,也不能被GC掉,所以造成內存泄漏。

public class MainActivity extends Activity {
    private static Drawable mDrawable;

    @Override
    protected void onCreate(Bundle saveInstanceState) {
        super.onCreate(saveInstanceState);
        setContentView(R.layout.activity_main);
        ImageView iv = new ImageView(this);
        mDrawable = getResources().getDrawable(R.drawable.ic_launcher);
        iv.setImageDrawable(mDrawable);
    }
}

5.2)正確使用Context

一般Context造成的內存泄漏,幾乎都是當Context銷燬的時候,卻因爲被引用導致銷燬失敗。而Application的Context對象可以理解爲隨着進程存在的,所以我們總結出使用Context的正確姿勢:

  • 當Application的Context能搞定的情況下,並且生命週期長的對象,優先使用Application的Context。
  • 不要讓生命週期長於Activity的對象持有到Activity的引用。
  • 儘量不要在Activity中使用非靜態內部類,因爲非靜態內部類會隱式持有外部類實例的引用,如果使用靜態內部類,將外部實例引用作爲弱引用持有。

補充 Intent

(一)什麼是Intent?

Android中提供了Intent機制來協助應用間的交互與通訊,或者採用更準確的說法是,Intent不僅可用於應用程序之間,也可用於應用程序內部的activity, service和broadcast receiver之間的交互。Intent這個英語單詞的本意是“目的、意向、意圖”。
  Intent是一種運行時綁定(runtime binding)機制,它能在程序運行的過程中連接兩個不同的組件。Intent負責對應應用中一次操作的動作、動作涉及數據、附加數據進行描述,通過Intent,你的程序可以向Android表達某種請求或者意願,Android會根據意願的內容選擇適當的組件來響應。因此,Intent在這裏起着一個媒體中介的作用,專門提供組件互相調用的相關信息,實現調用者與被調用者之間的解耦。
  例如,在一個聯繫人維護的應用中,當我們在一個聯繫人列表界面(listActivity)上,點擊某個聯繫人後,希望能夠跳出此聯繫人的詳細信息界面(detailActivity)。爲了實現這個目的,listActivity需要構造一個 Intent,這個Intent用於告訴系統,我們要做“查看”動作,此動作對應的查看對象是“某聯繫人”,然後調用startActivity (Intent intent),將構造的Intent傳入,系統會根據此Intent中的描述,到AndroidManifest中找到滿足此Intent要求的Activity,系統會調用找到的 Activity,即爲detailActivity,最終傳入Intent,detailActivity則會根據此Intent中的描述,執行相應的操作。

(二)Intent的使用

1、指定當前組件要完成的動作

(1)Activity跳轉

將intent對象傳給Context.startActivity() 或 Activity.startActivityForResult()來啓動一個activity。使用 Activity.setResult(),傳入一個intent來從activity中返回結果。

(1.1)顯式意圖

已知包名和類名的情況下,調用Intent.setClass()(同Intent.setComponent())方法明確指定了組件名的Intent爲顯式意圖,顯式意圖明確指定了Intent應該傳遞給哪個組件。通常啓動本應用中Activity之間的數據。
(1.1.1)打開外部應用

intent.setClassName(包名, 包名+activity名);
Intent mIntent = new Intent();
//打開系統鬧鐘
mIntent.setClassName("com.android.deskclock","com.android.deskclock.DeskClock");
mContext.startActivity(mIntent);

(1.1.2)應用內Activity跳轉

    intent.setClass(當前組件.this, 目標組件.class);
    //方法1:Intent intent = new Intent(mContext, XXActivity.class);
    //方法2:setClass打開XXActivity
    Intent mIntent = new Intent();
    mIntent.setClassName(MainActivity.this,XXActivity.class);
    startActivity(intent);
(1.2)隱式意圖

沒有明確指定組件名的Intent爲隱式意圖。 Android系統會根據隱式意圖中設置的動作(action)、類別(category)、數據(URI和數據類型)從AndroidManifest找到最合適的組件來處理這個意圖。通常啓動系統中某些動作,如打電話,或者是跨應用Activity啓動。
(1.2.1)意圖的參數
意圖包括:Action(動作),Category(附加信息),Data(數據,具體內容),Tpye(類型)等等,只有動作信息完整才能執行一個完整的意圖

  • Action:具體動作的描述,如打電話
  • Category:動作的附加描述
  • Data:動作的具體內容,如打電話時的電話號碼
  • Type:動作的類型

還有一些信息,比如scheme就是URI類型的數據的前綴(比如發送短信中的sms),host主機名,past路徑等等
(1.2.2)隱式跳轉之自定義隱式意圖
自定義隱式意圖,啓動指定瀏覽器網站loonggg://www.baidu.com/person,該網站傳遞的數據類型爲person/peoplr
步驟1:註冊清單文件

     <activity android:name="net.loonggg.intent.SecondActivity" >
                <intent-filter>
     
                    <!-- 自定義的動作 -->
                    <action android:name="net.loonggg.xxx" />
                    <!-- 自定義的scheme和host -->
                    <data
                        android:host="www.baidu.com"
                        android:path="/person"
                        android:scheme="loonggg" />
                    <!-- 自定義的類型 -->
                    <data android:mimeType="person/people" />
                    <!-- 附加信息 -->
                    <category android:name="android.intent.category.DEFAULT" />
                </intent-filter>
            </activity>

步驟2:Activity中調用

    public void start(View view) {
    		Intent intent = new Intent();
    		intent.setAction("net.loonggg.xxx");//設置Intent的動作爲清單指定的action
    		intent.addCategory("android.intent.category.DEFAULT");//與清單相同的category
    		intent.setDataAndType(Uri.parse("loonggg://www.baidu.com/person"),
    				"person/people");
    		startActivity(intent);
    	}

setData、setDataAndType、setType 這三種方法只能單獨使用,不可共用。
intent.setData(data)和intent.setType(type)注意這兩個方法會互相清除,意思就是:如果先設置setData(data)後設置setType(type),那麼後設置的setType(type)會把前面setData(data)設置的內容清除掉,而且會報錯,反之一樣,所以如果既要設置類型與數據,那麼使用setDataAndType(data,type)這個方法。

(1.2.3)隱式跳轉之調用系統應用
調用系統短信的例子,使用隱式意圖啓動系統短信,並給10086發送信息

    public void sendMessage(View view) {
    		Intent intent = new Intent();
    		intent.setAction("android.intent.action.SENDTO");// 發送信息的動作
    		intent.addCategory("android.intent.category.DEFAULT");// 附加信息
    		intent.setData(Uri.parse("sms:10086"));// 具體的數據,發送給10086
    		startActivity(intent);
    	}

舉例:
1、使用瀏覽器瀏覽網頁

 //web瀏覽器
 Uri uri= Uri.parse("http://www.baidu.com");
 Intent intent = new Intent(Intent.ACTION_VIEW, uri);
 startActivity(intent);

2、調用地圖

     //打開地圖查看經緯度
     Uri uri = Uri.parse("geo:38.899533,-77.036476");
     Intent intent = new Intent(Intent.ACTION_VIEW, uri);
     startActivity(intent);

3、調用電話撥號(不需要撥號權限)

    Uri uri = Uri.parse("tel:10086");
    Intent intent = new Intent(Intent.ACTION_DIAL, uri);//注意區別於下面4.4的action
    startActivity(intent);

4、調用電話直接撥號(需要撥號權限)

    Uri uri = Uri.parse("tel:15980665805");
    Intent intent = new Intent(Intent.ACTION_CALL, uri);//注意區別於上面4.3的aciton
    startActivity(intent);

5、調用短信程序(無需發送短信權限,接收者自填)

    Intent intent = new Intent(Intent.ACTION_VIEW);    
    intent.putExtra("sms_body", "這裏寫短信內容");    
    intent.setType("vnd.android-dir/mms-sms");    
    startActivity(intent);

6、調用短信程序(無需發送短信權限)

    Uri uri = Uri.parse("smsto:10086");//指定接收者
    Intent intent = new Intent(Intent.ACTION_SENDTO, uri);
    intent.putExtra("sms_body", "你這個黑心運營商");
    startActivity(intent);

7、調用郵件程序

    Intent intent = new Intent(Intent.ACTION_SENDTO); 
    intent.setData(Uri.parse("mailto:[email protected]")); 
    intent.putExtra(Intent.EXTRA_SUBJECT, "這是標題"); 
    intent.putExtra(Intent.EXTRA_TEXT, "這是內容"); 
    startActivity(intent);

8、調用音樂播放器

    Intent intent = new Intent(Intent.ACTION_VIEW);
    //Uri uri = Uri.parse("file:///sdcard/xiong_it.mp4");
    Uri uri = Uri.parse("file:///sdcard/xiong_it.mp3");
    intent.setDataAndType(uri, "audio/mp3");
    startActivity(intent);

9、調用視頻播放器

    Intent intent = new Intent(Intent.ACTION_VIEW);
    //Uri uri = Uri.parse("file:///sdcard/xiong_it.mp3");
    Uri uri = Uri.parse("file:///sdcard/xiong_it.mp4");
    intent.setDataAndType(uri, "video/mp4");
    startActivity(intent);

調用視頻播放器和音樂播放器的區別在setDataAndType()時一個是audio類型,一個是video類型,很容易記住,不允許使用其他意思相近的單詞代替,代替無效。
10、調用搜索

    Intent intent = new Intent(); 
    intent.setAction(Intent.ACTION_WEB_SEARCH); 
    intent.putExtra(SearchManager.QUERY, "android"); 
    startActivity(intent);
(2)啓動/綁定服務

將intent對象傳給Context.startService()來啓動一個service或者傳消息給一個運行的service。將intent對象傳給 Context.bindService()來綁定一個service。

(3)發送廣播

將intent對象傳給 Context.sendBroadcast(),Context.sendOrderedBroadcast(),或者Context.sendStickyBroadcast()等廣播方法,則它們被傳給 broadcast receiver。

2、通過Intent傳遞數據

(2.1)基本數據類型(包括String)
    public Intent putExtra(String name, int/String/boolean value) {
        if (mExtras == null) {
            mExtras = new Bundle();
        }
        mExtras.putInt(name, value);
        return this;
    }

Intent putExtra(String name, String value),Intent putExtra(String name, int value),Intent putExtra(String name, boolean value)等原理都一樣,他們都通過new Bundle.putxxx()來實現的,也就是說傳進來的這些數據都是通過Bundle這個容器來裝然後傳遞,在Intent類裏面維護了一個Bundle對象mExtras,如果intent已經攜帶了Bundle對象,那麼直接向裏面存儲數據,否則就新建一個Bundle對象

(2.2)Bundle

Bundle是一個存儲可傳輸數據的容器。可傳輸數據可理解爲能直接轉換爲字節流的數據類型(如基本數據類型+String及實現序列化接口的數據),由上述代碼可知Intent存取數據本質上就是通過Bundle內存取數據

    mExtras.putXXX()==Bundle.putXXX()
    public void putxxxx(@Nullable String key, value) {
            unparcel();
            mMap.put(key, value);
        }

即,在BaseBundle內維護了一個ArrayMap

    ArrayMap<String, Object> mMap = null;

intent的put操作和get操作就是對Bundle裏面的ArrayMap進行mMap.put(key, value);和mMap.get(key);操作.(本質:put方法會將參數傳給一個ArrayMap,再將ArrayMap打包成一個Parcel在進程間傳遞,獲取時對Parcel對下個解包)
發送數據

    String string = editText.getText().toString();
    Intent intent = new Intent(MainActivity.this, main.class);
    Bundle bundle = new Bundle();
    bundle.putString("name", string);
    intent.putExtras(bundle);
    startActivity(intent);

接收數據

    Intent intent = getIntent();
    Bundle bundle = intent.getExtras();
    String string = bundle.getString("name");
    textView.setText(string);
(2.3)Serializable和Parcelable

Android實現序列化接口有2種方式

1、實現Serializable接口(java)

Serializable的作用是爲了保存對象的屬性到本地文件、數據庫、網絡流、rmi以方便數據傳輸,當然這種傳輸可以是程序內的也可以是兩個程序間的。
對於對於Serializable,類只需要實現Serializable接口,並提供一個序列化版本id(serialVersionUID)即可
(Java的序列化機制是通過在運行時判斷類的serialVersionUID來驗證版本一致性的。在進行反序列化時,JVM會把傳來的字節流中的serialVersionUID與本地相應實體(類)的serialVersionUID進行比較,如果相同就認爲是一致的,可以進行反序列化,否則就會出現序列化版本不一致的異常。添加serialVersionUID使得在序列化時保持版本的兼容性,即在版本升級時反序列化仍保持對象的唯一性。)
實現Serializable接口的bean

    public class Person implements Serializable{
    
        private static final long serialVersionUID = 1L;
    
        private int id;
        private String name;
    
        public Person() {
        }
    
        public Person(int id, String name) {
            this.id = id;
            this.name = name;
        }
    
        public int getId() {
            return id;
        }
    
        public void setId(int id) {
            this.id = id;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    }

intent數據傳遞

    Person p = new Person();//數據封裝
        p.setId(320840);
        p.setName("小夥子");
        Intent i = new Intent(MainActivity.this, FirstActivity.class);
        i.putExtra("Person", p);
        startActivity(i);   

接收數據

    Person p = (Person)getIntent().getSerializableExtra("Person");       
       System.out.println("身份證"+p.getId());
       System.out.println("姓名"+p.getName()); 
2、實現Parcelable接口(android)

Android的Parcelable的設計初衷是因爲Serializable效率過慢,爲了在程序內不同組件間以及不同Android程序間(AIDL)高效的傳輸數據而設計,這些數據僅在內存中存在,Parcelable是通過IBinder通信的消息的載體。
實現Parcelable接口的bean

    public class Student implements Parcelable {
    
        private int id;
        private String name;
    
        public Student() {
        }
    
        public Student(int id, String name) {
            this.id = id;
            this.name = name;
        }
    
        public int getId() {
            return id;
        }
    
        public void setId(int id) {
            this.id = id;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        @Override
        public int describeContents() {
            return 0;
        }
    
        @Override
        public void writeToParcel(Parcel dest, int flags) {
    //打包:這個方法就是傳入一個空的Parcel對象,然後將我們要存儲的對象的屬性寫進Parcel中去(注意寫入順序,因爲在讀取時是按照寫入順序讀取)
            dest.writeInt(this.id);
            dest.writeString(this.name);
        }
    
        protected Student(Parcel in) {
            this.id = in.readInt();
            this.name = in.readString();
        }
    
        public static final Parcelable.Creator<Student> CREATOR = new Parcelable.Creator<Student>() {
            @Override
            public Student createFromParcel(Parcel source) {
    //解包:從Parcel對象中獲取值來創建一個我們需要傳遞的的Student 對象
                return new Student(source);
            }
    
            @Override
            public Student[] newArray(int size) {
                return new Student[size];
            }
        };
    }

Parcel類是用來封裝數據的容器,封裝後的數據可以通過Intent或IPC傳遞,除了基本類型外,只有實現了Parcelable接口的類才能打包成parcel對象來傳遞。對於對象傳遞時需要打包成Parcel對象傳遞,而打包和解包的過程都是我們自己來寫的。(複雜)
intent數據傳遞

    	Student s = new Student();//封裝數據
        s.setId(320840);
        s.setName("小夥子");
        Intent i = new Intent(MainActivity.this, FirstActivity.class);
        i.putExtra("Student", s);
        startActivity(i); 

接收數據

    	Student s = (Student)getIntent().getParcelableExtra("Student");       
       System.out.println("學號:"+s.getId());
       System.out.println("姓名:"+s.getName()); 
3、Serializable接口與Parcelable接口對比

Parcelable的性能比Serializable好,在內存開銷方面較小,所以在內存間數據傳輸時推薦使用Parcelable,如activity間傳輸數據,而Serializable可將數據持久化方便保存,所以在需要保存或網絡傳輸數據時選擇Serializable,因爲android不同版本Parcelable可能不同,所以不推薦使用Parcelable進行數據持久化。

總結:所以在傳遞對象時對於需要傳遞的對象的序列化選擇可以加以區分,需要數據持久化的建議實現Serializable接口,只在內存間數據傳輸時推薦使用Parcelable。

補充 Application

(一)定義

代表應用程序(Android App)的類,也屬於Android中的一個系統組件。
繼承自android.content.ContextWrapper類。

(二)特點

(2.1)實例創建方式:單例模式

每個Android App運行時,會首先自動創建Application並實例化Application對象,在整個應用程序中有且只有一個Application對象。即Application類是單例模式(singleton)類。也可以通過繼承Application類自定義Application類和實例。
故,每個App都有一個Application實例,如果我們可以通過繼承Application自定義Application子類。如果沒有繼承,則App會創建一個默認的實例。

(2.2)實例形式:全局實例

不同的組件(如Activity、Service)都可以獲得Application對象且都是同一對象。

(2.3)生命週期:等於 Android App 的生命週期

Application對象的生命週期是整個程序中最長的,等於Android App的生命週期。Application與App"同生共死"

(三)方法介紹

在這裏插入圖片描述

(3.1)onCreate()

調用時刻
Application實例創建時調用。
Android系統的入口是Application類的onCreate(),默認爲空實現
作用

  • 初始化應用程序級別的資源,如全局對象、環境配置變量、圖片資源初始化、推送服務的註冊等。
  • 數據共享、數據緩存設置全局共享數據,如全局共享變量、方法等。

注:
1、請不要執行耗時操作,否則會拖慢應用程序啓動速度
2、這些共享數據只在應用程序的生命週期內有效,當該應用程序被殺死,這些數據也會被清空,所以只能存儲一些具備 臨時性的共享數據

具體使用

public class MyAppliction extends Application {
private static final String VALUE = "First";
    @Override
    public void onCreate()
    {
        super.onCreate();
        VALUE = "Second";    // 初始化全局變量
    }
}

(3.2)registerComponentCallbacks()& unregisterComponentCallbacks()

作用
註冊和註銷 ComponentCallbacks2回調接口,複寫該回調接口裏的方法從而實現更多操作。
具體使用

registerComponentCallbacks(new ComponentCallbacks2() {
// 接口裏方法下面會繼續介紹
            @Override
            public void onTrimMemory(int level) {
            }
 
            @Override
            public void onLowMemory() { 
            }
 
            @Override
            public void onConfigurationChanged(Configuration newConfig) {
            }
        });

(3.3)onTrimMemory()

作用
通知應用程序當前內存使用情況(以內存級別進行識別)
在這裏插入圖片描述
應用場景
根據當前內存使用情況進行自身的內存資源的不同程度釋放,以避免被系統直接殺掉 & 優化應用程序的性能體驗。

  • 系統在內存不足時會按照LRU Cache中從低到高殺死進程;優先殺死佔用內存較高的應用
  • 若應用佔用內存較小 = 被殺死機率降低,從而快速啓動(即熱啓動 = 啓動速度快)
  • 可回收的資源包括:a. 緩存,如文件緩存,圖片緩存b. 動態生成 & 添加的View

常見的應用場景包括:
(1)常駐內存的應用:如Launcher、電話等。當用戶使用過要退出時,調用OnTrimMemory及時釋放用戶使用時產生的多餘的內存資源,如動態生成的View、圖片緩存。
(2)後臺Service運行的應用:如音樂、下載等。當用戶退出UI界面後,後臺任務還在繼續(如音樂播放&下載),此時應調用OnTrimMemory釋放部分UI資源
具體使用


registerComponentCallbacks(new ComponentCallbacks2() {
 
@Override
  public void onTrimMemory(int level) {
 
  // Android系統會根據當前內存使用的情況,傳入對應的級別
  // 下面以清除緩存爲例子介紹
    super.onTrimMemory(level);
  .   if (level >= ComponentCallbacks2.TRIM_MEMORY_MODERATE) {
 
        mPendingRequests.clear();
        mBitmapHolderCache.evictAll();
        mBitmapCache.evictAll();
    }
 
        });

可回調對象 & 對應方法

Application.onTrimMemory()
Activity.onTrimMemory()
Fragment.OnTrimMemory()
Service.onTrimMemory()
ContentProvider.OnTrimMemory()

特別注意:
onTrimMemory()中的TRIM_MEMORY_UI_HIDDEN與onStop()的關係
onTrimMemory()中的TRIM_MEMORY_UI_HIDDEN的回調時刻:當應用程序中的所有UI組件全部不可見時
Activity的onStop()回調時刻:當一個Activity完全不可見的時候 使用建議: 在 onStop()中釋放與
Activity相關的資源,如取消網絡連接或者註銷廣播接收器等
在onTrimMemory()中的TRIM_MEMORY_UI_HIDDEN中釋放與UI相關的資源,從而保證用戶在使用應用程序過程中,UI相關的資源不需要重新加載,從而提升響應速度
注:onTrimMemory的TRIM_MEMORY_UI_HIDDEN等級是在onStop()方法之前調用的

(3.4)onLowMemory()

調用時刻
Android系統整體內存較低時
作用
監聽 Android系統整體內存較低時刻(類似OnTrimMemory)
應用場景
Android 4.0前 檢測內存使用情況,從而避免被系統直接殺掉 & 優化應用程序的性能體驗
具體使用

registerComponentCallbacks(new ComponentCallbacks2() {
 
  @Override
            public void onLowMemory() {
            //釋放資源操作
            }
        });

特別注意:
OnTrimMemory() & OnLowMemory() 關係
OnTrimMemory()是 OnLowMemory() Android 4.0後的替代 API
OnLowMemory() = OnTrimMemory()中的TRIM_MEMORY_COMPLETE級別
若想兼容Android 4.0前,請使用OnLowMemory();否則直接使用OnTrimMemory()即可

(3.5)onConfigurationChanged()

調用時刻
應用程序配置信息 改變時調用
作用
監聽 應用程序 配置信息的改變,如屏幕旋轉等
配置信息是指:Manifest.xml文件下的 Activity標籤屬性android:configChanges的值,如下:

<activity android:name=".MainActivity">
      android:configChanges="keyboardHidden|orientation|screenSize"
// 設置該配置屬性會使 Activity在配置改變時不重啓,只執行onConfigurationChanged()
// 上述語句表明,設置該配置屬性可使 Activity 在屏幕旋轉時不重啓
 </activity>

具體使用

registerComponentCallbacks(new ComponentCallbacks2() {
 
            @Override
            public void onConfigurationChanged(Configuration newConfig) {
              ...
            }

        });

(3.6)registerActivityLifecycleCallbacks() & unregisterActivityLifecycleCallbacks()

調用時刻
當應用程序內 Activity生命週期發生變化時就會調用
作用
註冊 / 註銷對 應用程序內 所有Activity的生命週期監聽

實際上是調用registerActivityLifecycleCallbacks()裏ActivityLifecycleCallbacks接口裏的方法

具體使用


// 實際上需要複寫的是ActivityLifecycleCallbacks接口裏的方法
registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {
            @Override
            public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
                Log.d(TAG,"onActivityCreated: " + activity.getLocalClassName());
            }
 
            @Override
            public void onActivityStarted(Activity activity) {
                Log.d(TAG,"onActivityStarted: " + activity.getLocalClassName());
            }
 
            @Override
            public void onActivityResumed(Activity activity) {
                Log.d(TAG,"onActivityResumed: " + activity.getLocalClassName());
            }
 
            @Override
            public void onActivityPaused(Activity activity) {
                Log.d(TAG,"onActivityPaused: " + activity.getLocalClassName());
            }
 
            @Override
            public void onActivityStopped(Activity activity) {
                Log.d(TAG, "onActivityStopped: " + activity.getLocalClassName());
            }
 
            @Override
            public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
            }
 
            @Override
            public void onActivityDestroyed(Activity activity) {
                Log.d(TAG,"onActivityDestroyed: " + activity.getLocalClassName());
            }
        });
 
<-- 測試:把應用程序從前臺切到後臺再打開,看Activcity的變化 -->
 onActivityPaused: MainActivity
 onActivityStopped: MainActivity
 onActivityStarted: MainActivity
 onActivityResumed: MainActivity

(3.7)onTerminate()

調用時刻
應用程序結束時調用

但該方法只用於Android仿真機測試,在Android產品機是不會調用的

(四)應用場景

從Applicaiton類的方法可以看出,Applicaiton類的應用場景有:(已按優先級排序)

  • 初始化 應用程序級別 的資源,如全局對象、環境配置變量等
  • 數據共享、數據緩存,如設置全局共享變量、方法等
    數據共享
    在Application中創建一個HashMap ,以字符串爲索引,Object爲value這樣我們的HashMap就可以存儲任何類型的對象了。在Activity A中把需要傳遞的對象放入這個HashMap,然後通過Intent或者其它途經再把這索引的字符串傳遞給Activity B ,Activity B 就可以根據這個字符串在HashMap中取出這個對象了。只要再向下轉個型 ,就實現了對象的傳遞。
    數據緩存
    比如有一個Activity需要從網站獲取一些數據,獲取完之後我們就可以把這個數據cache到Application 當中,當頁面設置到其它Activity再回來的時候,就可以直接使用緩存好的數據了。但如果需要cache一些大量的數據,最好是cache一些 (軟引用)SoftReference ,並把這些數據cache到本地rom上或者sd卡上。如果在application中的緩存不存在,從本地緩存查找,如果本地緩存的數據也不存在再從網 絡上獲取。
  • 獲取應用程序當前的內存使用情況,及時釋放資源,從而避免被系統殺死
  • 監聽 應用程序 配置信息的改變,如屏幕旋轉等
  • 監聽應用程序內 所有Activity的生命週期

(五)Application的創建與獲取

(5.1)Application的創建

一個安裝的應用對應一個LoadedApk對象,對應一個Application對象,對於四大組件,Application的創建和獲取方式也是不盡相同的,具體說來:

  • Activity:通過LoadedApk的makeApplication()方法創建。
  • Service:通過LoadedApk的makeApplication()方法創建。
  • 靜態廣播:通過其回調方法onReceive()方法的第一個參數指向Application。
  • ContentProvider:無法獲取Application,因此此時Application不一定已經初始化。

(5.2)Application的獲取

如果是在Context環境下可以直接獲取

//方法1:application = (MyApplication)getContext();
//方法2:application = (MyApplication)getApplication();

非Context環境可以仿照單例的做法獲取

public class MyApplication extends Application {
    private static MyApplication instance;
}
@Override
public void onCreate() {
    super.onCreate();
    instance = this;
}
 // 獲取Application
    public static Context getMyApplication() {
        return instance;
}

(六)具體使用

若需要複寫實現上述方法,則需要自定義 Application類
具體過程如下

步驟1:自定義Application子類

自定義MyApplication繼承 Application 類.
Application的啓動是在App啓動時就啓動,在所有Activity之前,故可用它做資源初始化,WebView預加載,推送服務的註冊等等。但不要作耗時操作,否則會拖慢App啓動速度;也可設置一些全局共享數據。如:

  • 可以設置一些全局的共享常量,如一些TAG,枚舉值等。
  • 可以設置一些全局使用的共享變量數據,如一個全局的Handler等等,但是要注意,這裏緩存的變量數據的作用週期只在APP的生命週期,如果APP因爲內存不足而結束的話,再開啓這些數據就會消失,所以這裏只能存儲一些不重要的數據來使數據全APP共享,想要儲存重要數據的話需要SharePreference、數據庫或者文件存儲等這些本地存儲。
  • 可以設置一些靜態方法來讓其他類調用,來使用Application裏面的全局變量,如實現APP一鍵退出功能時候會用到。
public class MyApplication extends Application
  {
    ...
    // 根據自身需求,並結合上述介紹的方法進行方法複寫實現
 
    // 下面以onCreate()爲例
  private static final String VALUE = "Carson";
    // 初始化全局變量
    @Override
    public void onCreate()
    {
        super.onCreate();
        VALUE = 1;
    }
  }

步驟2:配置自定義的Application子類

在Manifest.xml文件中 < application >標籤裏進行配置
Manifest.xml

<application
 
        android:name=".MyApplication"
        // 此處自定義Application子類的名字 = MyApplication
    
</application>

步驟3:使用自定義的Application類實例

private CarsonApplicaiton app;
 
// 只需要調用Activity.getApplication() 或Context.getApplicationContext()就可以獲得一個Application對象
app = (CarsonApplication) getApplication();
 
// 然後再得到相應的成員變量 或方法 即可
app.exitApp();

(七)總結

在這裏插入圖片描述

補充 Window

(一)基本概念

1、Window

(1.1)定義
Window是一個抽象類,表示一個窗口,它的具體實現類是PhoneWindow。可理解爲View的承載器。View的源碼如下:

public abstract class Window {
    public static final int FEATURE_NO_TITLE = 1;
    public static final int FEATURE_CONTENT_TRANSITIONS = 12;
    //...
     public abstract View getDecorView();
     public abstract void setContentView(@LayoutRes int layoutResID);
     public abstract void setContentView(View view);
     public abstract void setContentView(View view, ViewGroup.LayoutParams params);
     public <T extends View> T findViewById(@IdRes int id) {
        return getDecorView().findViewById(id);
    }
    //...
}

Window是View的直接管理者。
當我們調用Activity的setContentView時,其實最終會調用Window的setContentView,當我們調用Activity的findViewById時,其實最終調用的是Window的findViewById。
但是Window並不是真實存在的,它更多的表示一種抽象的功能集合,View纔是Android中的視圖呈現形式,繪製到屏幕上的是View不是Window,但是View不能單獨存在,它必需依附在Window這個抽象的概念上面,Android中需要依賴Window提供視圖的有Activity,Dialog,Toast,PopupWindow,StatusBarWindow(系統狀態欄),輸入法窗口等,因此Activity,Dialog等視圖都對應着一個Window。
(1.2)具體實現類——PhoneWindow
PhoneWindow是Window的唯一實現類。它裏面包含了一個最頂層的DecorView,繼承自FrameLayout,可以通過getDecorView()獲得。大家使用的View都是存在於DecorView下面的。PhoneWindow裏面繼承了一個Callback接口,該接口裏麪包含了大量事件處理方法。分析的點擊事件,就是對這些方法進行分析的。
(1.3)分類
Window 有三種類型,分別是應用 Window、子 Window 和系統 Window。

  • 應用程序窗口:type值範圍是1~99,Activity就是一個典型的應用程序窗口,type值是TYPE_BASE_APPLICATION,WindowManager的LayoutParams默認type值是TYPE_APPLICATION。
  • 子窗口:type值範圍是1000~1999,PupupWindow就是一個典型的子窗口,以及一些常見的Dialog。type值是TYPE_APPLICATION_PANEL,子窗口不能獨立存在,必須依附於父窗口
  • 系統窗口:type值範圍是2000~2999,系統窗口的類型很多,Toast 和系統狀態欄都是系統 Window。type值是TYPE_STATUS_BAR,與應用程序窗口不同的是,系統窗口的創建是需要聲明權限的。

Window 是分層的,每個 Window 都有對應的 z-ordered(在屏幕z軸方向顯示次序),由type值決定。層級大的會覆蓋在層級小的 Window 上面,這和 HTML 中的 z-index 概念是完全一致的。在三種 Window 中,應用 Window 層級範圍是 1~99,子 Window 層級範圍是 1000~1999,系統 Window 層級範圍是 2000~2999,我們可以用一個表格來直觀的表示:
在這裏插入圖片描述
這些層級範圍對應着 WindowManager.LayoutParams 的 type 參數,如果想要 Window 位於所有 Window 的最頂層,那麼採用較大的層級即可,很顯然系統 Window 的層級是最大的,當我們採用系統層級時,需要聲明權限。
(1.4)屬性
Window的類型flag同樣被定義在WindowManager中的靜態內部類LayoutParams中,用來控制Window的顯示特性如下:

public interface WindowManager extends ViewManager {
    //...
    public static class LayoutParams extends ViewGroup.LayoutParams implements Parcelable {
        public static final int FLAG_ALLOW_LOCK_WHILE_SCREEN_ON     = 0x00000001;
        public static final int FLAG_DIM_BEHIND        = 0x00000002;
        public static final int FLAG_BLUR_BEHIND        = 0x00000004;
        public static final int FLAG_NOT_FOCUSABLE      = 0x00000008;
        public static final int FLAG_NOT_TOUCHABLE      = 0x00000010;
        public static final int FLAG_NOT_TOUCH_MODAL    = 0x00000020;
        public static final int FLAG_KEEP_SCREEN_ON     = 0x00000080;
		//...
    }
}

FLAG_ALLOW_LOCK_WHILE_SCREEN_ON:只要窗口對用戶可見,就允許在屏幕開啓狀態下鎖屏。
FLAG_KEEP_SCREEN_ON: 只要窗口對用戶可見,屏幕就一直亮着。
FLAG_SHOW_WHEN_LOCKED:窗口可以在鎖屏的界面上顯示。
FLAG_NOT_FOCUSABLE:窗口不能獲取焦點,也不能接受任何輸入事件,此標誌同時會啓用FLAG_NOT_TOUCH_MODAL,最終事件會直接傳遞給下層的具有焦點的窗口。
FLAG_NOT_TOUCH_MODAL:當前窗口區域以外的觸摸事件會傳遞給底層的窗口,當前窗口區域內的觸摸事件則自己處理,一般來說都要開啓此標記,否則其他Window將無法收到單機事件。
FLAG_NOT_TOUCHABLE:窗口不接收任何觸摸事件

2、WindowManagerService

(2.1)定義
WindowManagerService 就是位於 Framework 層的窗口管理服務,是一個系統級服務,由SystemService啓動,它的職責就是管理系統中的所有窗口。
一般的開發過程中,我們操作的是 UI 框架層,對 Window 的操作通過 WindowManager 即可完成,而 WindowManagerService 作爲系統級服務運行在一個單獨的進程,繼承自IWindowManager.Stub(用於進程間通信),實現了IWindowManager.AIDL接口,所以 WindowManager 和 WindowManagerService 的交互是一個 IPC 過程。WindowManagerService通過與進程通信實現對窗口的管理。
(2.2)主要功能
它的主要功能分爲以下兩個方面:
1、窗口管理
它負責窗口的啓動,添加和刪除,它還負責窗口的層級顯示(z-orderes)和維護窗口的狀態。我們繼續上面的mGlobal.addView,上面講到這個方法是向WMS發起一個顯示窗口視圖的請求,最終會走到mWindowSession.addToDisplay()方法,我們可以在Session中找到這個函數實現,如下:

 @Override
    public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs,
            int viewVisibility, int displayId, Rect outContentInsets, Rect outStableInsets,
            Rect outOutsets, InputChannel outInputChannel) {
        //返回WMS中addWindow所返回的結果
        return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId,
                outContentInsets, outStableInsets, outOutsets, outInputChannel);
    }

可以看到addToDisplay方法中最終返回了WMS中addWindow所返回的結果,Window的添加請求就交給WMS去處理,addWindow的實現在WMS中,裏面代碼很長,這裏就不再深究了,addWindow主要做的事情是先進行窗口的權限檢查,因爲系統窗口需要聲明權限,然後根據相關的Display信息以及窗口信息對窗口進行校對,再然後獲取對應的WindowToken,再根據不同的窗口類型檢查窗口的有效性,如果上面一系列步驟都通過了,就會爲該窗口創建一個WindowState對象,以維護窗口的狀態和根據適當的時機調整窗口狀態,最後就會通過WindowState的attach方法與SurfaceFlinger通信。因此SurfaceFlinger能使用這些Window信息來合成surfaces,並渲染輸出到顯示設備。
2、輸入事件的管理與分發
當我們的觸摸屏幕時就會產生輸入事件,在Android中負責管理事件的輸入是InputManagerService,它會尋找一個最合適的窗口來處理輸入事件,WMS是窗口的管理者,因此WMS會把這些輸入事件分發給合適的窗口,然後就會涉及我們熟悉的事件分發機制。我們來再來看在ViewRootImp的setView中調用mWindowSession.addToDisplay方法時傳入的參數:

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
  	//...	
   	mInputChannel = new InputChannel();
    //...
    //通過session與WMS建立通信,同時通過InputChannel接收輸入事件回調
     res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                                getHostVisibility(), mDisplay.getDisplayId(),
                                mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                                mAttachInfo.mOutsets, mInputChannel);
    //...
     if (mInputChannel != null) {
         //...
         //處理輸入事件回調
         mInputEventReceiver = new WindowInputEventReceiver(mInputChannel, Looper.myLooper());
     }
}

注意這個傳入的mInputChannel參數,它實現了Parcelable接口,用於接受WMS返回來的輸入事件。
(2.3)WMS系統結構
在這裏插入圖片描述

3、WindowManager

(3.1)定義
Window管理器,用戶通過WindowManager對Window的View進行操作。一個Window對應一個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。可以看到這些方法傳入的參數是View,不是Window,說明WindowManager管理的是Window中的View,我們通過WindowManager操作Window就是在操作Window中的View。WindowManager的具體實現類是WindowManagerImp。相應方法的實現爲:

public final class WindowManagerImpl implements WindowManager {
    private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
    private final Context mContext;
    private final Window mParentWindow;
    //...
    
    private WindowManagerImpl(Context context, Window parentWindow) {
        mContext = context;
        mParentWindow = parentWindow;
    }
    
      @Override
    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        //...
        mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
    }

    @Override
    public void updateViewLayout(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        //...
        mGlobal.updateViewLayout(view, params);
    }
    
     @Override
    public void removeView(View view) {
        mGlobal.removeView(view, false);
    }
}

可以看到WindowManagerImp也沒有做什麼,它把3個方法的操作都委託給了WindowManagerGlobal這個單例類,我們還看到了mParentWindow這個字段,它是Window類型,是從構造中被傳入,所以WindowManager會持有Window的引用,這樣WindowManager就可以對Window做操作了。
(3.2)使用
通過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);

    }
}

添加Window設置的爲系統Window,故應添加權限

<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>

效果如下:
在這裏插入圖片描述
第二個界面是鎖屏界面,由於按鈕是處於較大層級的系統 Window 中的,所以可以看到 button。

4、Window、WindowManager、WindowManagerService關係

在Android開發中,Window是所有視圖View的載體,如Activity,Dialog和Toast的視圖,我們想要對Window進行添加和刪除、更新View就要通過WindowManager來操作,而WindowManager就是通過Binder(Session)與WindowManagerService進行跨進程通信,把具體的實現工作交給WindowManagerService(下面簡稱WMS)。WMS會爲每一個Window創建一個WindowState並管理它們,具體的渲染工作WMS就交給SurfaceFinger處理。
類圖:
在這裏插入圖片描述
窗口的本質就是一塊顯示區域,在Android中就是繪製的畫布:Surface,當一塊Surface顯示在屏幕上時,就是用戶所看到的窗口了。WindowManagerService 添加一個窗口的過程,其實就是 WindowManagerService 爲其分配一塊 Surface 的過程,一塊塊的 Surface 在 WindowManagerService 的管理下有序的排列在屏幕上,Android 才得以呈現出多姿多彩的界面。於是根據對 Surface 的操作類型可以將 Android 的顯示系統分爲三個層次,如下圖:
在這裏插入圖片描述

(二)Window Manager 實現機制( Window的管理過程)

以addView爲例:

1、WindowManagerGlobal具體實現操作

在實際使用中無法直接訪問 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 來處理。

2、檢查參數合法性,如果是子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){
//爲子Window
   parentWindow.adjustLayoutParamsForSubWindow(wparams);
}

3、創建ViewRootImpl並將View添加到WindowManagerGlobal集合中

addView 操作時會1、創建ViewRootImpl(用於對View進行繪製) 2、將相關對象添加到對應集合中:

root = new ViewRootImpl(view.getContext(),display);
view.setLayoutParams(wparams);

mViews.add(view);
mRoots.add(root);
mParams.add(wparams);

root.setView(view, wparams, panelParentView);

在 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>();

Views 存儲的是所有 Window 所對應的 View,mRoots 存儲的是所有 Window 所對應的 ViewRootImpl,mParams 存儲的是所有 Window 所對應的佈局參數,mDyingViews 存儲了那些正在被刪除的 View 對象,或者說是那些已經調用了 removeView 方法但是操作刪除還未完成的 Window 對象,可以通過表格直觀的表示:
在這裏插入圖片描述

4、通過ViewRootImpl來更新界面並通過Session請求WindowManagerService完成Window的添加過程

在學習 View 的工作原理時,我們知道 View 的繪製過程是由 ViewRootImpl 來完成的,這裏當然也不例外,具體是通過 ViewRootImpl 的 setView 方法來實現的。在 setView 內部會通過 requestLayout 來完成異步刷新請求,如下:

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
  	//...	
    //這裏會進行View的繪製流程
    requestLayout();
public void requestLayout(){
   if(!mHandingLayoutInLayoutRequest){
       checkThread();
       mLayoutRequested = true;
       scheduleTraversals();
   }
}

可以看到 scheduleTraversals 方法是 View 繪製的入口,繼續查看它的實現:

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
...
//通過session與WMS建立通信
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);
}

至此建立起與WMS的通信的橋樑。然後WindowManager就間接的通過Session向WMS發起顯示窗口視圖的請求,WMS會嚮應用返回和窗口交互的信息。終於,Window 的添加請求移交給 WindowManagerService 手上了,在 WindowManagerService 內部會爲每一個應用保留一個單獨的 Session,具體 Window 在 WindowManagerService 內部是怎麼添加的,就不對其進一步的分析,因爲到此爲止我們對 Window 的添加這一從應用層到 Framework 的流程已經清楚了。
流程圖:
在這裏插入圖片描述
總結:用戶通過WindowManager對Window進行操作管理,它們最終都會通過一個IPC過程將操作移交給WindowManagerService這個位於Framework層的窗口管理服務來處理。

(三)Window創建過程

View 是 Android 中的視圖的呈現方式,但是 View 不能單獨存在,它必須附着在 Window 這個抽象的概念上面,因此有視圖的地方就有 Window。因此 Activity、Dialog、Toast 等視圖都對應着一個 Window。這也是面試中常問到的一個知識點:一個應用中有多少個 Window?下面分別分析 Activity、Dialog以及 Toast 的 Window 創建過程。

1、Activity的Window創建過程

Window 本質就是一塊顯示區域,所以關於 Activity 的 Window 創建應該發生在 Activity 的啓動過程,Activity 的啓動過程很複雜,最終會由 ActivityThread 中的 performLaunchActivity() 來完成整個啓動過程,在這個方法內部會通過類加載器創建 Activity 的實例對象,並調用其 attach 方法爲其關聯運行過程中所依賴的一系列上下文環境變量。

(1)創建Window

(1.1)attach方法創建Window對象並設置回調接口
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 等等。
(1.2)通過PolicyManager創建PhoneWindow對象
Activity 的 Window 是通過 PolicyManager 的一個工廠方法來創建的,但是在 PolicyManager 的實際調用中,PolicyManager 的真正實現是 Policy 類,Policy 類中的 makeNewWindow 方法的實現如下:

public Window  makeNewWindow(Context context){
   return new PhoneWindow(context);
}

可以看出,Window 的具體實現類的確是 PhoneWindow。到這裏 Window 以及創建完成了

(2)初始化DecorView並將Activity的視圖添加到DecorView中

創建PhoneWindow後應將Activity視圖附屬到Window上,Activity的視圖由setContentView提供

public void setContentView(int layoutResID){
   getWindow().setContentView(layoutResID);
   initWindowDecorActionBar();
}

可以看到,Activity 將具體實現交給了 Window,而 Window 的具體實現是 PhoneWindow,所以只需要看 PhoneWindow 的相關邏輯即可,它的處理步驟如下:
(2.1)創建DecorView
DecorView 是 Activity 中的頂級 View,是一個 FrameLayout,一般來說它的內部包含標題欄和內容欄,但是這個會隨着主題的變化而改變,不管怎麼樣,內容欄是一定存在的,並且有固定的 id:”android.R.id.content”,在 PhoneWindow 中,通過 generateDecor 方法創建 DecorView,通過 generateLayout 初始化主題有關佈局。
(2.2)將Activity View 添加到DecorView的mContentParent中
直接將 Activity 的視圖添加到 DecorView 的 mContentParent 中即可,由此可以理解 Activity 的 setContentView 這個方法的來歷了,爲什麼不叫 setView 呢?因爲 Activity 的佈局文件只是被添加到 DecorView 的 mContentParent 中,因此叫 setContentView 更加具體準確。
(2.3)回調Activity的onContentChanged方法通知Activity視圖已經發生改變
前面分析到 Activity 實現了 Window 的 Callback 接口,這裏當 Activity 的視圖已經被添加到 DecorView 的 mContentParent 中了,需要通知 Activity,使其方便做相關的處理。

經過上面的三個步驟,DecorView 已經被創建並初始化完畢,Activity 的佈局文件也已經成功添加到了 DecorView 的 mContentParent 中,但是這個時候 DecorView 還沒有被 WindowManager 正式添加到 Window 中。

(3)將DecorView添加到Window並顯示

在 ActivityThread 的 handleResumeActivity 方法中,首先會調用 Acitivy 的 onResume 方法,接着會調用 Acitivy 的 makeVisible() 方法,正是在 makeVisible 方法中,DecorView 才真正的完成了顯示過程,到這裏 Activity 的視圖才能被用戶看到,如下:

void makeVisible(){
   if(!mWindowAdded){
      ViewManager wm = getWindowManager();
      wm.addView(mDecor, getWindow().getAttributes());//將DecorView添加到Window
      mWindowAdded = true;
   }
   mDecor.setVisibility(View.VISIBLE);//顯示DecorView
}

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(extends ContextThemeWrapper),如果採用 Application 的 Context(extends ContextWrapper) 就會報錯。這是因爲沒有應用 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。

(1)Toast 訪問 NotificationManagerService

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 來實現。
代碼在顯示 Toast 中調用了 NMS 的 enqueueToast 方法, enqueueToast 方法內部將 Toast 請求封裝爲 ToastRecord 對象並將其添加到一個名爲 mToastQueue 的隊列中,對於非系統應用來說,mToastQueue 中最多同時存在 50 個 ToastRecord,用於防止 DOS (Denial of Service 拒絕服務)。

(2)NotificationManagerService 回調 Toast 裏的 TN 接口

TN 是一個 Binder 類,當 NMS 處理 Toast 的顯示或隱藏請求時會跨進程回調 TN 中的方法。由於 TN 運行在 Binder 線程池中,所以需要通過 Handler 將其切換到當前線程中,這裏的當前線程指的是發送 Toast 請求所在的線程。
當 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 來實現視圖的移除,這裏就不再貼出。

(四)ViewRoot、DecorView、Window、Activity

1、Activity控制器

Activity相當於應用程序的載體。用於控制生命週期&處理事件。可用來統籌視圖的添加&顯示。並通過其他回調方法與Window、View交互。
Activity不負責視圖控制,真正控制視圖的是Window,真正代表一個窗口。1個Activity包含一個Window

2、Window承載器

Window承載着視圖View的顯示。Window類是一個抽象類,PhoneWindow是其具體且唯一實現類。PhoneWindow中有個內部類DecorView是View的根佈局。通過創建DecorView來加載Activity中設置的佈局。
Window類通過WindowManager加載DecorView,並將DecorView交給ViewRoot進行視圖繪製及其他交互。

3、DecorView根佈局

DecorView是頂層視圖,即Android視圖樹的根節點;同時也是FrameLayout的子類。用於顯示與加載佈局。View層的事件都先經過DecorView再傳遞到View。
內含1個豎直方向的LinearLayout,分爲2部分:
上 = 標題欄(titlebar)
下 = 內容欄(content)
在這裏插入圖片描述

在Activity中通過 setContentView()所設置的佈局文件其實是被加到內容欄之中的,成爲其唯一子View =
id爲content的FrameLayout中

// 在代碼中可通過content得到對應加載的佈局

// 1. 得到content
ViewGroup content = (ViewGroup)findViewById(android.R.id.content);
// 2. 得到設置的View
ViewGroup rootView = (ViewGroup) content.getChildAt(0);

4、ViewRoot連接器

ViewRoot是連接器,對應於ViewRootImpl類,用於連接WindowManager和DecorView,並完成View的繪製流程。可以與WMS交互通訊,調整窗口大小及佈局、向DecorView派發輸入事件及完成三大繪製流程:measure、Layout、draw

// 在主線程中,Activity對象被創建後:
// 1. 自動將DecorView添加到Window中 & 創建ViewRootImpll對象
root = new ViewRootImpl(view.getContent(),display);

// 3. 將ViewRootImpll對象與DecorView建立關聯
root.setView(view,wparams,panelParentView)

5、ViewRoot、DecorView、Window、Activity關係

在這裏插入圖片描述
Activity在onCreate時調用attach方法,在attach方法中會創建window對象。window對象創建時並沒有創建 DocerView 對象。用戶在Activity中調用setContentView,其實就是調用window的setContentView,這時會檢查DecorView是否存在,如果不存在則創建DecorView對象,然後把用戶自己的 View 添加到 DecorView 中。

(五)總結

任何 View 都是附屬在一個 Window 上面的,Window 表示一個窗口的概念,也是一個抽象的概念,Window 並不是實際存在的,它是以 View 的形式存在的。WindowManager 是外界也就是我們訪問 Window 的入口,Window 的具體實現位於 WindowManagerService 中,WindowManagerService 和 WindowManager 的交互是一個 IPC 過程。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章