Android四大组件——Activity

  • 一、概述
  • 二、生命周期
  • 2.1 活动状态
  • 2.2 生存期
  • 2.3 保存临时数据
  • 2.3.1 触发时机
  • 2.3.2 调用函数
  • 2.3.3 横竖屏切换问题
  • 2.3.4 多窗口模式问题
  • 三、Intent
  • 3.1 显式intent
  • 3.2 隐式intent
  • 3.3 数据传递
  • 3.3.1 传递方式
  • 3.3.2 自定义对象传递
  • 3.3.3 Application定义全局变量
  • 3.4 系统intent
  • 3.5 启动Activity
  • 四、Activity管理机制
  • 4.1 taskAffinity和allowTaskReparenting
  • 4.2 alwaysRetainTaskState、clearTaskOnLaunch 和finishOnTaskLaunch
  • 4.3 launchMode
  • 4.4 Intent Flags
  • 五、异步消息处理机制
  • 5.1 机制组成
  • 5.2 处理过程
  • 5.2.1 生成消息线程
  • 5.2.2 使用消息线程
  • 5.2.3 runOnUiThread
  • 5.3 内存泄漏
  • 5.3.1 问题产生
  • 5.3.2 问题解决


一、概述

       Activity是应用程序与用户进行交互的界面,一个应用程序可以有多个Activity,这些Activity共同存放在Activity栈中,先进栈的后出栈

二、生命周期

2.1 活动状态

       一个Activity有如下四种状态:

  1. 运行状态:Activity位于栈顶,与用户进行交互,可见;
  2. 暂停状态:Activity不在栈顶,但是可见,如弹出框后面的Activity,内存极低时会被系统回收;
  3. 停止状态:Activity不在栈顶,不可见,如从一个Activity启动另一个Activity,可能被系统回收;
  4. 销毁状态:从栈顶移除,肯定被回收。

2.2 生存期

android activity是否在最上层 安卓activity位置_activity


图1 生存周期 其中,

  • 完整生存期:onCreate()-> … ->onDestory(),从创建到销毁;
  • 可见生存期:onStart() -> … ->onStop(),从可见到不可见,再到onStart()状态需要经过onRestart(),如从一个Activity启动另一个Activity后按下返回键回到之前的Activity;
  • 前台生存期:onResume()-> … ->onPause(),从栈顶可交互到非栈顶不可交互,注:onPause()被调用必须是启动了新的Activity,而onPause()又要满足可见条件,所以该Activity的样式应该是透明的或者是dialog的

进程生命周期:

  1. 前台进程;2. 可见进程; 3.服务进程; 4. 后台进程; 5.空进程,内部不包含应用程序的任何组件。

2.3 保存临时数据

2.3.1 触发时机

  1. 点击home键回到主页或长按后选择运行其他程序;
  2. 按下电源键关闭屏幕;
  3. 启动新的Activity;
  4. 横竖屏切换;
  5. 多窗口模式进入与退出。

        即,不是主动按下返回键或者主动调用finish()方法销毁activity,但系统仍可能会将activity销毁。系统“未经许可”销毁了activity,则它必须要提供一个机会让你保存数据(你可以保存也可以不保存)

2.3.2 调用函数

  1. 保存:protected void onSaveInstanceState(Bundle outState)被调用,可以通过outState.putInt()等方法携带数据;
  2. 读取:protected void onCreate(Bundle savedInstanceState)或者protected void onRestoreInstanceState(Bundle savedInstanceState)被调用,可以通过savedInstanceState.getInt()等方法获取数据。
    区别:onCreate()方法被调用不一定是为了恢复旧Activity,所以传入参数可能为null;而onRestoreInstanceState()方法一定是之前有触发onSaveInstanceState()方法,并且该Activity被系统回收了,为了恢复旧Activity而被调用的,所以传入参数不是null,且onRestoreInstanceState()调用顺序在onStart()之后。 横竖屏切换重建activity时肯定会调用onRestoreInstanceState()

另,上述调用函数还有一种重载形式,即传入两个参数,在android.app.Activity包下,如public void onRestoreInstanceState(Bundle savedInstanceState, PersistableBundle persistentState),拥有系统关机后重启的数据恢复能力。而上述函数在android.support.v7.app.AppCompatActivity包下,Activity与AppCompatActivity的主要区别是后者支持低版本兼容,默认带有actionbar。

2.3.3 横竖屏切换问题

  1. 禁止屏幕横竖屏自动切换
    横竖屏切换时会先销毁Activity,然后再重新创建。若要禁止屏幕横竖屏自动切换,可在AndroidManifest.xml中为Activity添加android:screenOrientation属性, 为应用程序固定一种屏幕展示方式,有下述值可选:
  • unspecified:默认值,由系统来判断显示方向。判定的策略是和设备相关的,所以不同的设备会有不同的显示方向;
  • landscape:横屏显示;
  • portrait:竖屏显示;
  • user:用户当前首选的方向;
  • behind:和该Activity下面的那个Activity的方向一致(在Activity栈中);
  • sensor:由物理感应器决定。如果用户旋转设备这屏幕会横竖屏切换;
  • nosensor:忽略物理感应器,这样就不会随着用户旋转设备而更改了("unspecified"设置除外)。
  1. 横竖屏展示不同的布局
    可创建两个布局文件夹,layout-portrait和layout-landscape。
  2. 判断当前是横屏还是竖屏
if(getResources().getConfiguration().orientation==Configuration.ORIENTATION_LANDSCAPE)
   ...
else if(getResources().getConfiguration().orientation==Configuration.ORIENTATION_PORTRAIT)
   ...
  1. 通过代码设置横竖屏
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
  1. 禁止横竖屏切换时重建activity
    可在AndroidManifest.xml文件中,对Activity配置configChanges属性为orientation|keyboardHidden|screen-Size|screenLayout,使应用程序监控configChanges中配置的事件,并调用public void onConfigurationChanged(Configuration newConfig),在函数中若不进行额外处理,则进入多窗口模式或者进行横竖屏切换可不重新创建活动。

2.3.4 多窗口模式问题

  1. 进入多窗口模式
    Android 7.0+会自动引入多窗口模式功能,而进入多窗口模式的方式有两种,一种是在打开app后长按overview,另一种是点击overview后将app窗口拖入多窗口模式。
  2. 生命周期
    同横竖屏切换。而多窗口模式中的一个窗口与用户进行交互,而另一个窗口则处于暂停状态。
  3. 禁止重新创建活动
    同横竖屏切换
  4. 禁止多窗口模式的方式
    · 可在application或者activity中配置resizeableActivity属性为false。只能在targetSdkVersion为24+时才有效。
    · targetSdkVersion低于24时,若应用不支持横竖屏切换,则也无法进行多窗口模式。而禁用横竖屏切换的方式为配置screenOrientation属性为portrait竖屏、landscape横屏。

三、Intent

        Intent是将要执行的操作的抽象描述,即“意图”,可以用来启动Activity,发送Broadcast和后台Service通信等,能在不同应用程序代码间进行运行时绑定,是activities之间交流的桥梁纽带。

3.1 显式intent

        intent有明确的component,能提供确切的类,通常是应用程序启动各种内部activity的一种方式。显式定义intent的方式有以下5中:

  1. public Intent(Context packageContext, Class<?> cls)
  2. public Intent setClass(Context packageContext, Class<?> cls)
  3. public Intent setClassName(Context packageContext, String className)
  4. public Intent setClassName(String packageName, String className)
  5. public Intent setComponent(ComponentName component)

        其本质就是通过相关参数确定component,并将component传入intent。虽然上述方法接收的参数各有不同,但除了直接给定component外,其他都是通过第一个参数获取component所在包的packageName,通过第二个参数获取component的全类名。其中,packageName确定intent所指向的应用程序,component全类名则确定intent所指向的应用程序中的唯一component(应用包名下有新建包),这样就可以确定系统中的唯一component,注:启动其他应用程序的组件需要该组件支持外部程序访问

3.2 隐式intent

        intent没有明确的component,需要提供actioncategorytype信息并与AndroidManifest.xml文件中的<intent-filter>匹配符合条件的component。AndroidManifest.xml中可以给action、category配置多条,但隐式启动activity时,category中必须有一条默认的,即 <category android:name=“android.intent.category.DEFAULT”/> ,使Context.startActivity方法能解析到。其中,程序入口的标注方式如下:

<intent-filter>
     <action android:name="android.intent.action.MAIN" />
     <category android:name="android.intent.category.LAUNCHER" />
 </intent-filter>

       而intent对象只能设定一个action,可以设定多个category,而type可以显式或者隐式声明在intent中,如利用setType(String type)进行显式声明,或者利用setData(Uri data)以及public Intent(String action, Uri uri)进行隐式声明,data为uri格式,type根据uri中的scheme确定,无论显式或者隐式,type在intent中只能声明一次,或者利用setDataAndType(Uri data, String type)同时声明。其中,URI与mimeType分别为:

  1. URI——统一资源标识符
           URL是其中一种标识资源的具体方式。URI一般由三部分组成:scheme协议名、[host:port]服务器名/[authority]、path路径。
  2. mimeType
           格式为type/subtype,其中标准的mimeType的type有如下类型,text、multipart、 application、message、image、audio、video,而subtype为type的详细形式。例如,text/html、image/jpeg等。
           自定义mimeType(vendor-specific)由ContentProvider中的getType(Uri uri)返回,通常由2部分组成,第一部分以"vnd.android.cursor"开头,单行数据则为"vnd.android.cursor.item",多行数据则为"vnd.android.cursor.dir",第二部分由<vnd>.<authority>.<path>。例如,“vnd.android.cursor.item/vnd.google.note”、“vnd.android.cursor.dir/vnd.google.note”。

3.3 数据传递

3.3.1 传递方式

  • 直接调用intent对象的putExtra(String key, value)方法接收数据,其中,value值可以是基本数据类型以及对应的数组,String以及String[]、CharSequence以及CharSequence[]等,以上数据类型的集合可以调用putStringArrayListExtra(ArrayList<String> valueList)等类似方法;
  • 将数据保存在buddle对象中,然后再调用intent对象的putExtra(String key, Buddle value)或者putExtras(Buddle value)方法接收数据。其中,将数据保存在buddle对象中可以调用putString(String value)以及putStringArray(String[] value)、putStringArrayList(ArrayList<String> valueList)等类似形式的方法。

       以上两种方式都可以进行数据传递,区别在于方式二将数据先封装在buddle对象中,提高了数据的复用性,在传递相同数据给不同component时更方便,降低了数据与intent的耦合性。

3.3.2 自定义对象传递

  1. JSON
           将JAVA对象转化为JSON字符串进行传递,解析时再将JSON字符串转化为JAVA对象;
  2. Serializable
           实现方式:使javabean对象实现Seriablizable接口,整个对象都能传输。
           发送:intent.putExtra(String tag, javabean)或者buddle.putSeriablizable()
           接收:intent.getSeriablizableExtra(String tag, javabean)或者buddle.getSeriablizable()
  3. Parcelable
  • 使javabean对象实现Parcelable接口,并实现int describleContents()void writeToParcel(parcel, int flags) 方法。describleContents()方法默认返回0即可;writeToParcel方法中将javabean对象中需要传输的成员变量写入到parcel中,写入方式为调用parcel的writeString()、writeInt()方法。
  • 定义静态常量Parcel.Creator<javabean>,实现javabean createFromParcel(parcel)javabean[] newArray(int size) 方法,createFromParcel从parcel中按写入顺序读取成员变量,读取方式为调用parcel的readString()、readInt()方法。
           发送:intent调用putExtra(String tag,value)、putParcelableArrayListExtra(String tag,ArrayList javabean),其中value为Parcelable或者Parcelable[]
           buddle调用putParcelable()、putParcelableArray()或者putParcelableArrayList()。
    区别:Parcel可以按需传输,不用每次都传递整个对象,传输速度快;Seriablizable方便。有相关插件可直接生成Parcel接口代码。
@Override  
public int describeContents() {  
     return 0;  
}  

@Override  
public void writeToParcel(Parcel parcel, int flags){  
    parcel.writeString(bookName);  
    parcel.writeString(author);  
    parcel.writeInt(publishTime);  
}  

public static final Parcelable.Creator<Book> CREATOR = new Creator<Book>() {  
    @Override  
    public Book[] newArray(int size) {  
        return new Book[size];  
    }  
          
    @Override  
    public Book createFromParcel(Parcel source) {  
        Book mBook = new Book();    
        mBook.bookName = source.readString();   
        mBook.author = source.readString();    
        mBook.publishTime = source.readInt();   
        return mBook;  
    }  
};

3.3.3 Application定义全局变量

  1. 定义Application
    继承Application类,并在AndroidManifest.xml文件中的<application>标签中设定android:name属性为该Application类。
  2. 获取Application
    getApplicationContext()或者在Application类中利用静态变量将自身对象暴露出来。

3.4 系统intent

//1.拨打电话
// 给移动客服10086拨打电话
Uri uri = Uri.parse("tel:10086");
Intent intent = new Intent(Intent.ACTION_DIAL, uri);

//2.发送短信
// 给10086发送内容为“Hello”的短信
Uri uri = Uri.parse("smsto:10086");
Intent intent = new Intent(Intent.ACTION_SENDTO, uri);
intent.putExtra("sms_body", "Hello");

//3.打开浏览器:
// 打开baidu主页
Uri uri = Uri.parse("http://www.baidu.com");
Intent intent  = new Intent(Intent.ACTION_VIEW, uri);

//4.多媒体播放:
Intent intent = new Intent(Intent.ACTION_VIEW);
Uri uri = Uri.parse("file:///sdcard/foo.mp3");
intent.setDataAndType(uri, "audio/mp3");

//5.获取SD卡下所有音频文件,然后播放第一首=-= 
Uri uri = Uri.withAppendedPath(MediaStore.Audio.Media.INTERNAL_CONTENT_URI, "1");
Intent intent = new Intent(Intent.ACTION_VIEW, uri);

//6.打开摄像头拍照:
// 打开拍照程序
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); 
startActivityForResult(intent, 0);
// 取出照片数据
Bundle extras = intent.getExtras(); 
Bitmap bitmap = (Bitmap) extras.get("data");

//另一种:
//调用系统相机应用程序,并存储拍下来的照片
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); 
time = Calendar.getInstance().getTimeInMillis();
intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(new File(Environment
.getExternalStorageDirectory().getAbsolutePath()+"/tucue", time + ".jpg")));
startActivityForResult(intent, ACTIVITY_GET_CAMERA_IMAGE);

//7.进入手机设置界面:
// 进入无线网络设置界面(其它可以举一反三)  
Intent intent = new Intent(android.provider.Settings.ACTION_WIRELESS_SETTINGS);  
startActivityForResult(intent, 0);

//8.安装与卸载apk:
Uri uri = Uri.fromParts("package", strPackageName, null); 
Intent it = new Intent(Intent.ACTION_DELETE, uri);
it = new Intent(Intent.ACTION_PACKAGE_ADDED, uri);

//9.进入联系人页面:
Intent intent = new Intent();
intent.setAction(Intent.ACTION_VIEW);
intent.setData(People.CONTENT_URI);

//17.查看指定联系人:
Uri personUri = ContentUris.withAppendedId(People.CONTENT_URI, info.id);//info.id联系人ID
Intent intent = new Intent();
intent.setAction(Intent.ACTION_VIEW);
intent.setData(personUri);

3.5 启动Activity

  • startActivity(Intent intent)
  • startActivityForResult(Intent intent, int requestCode)
  1. 在下一个Activity中通过setResult(int resultCode,Intent intent)来设置返回结果,intent不需要指定component,setResult()必须在finish()方法之前调用,且最好主动调用finish()方法,因为设置的返回结果是在finish()方法中进行返回的。一般不要在onPause()、onStop()、onDestory()方法中调用setResult(),因为不能保证这些方法在执行之后会调用finish()方法。注:使用返回键,resultCode会被重置为RESULT_CANCEL
  2. 在上一个Activity中的onAcitivityResult(int requestCode,int resultCode,Intent intent)中处理返回结果。其中,requestCode用于区分数据来源,resultCode用于判断处理结果,Intent用于携带数据。requestCode只要唯一就行,ResultCode一般为RESULT_OK或RESULT_CANCELED。

四、Activity管理机制

4.1 taskAffinity和allowTaskReparenting

  1. taskAffinity
  • 对于Activity来说,每个Activity都有taskAffinity属性,该属性代表Activity是否处于同一个Task。如果Activity没有显式指明taskAffinity,那么该属性就等于Application的taskAffinity,如果 Application也没有指明,那么该属性值就等于包名。
  • 对于Task来说,其affinity属性值等于它的根 Activity的taskAffinity的值。
  1. 无缝衔接
    在一个应用程序中启动另一个应用程序的activity,不会重新建栈,而是将该activity继续添加进原来的栈中。
  2. allowTaskReparenting
    当该值为true时表示该activity所在的栈进入后台,而与该activity具有相同affinity值的另外一个task进入前台,则该activity会从启动它的task栈中转移到相同affinity值的前台task栈中并显示。启动该activity的task栈与和该activity具有相同affinity值的task栈的affinity值不同,即启动该activity的task栈与这个activity具有不同的affinity值,而这种情况的产生来源于无缝衔接效果。

4.2 alwaysRetainTaskState、clearTaskOnLaunch 和finishOnTaskLaunch

       当Task长期停留在后台时,系统会清除task中栈底Activity外的所有Activity 。

  1. alwaysRetainTaskState: 如果栈底Activity的这个属性被设置为true,则Task中的所有activity将被长时间保存。
  2. clearTaskOnLaunch:如果栈底activity的这个属性被设置为true,一旦该Task转移至后台, 则 Task栈中的Activity将被清空到只剩下栈底activity。即使只是短暂地切换至后台。
  3. finishOnTaskLaunch:只对单独的activity操作。当它设置为true时,当前的Activity所在栈转移至后台后,该activity将被清除,即使该activity是栈底的Activity。

4.3 launchMode

  1. Standard模式
    默认,每次启动都会新建一个activity,无论该activity是否已经在栈顶。
  2. SingleTop模式
    会检测调用方目标栈中栈顶是否有该activity,若有则不会重新创建,但会调用protected void onNewIntent(Intent intent)方法,可以刷新数据。若没有,则重新创建activity。总之,只涉及调用方所在的这一个栈。
  3. SingleTask模式
    系统中只允许有包含该activity的一个task。
  • 若是同一个应用程序,则只涉及一个栈的问题。activity不在栈顶,则将activity移至该activity所在栈的栈顶,触发onNewIntent()方法,并把之前在activity之上的所有activity出栈;若activity未进栈,则在该栈的栈顶创建该activity实例。
  • 若启动的是其他应用程序的activity,则涉及调用方所在栈以及与activity的taskAffinity值相匹配的栈。如果该activity未进栈,则创建一个新的栈以及一个activity实例,并将该activity作为新栈的栈底activity;若不在栈顶,则将activity移至该栈的栈顶并调用onNewIntent()方法,之上的activity出栈。
  1. SingleInstance模式
    系统中只允许有该activity一个实例,且其所在栈也有且只有一个activity。
  • 若是同一个应用程序,且该activity还未进栈则相当于从原始栈中另外开辟一个新栈,且只存放一个activity。若已进栈,则调用onNewIntent()方法更新数据。
  • 若启动的是其他应用程序的activity,则涉及调用方所在栈、原始栈以及新栈。如果activity还未进栈,则相当于从原始栈中另外开辟一个新栈,且只存放一个activity。若activity已进栈,则调用onNewIntent()更新数据。

4.4 Intent Flags

  • FLAG_ACTIVITY_NEW_TASK:寻找非调用方的task存放activity,若能找到除调用方task外的与activity的taskAffinity值相匹配的task,则将该activity压入该栈中,若没有则创建新栈存放。一般用于在非activity组件中启动activity时使用。
  • FLAG_ACTIVITY_SINGLE_TOP:与SingleTop模式对应
  • FLAG_ACTIVITY_CLEAR_TOP:与SingleTask模式对应
  • FLAG_ACTIVITY_NO_HISTORY:该activity不在栈中保存。

五、异步消息处理机制

       主线程中不能进行耗时操作。若要进行耗时操作,则需要开辟子线程。但由于UI操作是线程不安全的,所以针对UI的操作,系统采用的是单线程模式,即只能在主线程中更新UI,而不能在子线程中进行UI操作。所以当子线程进行一些处理后需要进行UI操作时,需要告知主线程进行处理,这就涉及不同线程间通信的问题。

5.1 机制组成

android activity是否在最上层 安卓activity位置_launch mode_02


图2 异步消息处理机制

  • Message:用于不同线程之间传递信息,what代表消息类型,arg0、arg1可以保存两个整型数据,object可以保存对象;
  • MessageQueue:每个线程有一个MessageQueue,用于存放message;
  • Looper:一个MessageQueue对应一个Looper,用来管理MessageQueue;
  • Handler:在创建handler的线程上进行消息发送sendMessage()和消息处理handleMessage()。

5.2 处理过程

5.2.1 生成消息线程

  1. 创建Looper
public static void prepare() {
	prepare(true);
}

private static void prepare(boolean quitAllowed) {
	if (sThreadLocal.get() != null) {
		throw new RuntimeException("Only one Looper may be created per thread");
	}
	sThreadLocal.set(new Looper(quitAllowed));
}

       调用Looper.prepare()方法,该方法会先调用looper的构造方法创建looper对象,构造方法中会创建MessageQueue作为looper的一个成员变量,并设置在sThreadLocal中。

  1. 创建handler子类对象
           handler中定义了空的handleMessage()方法,只能通过继承的方式重写handleMessage()方法;或者也可以调用带有callback接口参数的构造方法创造对象以及new Handler().post(Runnable)。构造方法会先从sThreadLocal中取出looper。
  2. 死循环读取message
           调用Looper.loop()会通过死循环从MessageQueue中循环读取message,并调用对应的handler的dispatchMessage(),该方法中会判断是调用handleCallback()还是调用handleMessage()。
public void dispatchMessage(Message msg) {
	if (msg.callback != null) {
		handleCallback(msg);
	} else {
		if (mCallback != null) {
			if (mCallback.handleMessage(msg)) {
				return;
			}
		}
		handleMessage(msg);
	}
}

private static void handleCallback(Message message) {
	message.callback.run();
}
  1. 标准异步处理线程
class LooperThread extends Thread {

	public Handler mHandler;

	public void run() {
		Looper.prepare();

		mHandler = new Handler() {
			public void handleMessage(Message msg) {
			// process incoming messages here
			
			}
 		};

		Looper.loop();
	}
}

5.2.2 使用消息线程

looperThread = new LooperThread();  
// 启动新线程  
looperThread.start();  
// 创建消息  
Message msg = new Message();  
msg.what = 0x123;  
Bundle bundle = new Bundle();  
bundle.putInt("data",1);  
msg.setData(bundle);  
// 向新线程中的Handler发送消息  
looperThread.mHandler.sendMessage(msg);
  • message的创建可以通过调用构造方法的方式,也可以调用handler.obtainMessage()Message.obtain() 方法。构造方法会直接创建新对象,而后面两种方式内部也是调用的Message.obtain()方法从message池中获取message进行复用,在message使用完后会从新进入message池。
  • sendMessage()方法会将handler对象赋值给msg的成员变量,以便looper从MessageQueue提取消息并分发给对应的handler进行处理。

5.2.3 runOnUiThread

       子线程想要更新UI,可以调用主线程中创建的Handler对象发送消息,消息进过一系列辗转最终会自动调用该Handler对象的handleMessage()中实现的方法或者是runnable中的方法。runOnUiThread也是封装了这一系列过程。

public final void runOnUiThread(Runnable action) {
    if (Thread.currentThread() != mUiThread) {
        mHandler.post(action);
    } else {
        action.run();
    }
}

//handler中的post方法
public final boolean post(Runnable r)
{
   return  sendMessageDelayed(getPostMessage(r), 0);
}

private static Message getPostMessage(Runnable r) {
	Message m = Message.obtain();
	m.callback = r;
	return m;
}

       runOnUiThread()方法先判断自身是在哪个线程中被调用的,如果不是主线程,则调用主线程中的handler(该mHandler对象是主线程初始化时预先在内部创建的)的post方法,该方法最终也是向MessageQueue中添加message,并将runnable的子类对象赋值给message的callback成员变量,供处理message时调用dispatchMessage()使用;如果是主线程,则相当于在主线程中执行runnable,且立即执行。

5.3 内存泄漏

       程序中己动态分配的堆内存由于某种原因(例如,有另外一个正在使用的对象持有它的引用),程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。

5.3.1 问题产生

  • 内部类(除静态内部类外)会天然持有外部类的引用;
  • MessageQueue的生命周期等于其对应线程的生命周期;
  • 发生内部类的生命周期大于外部类的情况;

       如果在activity中通过内部类的形式定义并创建handler的子类,则该子类会持有activity的引用;而此时handler在耗时操作的子线程中使用,或者是MessageQueue中有该handler发出的message,则handler只有在子线程的耗时操作或者是MessageQueue中message处理完之后才会被释放,相应持有的activity引用也才能被释放,若在此之前,activity被主动出栈,不需要使用activity了,但由于其引用无法释放,所以activity无法被回收。

5.3.2 问题解决

  • 针对内部类(除静态内部类外)会天然持有外部类的引用的情况,可以通过定义静态内部类的方式,使handler不再持有外部类的引用;若是handler中的handlemessage()或者callback以及子线程中有对activity的其他显式引用,则可以使用弱引用的方式(在垃圾回收时,弱引用不会阻止引用对象被回收,而强引用肯定不会被回收)。
private static class MyHandler extends Handler {
	private final WeakReference<SampleActivity> mActivity;

	public MyHandler(SampleActivity activity) {
		mActivity = new WeakReference<SampleActivity>(activity);
	}

    @Override
    public void handleMessage(Message msg) {
		SampleActivity activity = mActivity.get();
		if (activity != null) {
			 ...
		}
	}
}
  • 针对内部类的生命周期大于外部类的情况,可以在外部类的生命周期结束前主动结束内部类的生命周期,例如将消息队列中持有handler引用的消息清除。

参考:
https://www.runoob.com/w3cnote/android-tutorial-activity.html