第一章 四大组件 之 补充

文章目录

第一章 四大组件

补充 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 过程。

发布了74 篇原创文章 · 获赞 15 · 访问量 6255

猜你喜欢

转载自blog.csdn.net/qq_29966203/article/details/90735948
今日推荐