美图欣赏
Java、Android知识点汇集
Java集合类
** Java集合相关的博客**
Java线程相关
1.线程
- Java并发编程:线程间协作的两种方式:wait、notify、notifyAll和Condition
- Java 多线程 并发编程
- JAVA多线程和并发基础面试问答
- Java线程面试题 Top 50
- Java并发编程:Timer和TimerTask
synchronized的作用域:
synchronized的用法有两种,同步方法和同步代码块。
一、同步方法。
1、非静态同步方法的锁是当前类对象
this
,即非静态的synchronized方法的作用域为对象本身。例如,一个对象有多个同步方法,只要有一个线程访问了其中的一个synchronized方法,其他线程就不能同时访问这个对象中的任何一个synchronized方法。但是不同对象实例之间的synchronized方法是互不干扰的。2、静态同步方法的锁是当前类的字节码文件,作用域为当前类,即作用于该类的所有对象实例。
二、同步代码块。对该代码块的资源实行互斥访问。
注:
0、同步方法和同步代码块的区别,同步代码块不会锁住整个对象(当然你也可以让它锁住整个对象)。同步方法会锁住整个对象,哪怕这个类中有多个不相关联的同步块,这通常会导致他们停止执行并需要等待获得这个对象上的锁。Java的synchronized的同步代码块和同步方法的区别
1、wait(),notify(),notifyAll()方法只能在synchronized中调用。
2、synchronized中调用wait()方法时立即阻塞让出CPU执行权,代码块中剩下的代码不会继续执行,只能等待唤醒继续执行。
3、synchronized中调用notify()或者notifyAll()后,synchronized中的剩余代码会继续执行。并且只有等待该同步方法或者同步代码块中剩余代码执行完成后,其他正在等待获取该对象锁的同步方法或者同步代码块才会继续执行。
4、死锁
public class Test {
public static Object object = new Object();
/**
* @param args
* @throws ClassNotFoundException
*/
public static void main(String[] args) throws ClassNotFoundException {
Thread1 thread1 = new Thread1();
Thread2 thread2 = new Thread2();
thread1.start();
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
thread2.start();
}
static class Thread1 extends Thread{
@Override
public void run() {
synchronized (object) {
try {
System.out.println("开始Thread1");
Thread.sleep(2000);
object.wait();
} catch (InterruptedException e) {
}
System.out.println("线程"+Thread.currentThread().getName()+"获取到了锁");
}
}
}
static class Thread2 extends Thread{
@Override
public void run() {
synchronized (object) {
object.notifyAll();
System.out.println("线程"+Thread.currentThread().getName()+"调用了object.notify()");
try {
sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("sleep end");
}
System.out.println("线程"+Thread.currentThread().getName()+"释放了锁");
}
}
}
控制台输出结果如下:
1 开始Thread1
2 线程Thread-1调用了object.notify()
3 sleep end
4 线程Thread-1释放了锁
5 线程Thread-0获取到了锁
需要注意的是1,2,3是先执行的而且是顺序执行。4,5执行顺序不确定。
2.ThreadLocal(线程本地存储)
3.线程池
对比new Thread()和线程池的优劣
new Thread()弊端:
1、new Thread()每次创建新的对象,性能差;
2、缺乏统一管理,资源开销大。无限制的创建可能会因为资源占用过高导致OOM或者死机;
3、功能单一,没有办法定时,定期执行任务等;
相比于new Thread()
1、重用线程,减少对象的创建,回收空闲的线程,提升性能;
2、可以有效控制最大并发数,提高系统资源利用率,同时避免过多的资源竞争,避免堵塞;
3、提供可定时,定期,单线程,并发控制等功能
4.volatile关键字
** volatile特性**
当一个共享变量被volatile修饰的时候,它能保证修改的值能够立即被更新到主存。
内存可见性:线程A对一个volatile修饰的共享变量的修改,对于其他线程来说是可见的,即线程每次获取volatile变量的值都是最新的。
volatile的使用条件
volatile相当于一个轻量级的synchronize。但是volatile必须满足两个条件:
1、对变量的写操作不能依赖于本身(即不能依赖于当前值)。如多线程下执行a++,这样是无法通过volatile保证结果的准确性的。
2、该变量没有包含在具有其他变量的不变式中
public class NumberRange {
private volatile int lower = 0;
private volatile int upper = 10;
public int getLower() { return lower; }
public int getUpper() { return upper; }
public void setLower(int value) {
if (value > upper)
throw new IllegalArgumentException(...);
lower = value;
}
public void setUpper(int value) {
if (value < lower)
throw new IllegalArgumentException(...);
upper = value;
}
}
上述代码中,上下界初始化分别为0和10,假设线程A和B在某一时刻同时执行了setLower(8)和setUpper(5),且都通过了不变式的检查,设置了一个无效范围(8, 5),所以在这种场景下,需要通过sychronize保证方法setLower和setUpper在每一时刻只有一个线程能够执行。
volatile的使用场景
1、状态标记
2、double check(双重检查)
private volatile static Singleton instance;
public static Singleton getInstance() {
if (instance == null) {
synchronized(Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
Java代理模式
组合与继承
泛型
Java反射
Java内存相关
注意点
java异常
所有的异常都继承自一个共同的父类Throwable,而Throwable有两个重要的子类:Exception(异常)和Error(错误)
** Error**(错误)
是程序无法处理的错误,表示运行应用程序中较严重问题。大多数错误与代码编写者执行的操作无关,而表示代码运行时 JVM(Java 虚拟机)出现的问题。例如,Java虚拟机运行错误(Virtual MachineError),当 JVM 不再有继续执行操作所需的内存资源时,将出现 OutOfMemoryError。这些异常发生时,Java虚拟机(JVM)一般会选择线程终止。
这些错误表示故障发生于虚拟机自身、或者发生在虚拟机试图执行应用时,如Java虚拟机运行错误(Virtual MachineError)、类定义错误(NoClassDefFoundError)等。这些错误是不可查的,因为它们在应用程序的控制和处理能力之 外,而且绝大多数是程序运行时不允许出现的状况。对于设计合理的应用程序来说,即使确实发生了错误,本质上也不应该试图去处理它所引起的异常状况。在 Java中,错误通过Error的子类描述。** Exception**(异常)
是程序本身可以处理的异常。主要包含RuntimeException等运行时异常和IOException,SQLException等非运行时异常。
** 运行时异常包括:**都是RuntimeException类及其子类异常,如NullPointerException(空指针异常)、IndexOutOfBoundsException(下标越界异常)等,这些异常是不检查异常,程序中可以选择捕获处理,也可以不处理。这些异常一般是由程序逻辑错误引起的,程序应该从逻辑角度尽可能避免这类异常的发生。
运行时异常的特点是Java编译器不会检查它,也就是说,当程序中可能出现这类异常,即使没有用try-catch语句捕获它,也没有用throws子句声明抛出它,也会编译通过。
** 非运行时异常**(编译异常)包括:RuntimeException以外的异常,类型上都属于Exception类及其子类。从程序语法角度讲是必须进行处理的异常,如果不处理,程序就不能编译通过。如IOException、SQLException等以及用户自定义的Exception异常,一般情况下不自定义检查异常。
从** 编译器是否要求强制处理**的角度分类,异常类别又可分为:
** 可查异常**
正确的程序在运行中,很容易出现的、情理可容的异常状况。可查异常虽然是异常状况,但在一定程度上它的发生是可以预计的,而且一旦发生这种异常状况,就必须采取某种方式进行处理。
除了RuntimeException及其子类以外,其他的Exception类及其子类都属于可查异常。这种异常的特点是Java编译器会检查它,也就是说,当程序中可能出现这类异常,要么用try-catch语句捕获它,要么用throws子句声明抛出它,否则编译不会通过。
** 不可查异常**
包括运行时异常(RuntimeException与其子类)和错误(Error)。
面试常见问题
1.描述Java 7 ARM(Automatic Resource Management,自动资源管理)特征和多个catch块的使用如果一个try块中有多个异常要被捕获,catch块中的代码会变丑陋的同时还要用多余的代码来记录异常。有鉴于此,Java 7的一个新特征是:一个catch子句中可以捕获多个异常。示例代码如下:
catch(IOException | SQLException | Exception ex){
logger.error(ex);
throw new MyException(ex.getMessage());
}
大多数情况下,当忘记关闭资源或因资源耗尽出现运行时异常时,我们只是用finally子句来关闭资源。这些异常很难调试,我们需要深入到资源使用的每一步来确定是否已关闭。因此,Java 7用try-with-resources进行了改进:在try子句中能创建一个资源对象,当程序的执行完try-catch之后,运行环境自动关闭资源。下面是这方面改进的示例代码:
try (MyResource mr = new MyResource()) {
System.out.println("MyResource created in try-with-resources");
} catch (Exception e) {
e.printStackTrace();
}
2.在Java中throw与throws关键字之间的区别?
throws用于在方法签名中声明此方法可能抛出的异常,而throw关键字则是中断程序的执行并移交异常对象到运行时进行处理。
3.** 被检查的异常和 不受检查的异常**有什么区别?
被检查的异常应该用try-catch块代码处理,或者在main方法中用throws关键字让JRE了解程序可能抛出哪些异常。不受检查的异常在程序中不要求被处理或用throws语句告知。
Exception是所有被检查异常的基类,然而,RuntimeException是所有不受检查异常的基类。
被检查的异常适用于那些不是因程序引起的错误情况,比如:读取文件时文件不存在引发的FileNotFoundException。然而,不被检查的异常通常都是由于糟糕的编程引起的,比如:在对象引用时没有确保对象非空而引起的NullPointerException。
4.Java中** final, finally, finalize**的区别?
** final和 finally在Java中是关键字,而 finalize则是一个方法。
** final关键字使得类变量不可变,避免类被其它类继承或方法被重写。
** finally跟 try-catch块一起使用,即使是出现了异常,其子句总会被执行,通常, finally子句用来关闭相关资源。
** finalize方法中的对象被销毁之前会被垃圾回收。
异常相关的博客
JVM模型
Java常用算法
Android基本架构
Mac OS 下Android环境搭建
四大组件之 Activity
1. Activity生命周期
正常情况下Activity会经历如下生命周期:
onCreate(),表示Activity的创建。在这个方法中我们可以做一些初始化的工作。比如调用setContentView去加载layout布局资源,初始化Activity所需的数据等。
onRestart(),重新启动Activity。一般情况下,当当前的Activity从不可见变为可见的时候,该方法会被调用。这中情况一般是用户按Home健回到桌面或者启动了一个新的Activity,这个时候onPause和onStop方法就会被调用。上面的操作完成后用户如果点击桌面应用的图标或者按back键回到之前的Activity,这时候onRestart就会被调用。
onStart(),表示开始启动Activity。这个时候Activity就变为可见了,但是前台还是看不到的,还没有办法和用户进行交互。只有当onResume方法被调用的时候,才会真正的出现在前台。
onResume(),表示Activity已经变得可见,可以和用户进行交互了。需要注意的是,虽然onStart和onResume虽然都表示Activity变得可见。但是onStart的时候,Activity还在后台,onResume的时候才真正的出现在前台。
onPause(),此时的Activity正准备停止,正常情况下,接着就会调用onStop方法。特殊情况下,如果这个时候用户又回到这个Activity,那么onResume就会被调用。这是一种非常极端的情况,很难重现。onPause中不要做太多耗时的操作,因为会影响新的Activity的展现,onPause必须先执行完成,新的Activity的onResume方法才会被执行。
onStop(),表示Activity即将停止。可以做一些稍微重量级的回收工作。同样不能做太多耗时的操作。
onDestroy(),这是Activity生命周期中最后一个回调,表示Activity即将被销毁。可以做一些回收工作,释放资源等等。
Activity启动模式
Android中Activity的启动模式有四种,分别是standard、singleTop、singleTask和singleInstance。
1、standard:标准模式。这也是Activity默认的启动模式。每次启动一个Activity都会重新创建新的Activity实例。不管这个Activity实例在任务栈中是否已经存在。被创建的Activity实例的生命周期符合典型情况下的Activity生命周期。
事例说明:此时有两个Activity,SecondActivity和ThirdActivity。当我们当我们从SecondActivity跳转到ThirdActivity再从ThirdActivity跳转到SecondActivity。我们可以发现SecondActivity被创建了两次。这就说明标准模式下的Activity不会复用,每次启动都会创建新的Activity实例。
2、singleTop:栈顶复用模式。这种模式下,如果要被启动的Activity实例已经位于任务栈的栈顶,则不再创建新的Activity实例。同时它的onNewIntent方法会被调用。通过这个方法我们可以取出当前请求的信息。需要注意的是,这个Activity的onCreate。onStart不会被调用。因为它并没有发生改变。如果新的Activity不是位于栈顶,那么新的Activity任然会被创建。例如现在任务栈中有ABCD,ABCD启动模式均为singleTop模式,A位于栈底,D位于栈顶,如果此时要启动C,那么任务栈的情况就会变成ABCDC。
3、singleTask:这是站内复用模式。这种模式下,只要Activity实例在任务栈中存在,启动的时候就不会去再次创建新的Activity实例,而是沿用已经存在的Activity实例。不论启动多少次,任务栈都只会存在一个Activity实例。这就是站内复用模式。和singleTop一样,系统启动Activity的时候,如果任务栈中存在该Activity实例,就不会调用onCreate和onStart方法,而是会调用onNewIntent方法。
singleTask站内复用模式的几种情况:
1)如果目前任务栈T1中存在ABC三个activity实例,这个时候Activity D以singleTask模式请求启动,其所需要的任务栈未T2.由于T2和D的实例都不存在,所以系统会首先创建T2,然后将D压入到T2中。
2)第二种情况,如果目前任务栈T1中存在ABCD三个activity实例,这个时候Activity D以singleTask模式请求启动,由于D已经存在T1的任务栈,所以D就不会被再次创建,而是直接沿用已经存在的D的实例。
3)第三种情况,如果目前任务栈T1中存在ADBC三个activity实例,这个时候Activity D以singleTask模式请求启动,由于D已经在T1中已经存在,同样不会再次创建,也是沿用已经存在的D的实例。同时会将位于D上面的其他Activity实例移出栈,使自己位于栈顶位置。
4、singleInstance:单实例模式。这种模式可以看成是singleTask的加强模式,它除了具有singleTask的模式所具有的一些列特性之外,还增加了一点特殊的特性。具有这种模式的activity只能单独的运行在独立的任务栈中。什么叫运行在独立的任务栈,其实就是如果Activity A是singleInstance模式,当A启动后,系统会为A创建一个新的任务栈使其独立运行在其中,这个栈中只有A自己不存在其他Activity实例。由于它也具有singleTask的复用的特性,在后续启动的时候,也会复用这个Activity。
Fragment
四大组件之 Service
四大组件之 Broadcast Receiver
- BroadcastReceiver(含安全性问题)
- Android开发之Intent和BroadcastReceiver
- BroadcastReceiver应用详解
- Android应用界面开发——BroadcastReceiver(实现基于Service的音乐播放器)
四大组件之 ContentProvider
Notification
SharedPreferences
SharedPreferences类是一个接口类,真正的实现类是SharedPreferencesImpl。修改SharedPreferences需要获取它的Editor,在对Editor进行put操作后,最后通过commit或者apply提交修改到内存和文件。当然有了两种都可以提交的方法,肯定要区别一下的。从实现类SharedPreferencesImpl的源码上看也很容易看出两者的区别:
commit这种方式很常用,在比较早的SDK版本中就有了,这种提交修改的方式是同步的,会阻塞调用它的线程,并且这个方法会返回boolean值告知保存是否成功(如果不成功,可以做一些补救措施)。
而apply是异步的提交方式,目前Android Studio也会提示大家使用这种方式。
还有一点用得比较少的,就是SharedPreferences还提供一个监听接口可以监听SharedPreferences的键值变化,需要监控键值变化的可以用registerOnSharedPreferenceChangeListener添加监听器。
public interface SharedPreferences {
/**
* Interface definition for a callback to be invoked when a shared
* preference is changed.
*/
public interface OnSharedPreferenceChangeListener {
void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key);
}
}
多进程操作和读取SharedPreferences的问题
在SDK 3.0及以上版本,可以通过Context.MODE_MULTI_PROCESS属性来实现SharedPreferences多进程共享。如下设置:
public static SharedPreferences getSharedPreferences(String name) {
if (null != context) {
if (Build.VERSION.SDK_INT >= 11) {
return context.getSharedPreferences(name, Context.MODE_MULTI_PROCESS);
} else {
return context.getSharedPreferences(name, Context.MODE_PRIVATE); }
}
return null;
}
MODE_MULTI_PROCESS属性使用SharedPreferences也不能完全保证进程间的共享数据不会出问题,真正使用中发现有会有一定概率出现这个取值出错(变为初始值)问题。
Google也在SDK 6.0的版本将这个MODE_MULTI_PROCESS标识为deprecated(不赞成使用)。目前来说,越来越多的项目在不断的膨胀,为了降低单个进程的内存占用率,使用"android:process"配置一些组件在单独的进程中运行已经是司空见惯了,所以大家在遇到自己的项目有多进程时,要注意一下SharedPreferences的问题。
注意: 在一个进程中,SharedPreference往往建单个实例就可以了,一般不会出现并发冲突,如果对提交的结果不关心的话,建议使用apply,当然需要确保提交成功且有后续操作的话,还是需要用commit的。关于SharedPreferences多进程数据共享问题,可以借鉴开源�替代方案如Github上的tray。
Android消息处理机制(Looper、Handler、MessageQueue、Message)
** 概述**:Android应用程序是通过消息来驱动的,Android某种意义上也可以说成是一个以消息驱动的系统,UI、事件、生命周期都和消息处理机制息息相关,并且消息处理机制在整个Android知识体系中也是尤其重要。
Handler的运行机制描述:谈到Handler的运行机制一般情况下,都会涉及到几个比较重要的类,Looper,MessageQueue,Message等等。Handler的实例必须在Looper线程中创建,否则就会抛出RuntimeException异常(
Can't create handler inside thread that has not called Looper.prepare()
)提示handler实例的创建必须在looper线程中进行。handler实例的创建一般都是在UI线程,因为,一般情况下我们使用handler的目的是为了执行完后台任务后,和UI线程进行交互。由于UI线程在创建之初,就被设置成了Looper线程(这个可以在ActivityThread源码中看到,里面有一个main方法,在实例化ActivityThread的之前,调用了Looper.prepareMainLooper()
),所以我们在实例化Handler的时候不需要手动再次调用Looper.prepare()
方法。Looper线程中会维护一个MessageQueue消息队列。handler通过sendMessage()方法向Looper中的消息队列插入一条Message消息。MessageQueue通过enqueueMessage方法将handler发送来的message消息放到消息队列中。由于Looper线程是一个循环进程,里面有一个阻塞方法loop()
。在该方法中looper会调用MessageQueue的next()
不停的循环遍历MessageQueue中的消息(可以在Looper源码的loop()方法中看到)。当Looper发现有新的message来的时候,就会回调给Handler中handMessage方法进行处理。
备注:handler在主线程中实例化后就会拿到主线程的MessageQueue的引用(Looper中维护的MessageQueue其实就是主线程中MessageQueue)。handler在sendMessage的时候,发送的Message里面持有Handler的引用。这样在整个消息流程中就就把Looper,MessageQueue,Message,Handler串联连起来了,不会出现错乱。
消息处理机制的本质:** 一个线程开启循环模式持续监听并依次处理各个线程发来的消息**
简单的说:一个线程开启一个无限循环模式,不断遍历自己的消息列表,如果有消息就挨个拿出来做处理,如果列表没消息,自己就堵塞(相当于wait,让出cpu资源给其他线程),其他线程如果想让该线程做什么事,就往该线程的消息队列插入消息,该线程会不断从队列里拿出消息做处理。
** 消息处理机制的描述:**
线程其实就是一段可执行代码,当这段代码执行完成后,该线程的生命周期就结束了,线程就会退出。既然如此,UI线程在执行完成后为什么没有退出呢?因为UI线程在创建的时候就被变成了Looper线程(这个可以在ActivityThread的main方法中看到,在ActivityThread创建之前调用了Looper.prepareMainLooper();
ActivityThread创建完成后接着就调用了Looper.loop();
),Looper里面有一个loop()
方法,这个方法里面有一段死循环的代码。
主线程会一直处于这个死循环中,由于Looper里面维护一个MessageQueue消息队列,这个消息队列就用来维护其他线程发送来的Message消息(例如,Activity的启动,生命周期,UI的更新,控件的事件等等),UI线程会根据消息队列中的消息(例如,Activity的启动,生命周期,UI的更新,控件的事件等等),依次做出处理。
那么其他线程是如何发送Message到UI线程的MessageQueue消息队列中的?首先肯定是要拿到MessageQueue的实例,Google为了统一添加消息和消息的回调处理,又专门构建了Handler类,你只要在主线程构建Handler类,那么这个Handler实例就获取主线程MessageQueue实例的引用(获取方式mLooper = Looper.myLooper();mQueue = mLooper.getQueue();)
,Handler 在sendMessage的时候就通过这个引用往消息队列里插入新消息。Handler 的另外一个作用,就是能统一处理消息的回调。这样一个Handler发出消息又确保消息处理也是自己来做
** Looper、Handler、MessageQueue、Message作用和存在的意义?**
** Looper** 循环线程,在主线程中调用Looper.prepare()...Looper.loop()就会变当前线程变成Looper线程(可以先简单理解:无限循环不退出的线程)
** Handler**简单说Handler用于同一个进程的线程间通信。Google 为了统一添加消息和消息的回调处理,又专门构建了Handler类,你只要在主线程构建Handler类,那么这个Handler实例就获取主线程MessageQueue实例的引用(获取方式mLooper = Looper.myLooper();mQueue = mLooper.mQueue;),Handler 在sendMessage的时候就通过这个引用往消息队列里插入新消息。Handler 的另外一个作用,就是能统一处理消息的回调。这样一个Handler发出消息又确保消息处理也是自己来做,这样的设计非常的赞。具体做法就是在队列里面的Message持有Handler的引用(哪个handler 把它放到队列里,message就持有了这个handler的引用),然后等到主线程轮询到这个message的时候,就来回调我们经常重写的Handler的handleMessage(Message msg)方法。
- ** MessageQueue**MessageQueue 存在的原因很简单,就是同一线程在同一时间只能处理一个消息,同一线程代码执行是不具有并发性,所以需要队列来保存消息和安排每个消息的处理顺序。多个其他线程往UI线程发送消息,UI线程必须把这些消息保持到一个列表(它同一时间不能处理那么多任务),然后挨个拿出来处理,这种设计很简单,我们平时写代码其实也经常这么做。每一个Looper线程都会维护这样一个队列,而且仅此一个,这个队列的消息只能由该线程处理。
- ** Message** UI线程和子线程通信的载体,主要存储和传递一些子线程想让UI线程处理的内容
** 关于Looper中loop()
方法的疑问**
1.UI线程一直在这个循环里跳不出来,主线程不会因为Looper.loop()里的死循环卡死吗,那还怎么执行其他的操作呢?
在looper启动后,主线程上执行的任何代码都是被looper从消息队列里取出来执行的。也就是说主线程之后都是通过其他线程给它发消息来实现执行其他操作的。生命周期的回调也是如此的,系统服务ActivityManagerService通过Binder发送IPC调用给APP进程,App进程接到到调用后,通过App进程的Binder线程给主线程的消息队列插入一条消息来实现的。
2.主线程是UI线程和用户交互的线程,优先级应该很高,主线程的死循环一直运行是不是会特别消耗CPU资源吗?App进程的其他线程怎么办?
这基本是一个类似生产者消费者的模型,简单说如果在主线程的MessageQueue没有消息时,就会阻塞在loop的queue.next()方法里,这时候主线程会释放CPU资源进入休眠状态,直到有下个消息进来时候就会唤醒主线程,在2.2 版本以前,这套机制是用我们熟悉的线程的wait和notify 来实现的,之后的版本涉及到Linux pipe/epoll机制,通过往pipe管道写端写入数据来唤醒主线程工作。原理类似于I/O,读写是堵塞的,不占用CPU资源。
** 关于Handler实例的创建**
- Handler实例的创建,是需要在创建之前调用Looper.prepare()将当前线程变成Looper线程的,同时调用Looper.loop()。那为什么我们平时在UI线程中创建Handler实例的时候为什么没有调用Looper.prepare()和Looper.loop(),那时因为UI线程在创建的时候就变成了Looper线程,所以不再需要调用Looper.prepare()和Looper.loop()。如果再调用这两个方法的话就会抛出
Only one Looper may be created per thread
异常。就是告诉你Looper实例已经存在了,并且只能有一个。
我们在Activity中创建的Handler实例IDE常常提示出一些警告,这些警告一半都是提示这种定义方式可能出现内存泄露。那么如何正确创建Handler实例?�可以采用static方式,定义一个内部类继承Handler,利用软引用WeakReference,接收外部类的引用。具体可以参照下面这两篇文章
** Android消息处理相关机制相关的博文**
- Android的Handler消息机制
- Android 消息机制分析
- Android 消息处理机制(Looper、Handler、MessageQueue,Message)
- Android Handler原理分析
- Handler 的使用与原理
AsyncTask
1.AsyncTask机制
AnsycTask执行任务时,内部会创建一个进程作用域的线程池来管理要运行的任务,也就就是说当你调用了AsyncTask.execute()后,AsyncTask会把任务交给线程池,由线程池来管理创建Thread和运行Therad。最后和UI打交道就交给Handler去处理了。
这是从API 23的AsyncTask源码中截取的一部分源码:
从上面的代码中我们可以看出,AsyncTask中的线程池,核心线程数(
CORE_POOL_SIZE
)由可获得的CPU内核数决定,最大线程数量(MAXIMUM_POOL_SIZE
)是可获得CPU内核数的两倍加1(CPU_COUNT*2+1
)。keepAliveTime是30秒,工作队列是128。
- corePoolSize: 核心线程数目,即使线程池没有任务,核心线程也不会终止(除非设置了allowCoreThreadTimeOut参数)可以理解为“常驻线程”
- maximumPoolSize: 线程池中允许的最大线程数目;一般来说,线程越多,线程调度开销越大;因此一般都有这个限制。
- keepAliveTime: 当线程池中的线程数目比核心线程多的时候,如果超过这个keepAliveTime的时间,多余的线程会被回收;这些与核心线程相对的线程通常被称为缓存线程
- unit: keepAliveTime的时间单位
- workQueue: 任务执行前保存任务的队列;这个队列仅保存由execute提交的Runnable任务
- threadFactory: 用来构造线程池的工厂;一般都是使用默认的;
- handler: 当线程池由于线程数目和队列限制而导致后续任务阻塞的时候,线程池的处理方式。
2.多个AsyncTask任务是串行还是并行?
从Android 1.6到2.3(Gingerbread) AsyncTask是并行的,即上面我们提到的有5个核心线程的线程池(ThreadPoolExecutor)负责调度任务。从Android 3.0开始,Android团队又把AsyncTask改成了串行,默认的Executor被指定为SERIAL_EXECUTOR。
3.AsyncTask容易引发的Activity内存泄露
如果AsyncTask被声明为Activity的非静态的内部类,那么AsyncTask会保留一个对创建了AsyncTask的Activity的引用。如果Activity已经被销毁,AsyncTask的后台线程还在执行,它将继续在内存里保留这个引用,导致Activity无法被回收,引起内存泄露。
4.博文推荐
总结
AsyncTask的运行机制
AsyncTask分为两个部分,一部分和主线程交互,一部分负责线程调度。虽然可能会存在多个AsyncTask的子类实例,但是其内部的Handler和ThreadPoolExecutor都是静态的,是进程范围内共享的。所以AsyncTask控制着进程范围内所有其子实例。
与主线程交互 AsyncTask和主线程交互是通过Handler来完成的;
�线程调度 关于AsyncTask内部的线程调度,其内部会创建一个进程作用域内的线程池;也就是说当你调用了AsyncTask的execute(...)方法后,AsyncTask就会把任务交给线程池,由线程池来管理创建和运行Thread。对于内部线程池,不同版本的Android内部实现方式是不一样的。
在Android2.3(API 10)
之前,线程池限制数为5个,因为Android2.3之前的AsyncTask是并行的,所以同时只能有5个线程在运行,超过的只能等待,等待前面5个完成才能继续执行。
这种情况在Android3.0(API 11)
之后的版本得到了改善,Google工程师把AsyncTask的execute(...)方法由之前的并行方式,改成了串行方式,按先后顺序每次只允许一个线程执行。除此之外,还添加了一个executeOnExecutor(...)方法,这个方法是并行执行方法,同时也允许开发者提供自定义的线程池来运行和调度Thread,如果你想让所有的任务都能并发同时运行,那就创建一个没有限制的线程池(Executors.newCachedThreadPool()),并提供给AsyncTask。这样这个AsyncTask实例就有了自己的线程池而不必使用AsyncTask默认的。当然AsyncTask中还有两个预设的线程池SERIAL_EXECUTOR和THREAD_POOL_EXECUTOR。SERIAL_EXECUTOR表示串行执行;THREAD_POOL_EXECUTOR表示并行执行。SERIAL_EXECUTOR作用是保证任务执行的顺序,也就是它可以保证提交的任务确实是按照先后顺序执行的。它的内部有一个队列用来保存所提交的任务,保证当前只运行一个,这样就可以保证任务是完全按照顺序执行的,默认的execute()使用的就是这个,也就是executeOnExecutor(AsyncTask.SERIAL_EXECUTOR)与execute()是一样的。�对于THREAD_POOL_EXECUTOR,可以通过调用executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR),这样起码不用等到前面的都结束了再执行。
AsyncTask带来的问题
1.生命周期,AsyncTask不会�随着Activity的�销毁而销毁。正常情况下Activity销毁了,此时如果有AsyncTask在执行,AsyncTask并不会随着Activity的�销毁而销毁。而是会继续执行,如果我们在onPostExecute()
方法中做了更新UI的操作,就有可能出现crash现象。解决方法就是在Activity销毁的时候,检测当前是否存在正在运行的AsyncTask(通过isCancelled()
),如果有的话,就调用AsyncTask的cancel(true)
方法,取消任务,注意不要再AsyncTask的doBackground()
方法中执行不可中断的操作(如BitmapFactory.decodeStream()),否则无法立即cancel掉。除此,还可以在onPostExecute()
方法中,在执行前判断当前Activity是否已存活,如果是活着的,就继续执行,反之则return取消掉。
2.内存泄露,如果AsyncTask被声明为Activity的非静态的内部类,那么AsyncTask会保留一个对创建了AsyncTask的Activity的引用。如果Activity已经被销毁,AsyncTask的后台线程还在执行,它将继续在内存里保留这个引用,导致Activity无法被回收,引起内存泄露。解决办法,就是在Activity销毁的时候,检查是否有还在运行的AsyncTask,如果有就cancel掉。
3.结果丢失,屏幕旋转或Activity在后台被系统杀掉等情况会导致Activity的重新创建,之前运行的AsyncTask会持有一个之前Activity的引用,这个引用已经无效,这时调用onPostExecute()再去更新界面将不再生效。解决方法和上面一样,也是在销毁之前cancel掉