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有如下四种状态:
- 运行状态:Activity位于栈顶,与用户进行交互,可见;
- 暂停状态:Activity不在栈顶,但是可见,如弹出框后面的Activity,内存极低时会被系统回收;
- 停止状态:Activity不在栈顶,不可见,如从一个Activity启动另一个Activity,可能被系统回收;
- 销毁状态:从栈顶移除,肯定被回收。
2.2 生存期
图1 生存周期
其中,
- 完整生存期:onCreate()-> … ->onDestory(),从创建到销毁;
- 可见生存期:onStart() -> … ->onStop(),从可见到不可见,再到onStart()状态需要经过onRestart(),如从一个Activity启动另一个Activity后按下返回键回到之前的Activity;
- 前台生存期:onResume()-> … ->onPause(),从栈顶可交互到非栈顶不可交互,注:onPause()被调用必须是启动了新的Activity,而onPause()又要满足可见条件,所以该Activity的样式应该是透明的或者是dialog的。
进程生命周期:
- 前台进程;2. 可见进程; 3.服务进程; 4. 后台进程; 5.空进程,内部不包含应用程序的任何组件。
2.3 保存临时数据
2.3.1 触发时机
- 点击home键回到主页或长按后选择运行其他程序;
- 按下电源键关闭屏幕;
- 启动新的Activity;
- 横竖屏切换;
- 多窗口模式进入与退出。
即,不是主动按下返回键或者主动调用finish()方法销毁activity,但系统仍可能会将activity销毁。系统“未经许可”销毁了activity,则它必须要提供一个机会让你保存数据(你可以保存也可以不保存)
2.3.2 调用函数
- 保存:protected void onSaveInstanceState(Bundle outState)被调用,可以通过outState.putInt()等方法携带数据;
- 读取: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 横竖屏切换问题
- 禁止屏幕横竖屏自动切换
横竖屏切换时会先销毁Activity,然后再重新创建。若要禁止屏幕横竖屏自动切换,可在AndroidManifest.xml中为Activity添加android:screenOrientation属性, 为应用程序固定一种屏幕展示方式,有下述值可选:
- unspecified:默认值,由系统来判断显示方向。判定的策略是和设备相关的,所以不同的设备会有不同的显示方向;
- landscape:横屏显示;
- portrait:竖屏显示;
- user:用户当前首选的方向;
- behind:和该Activity下面的那个Activity的方向一致(在Activity栈中);
- sensor:由物理感应器决定。如果用户旋转设备这屏幕会横竖屏切换;
- nosensor:忽略物理感应器,这样就不会随着用户旋转设备而更改了("unspecified"设置除外)。
- 横竖屏展示不同的布局
可创建两个布局文件夹,layout-portrait和layout-landscape。 - 判断当前是横屏还是竖屏
if(getResources().getConfiguration().orientation==Configuration.ORIENTATION_LANDSCAPE)
...
else if(getResources().getConfiguration().orientation==Configuration.ORIENTATION_PORTRAIT)
...
- 通过代码设置横竖屏
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
- 禁止横竖屏切换时重建activity
可在AndroidManifest.xml文件中,对Activity配置configChanges属性为orientation|keyboardHidden|screen-Size|screenLayout,使应用程序监控configChanges中配置的事件,并调用public void onConfigurationChanged(Configuration newConfig),在函数中若不进行额外处理,则进入多窗口模式或者进行横竖屏切换可不重新创建活动。
2.3.4 多窗口模式问题
- 进入多窗口模式
Android 7.0+会自动引入多窗口模式功能,而进入多窗口模式的方式有两种,一种是在打开app后长按overview,另一种是点击overview后将app窗口拖入多窗口模式。 - 生命周期
同横竖屏切换。而多窗口模式中的一个窗口与用户进行交互,而另一个窗口则处于暂停状态。 - 禁止重新创建活动
同横竖屏切换 - 禁止多窗口模式的方式
· 可在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中:
- public Intent(Context packageContext, Class<?> cls)
- public Intent setClass(Context packageContext, Class<?> cls)
- public Intent setClassName(Context packageContext, String className)
- public Intent setClassName(String packageName, String className)
- public Intent setComponent(ComponentName component)
其本质就是通过相关参数确定component,并将component传入intent。虽然上述方法接收的参数各有不同,但除了直接给定component外,其他都是通过第一个参数获取component所在包的packageName,通过第二个参数获取component的全类名。其中,packageName确定intent所指向的应用程序,component全类名则确定intent所指向的应用程序中的唯一component(应用包名下有新建包),这样就可以确定系统中的唯一component,注:启动其他应用程序的组件需要该组件支持外部程序访问。
3.2 隐式intent
intent没有明确的component,需要提供action、category、type信息并与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分别为:
- URI——统一资源标识符
URL是其中一种标识资源的具体方式。URI一般由三部分组成:scheme协议名、[host:port]服务器名/[authority]、path路径。 - 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 自定义对象传递
- JSON
将JAVA对象转化为JSON字符串进行传递,解析时再将JSON字符串转化为JAVA对象; - Serializable
实现方式:使javabean对象实现Seriablizable接口,整个对象都能传输。
发送:intent.putExtra(String tag, javabean)或者buddle.putSeriablizable()
接收:intent.getSeriablizableExtra(String tag, javabean)或者buddle.getSeriablizable() - 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定义全局变量
- 定义Application
继承Application类,并在AndroidManifest.xml文件中的<application>标签中设定android:name属性为该Application类。 - 获取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)
- 在下一个Activity中通过setResult(int resultCode,Intent intent)来设置返回结果,intent不需要指定component,setResult()必须在finish()方法之前调用,且最好主动调用finish()方法,因为设置的返回结果是在finish()方法中进行返回的。一般不要在onPause()、onStop()、onDestory()方法中调用setResult(),因为不能保证这些方法在执行之后会调用finish()方法。注:使用返回键,resultCode会被重置为RESULT_CANCEL。
- 在上一个Activity中的onAcitivityResult(int requestCode,int resultCode,Intent intent)中处理返回结果。其中,requestCode用于区分数据来源,resultCode用于判断处理结果,Intent用于携带数据。requestCode只要唯一就行,ResultCode一般为RESULT_OK或RESULT_CANCELED。
四、Activity管理机制
4.1 taskAffinity和allowTaskReparenting
- taskAffinity
- 对于Activity来说,每个Activity都有taskAffinity属性,该属性代表Activity是否处于同一个Task。如果Activity没有显式指明taskAffinity,那么该属性就等于Application的taskAffinity,如果 Application也没有指明,那么该属性值就等于包名。
- 对于Task来说,其affinity属性值等于它的根 Activity的taskAffinity的值。
- 无缝衔接
在一个应用程序中启动另一个应用程序的activity,不会重新建栈,而是将该activity继续添加进原来的栈中。 - 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 。
- alwaysRetainTaskState: 如果栈底Activity的这个属性被设置为true,则Task中的所有activity将被长时间保存。
- clearTaskOnLaunch:如果栈底activity的这个属性被设置为true,一旦该Task转移至后台, 则 Task栈中的Activity将被清空到只剩下栈底activity。即使只是短暂地切换至后台。
- finishOnTaskLaunch:只对单独的activity操作。当它设置为true时,当前的Activity所在栈转移至后台后,该activity将被清除,即使该activity是栈底的Activity。
4.3 launchMode
- Standard模式
默认,每次启动都会新建一个activity,无论该activity是否已经在栈顶。 - SingleTop模式
会检测调用方目标栈中栈顶是否有该activity,若有则不会重新创建,但会调用protected void onNewIntent(Intent intent)方法,可以刷新数据。若没有,则重新创建activity。总之,只涉及调用方所在的这一个栈。 - SingleTask模式
系统中只允许有包含该activity的一个task。
- 若是同一个应用程序,则只涉及一个栈的问题。activity不在栈顶,则将activity移至该activity所在栈的栈顶,触发onNewIntent()方法,并把之前在activity之上的所有activity出栈;若activity未进栈,则在该栈的栈顶创建该activity实例。
- 若启动的是其他应用程序的activity,则涉及调用方所在栈以及与activity的taskAffinity值相匹配的栈。如果该activity未进栈,则创建一个新的栈以及一个activity实例,并将该activity作为新栈的栈底activity;若不在栈顶,则将activity移至该栈的栈顶并调用onNewIntent()方法,之上的activity出栈。
- 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 机制组成
图2 异步消息处理机制
- Message:用于不同线程之间传递信息,what代表消息类型,arg0、arg1可以保存两个整型数据,object可以保存对象;
- MessageQueue:每个线程有一个MessageQueue,用于存放message;
- Looper:一个MessageQueue对应一个Looper,用来管理MessageQueue;
- Handler:在创建handler的线程上进行消息发送sendMessage()和消息处理handleMessage()。
5.2 处理过程
5.2.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中。
- 创建handler子类对象
handler中定义了空的handleMessage()方法,只能通过继承的方式重写handleMessage()方法;或者也可以调用带有callback接口参数的构造方法创造对象以及new Handler().post(Runnable)。构造方法会先从sThreadLocal中取出looper。 - 死循环读取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();
}
- 标准异步处理线程
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