Java基础技术核心归纳(四)
转载请声明出处:http://blog.csdn.net/andrexpert/article/details/77931466
Android Java 数据结构
Android基础技术核心归纳(一) Java基础技术核心归纳(一) 数据结构基础知识核心归纳(一)
Android基础技术核心归纳(二) Java基础技术核心归纳(二) 数据结构基础知识核心归纳(二)
Android基础技术核心归纳(三) Java基础技术核心归纳(三) 数据结构基础知识核心归纳(三)
Android基础技术核心归纳(四) Java基础技术核心归纳(四)
1.Java命名规则
(1)变量:以$、字母、下划线开头,后面以数字、字母、下划线。标识符不能是关键字和保留字,字符长度没有限制。(2)常量:即static final,常量所有字母要大写;
(3)方法:方法命名变量类似,如count(),getSum();
(4)类:类名的所有单词首字母均大写. 如Person{} , DataCenter{};
(5)包名:用小写的倒置域名来命名. 格式: 前缀 + 项目名 + 模块名 + 层如: org.itfuture.domain.sorts
2.null关键字
(1)null是代表不确定的对象。Java中,null是一个关键字,用来标识一个不确定的对象。因此可以将null赋给引用类型变量,但不可以将null赋给基本类型变量。
典型实例:
public class HelloWord {说明:编译通过,运行时出现"java.lang.NullPointerException"异常。如果将say()方法修改为static静态方法时,执行" ((HelloWord)null).say(); "输出1。
public void say(){
System.out.println("1");
}
public static void main(String[] args) {
((HelloWord)null).say();
}
}
(2)null本身不是对象,也不是Objcet的实例。
(3)在定义变量的时候,如果定义后没有给变量赋值,则Java在运行时会自动给变量赋值。赋值原则是整数类型int、byte、short、long的自动赋值为0,带小数点的float、double自动赋值为0.0,boolean的自动赋值为false,其他各供引用类型变量自动赋值为null(String s默认值为null)。
典型实例:
public class HelloWord {说明:由于局部变量String s没有初始化,代码不能编译通过。如果将String s改为静态成员变量,那么当类加载时会自动给s赋初值null,输出"s=null"。
public static void main(String[] args) {
String s;
System.out.println("s="+s);
}
}
(4)容器类型与null。
List:允许重复元素,可以加入任意多个null。
Set:不允许重复元素,最多可以加入一个null。
Map:Map的key最多可以加入一个null,value字段没有限制。
数组:基本类型数组,定义后,如果不给定初始值,则java运行时会自动给定值。引用类型数
组,不给定初始值,则所有的元素值为null。
(5)释放内存。让一个非null的引用类型变量指向null。这样这个对象就不再被任何对象应用了。等待JVM垃圾回收机制去回收
3.Java构造方法被调用情况
当一个子类继承于父类时,实例化子类,首先会执行父类默认无参数构造方法,然后再执行子类用于实例化对象的构造方法。
class People{4.Java的实例化对象过程
String name;
public People() { //第一个执行
System.out.println(1);
}
public People(String name) { //第三个执行
System.out.println(2);
this.name = name;
}
}
public class HelloWord extends People{
People father;
public HelloWord(String name) { //第二个执行
System.out.println(3);
this.name = name;
father = new People(name+";F");
}
public HelloWord() {
System.out.println(4);
}
public static void main(String[] args) {
new HelloWord("milk");
}
}
学习JAVA这门面向对象的语言,实质就是不断地创建类,并把类实例化为对象并调用方法。对于初学JAVA的人总搞清楚对象是如何实例化的,假如类之间存在继承关系,那就更糊涂了。下面我们通过两个例题来说明对象的实例化过程。
编译并运行该程序会有以下输出 Static Block Employee Company:china soft Non-Static Block Employee phone:0755-51595599 Employee(String) Empoloyee() 下面我们来对结果做分析: 1 在代码34行实例化对象时, 先对给(1)静态变量分配内存空间并初始化,然后执行静态块。 因此,在这里会输出: Static Block Employee Company:china soft Employee Company:china soft的输出也说明是先初始化静态变量然后再执行静态块,否则company将为null。 (2)然后在内存中为Employee分配内存空间,并做默认初始化(即所有变量初始化为默认值,这里都初始化为null)。 3 默认初始化完成后,开始显示初始化。即执行第5行,将phone初始化"0755-51595599",并且执行非静态方法块;因此在这里会有以下输出: Non-Static Block Employee phone:0755-51595599 4 最后才调用默认构造函数,在默认构造函数中调用了带参数的构造函数,所以在这里先输出带参数构造函数中的:Employee(String),然后才输出:Empoloyee()。
例2:
上面的代码中Manager继承了前面写的Employee类,当我们编译并运行Manager类,会产生以下的输出:
Static Block Employee Company:china soft Sub Static Block Manager department:sale //静态代码块
Non-Static Block Employee phone:0755-51595599 Employee(String) Empoloyee() //父类:代码块、无参构造方法
Sub Non-Static Block Manager salary:8000 Manager(String) Manager() //子类:代码块、构造方法
下面我们对结果做分析: 1 在行34实例化对象时,由于Manager继承了Employee,所以先从父类Employee开始;(1)先给父类静态变量分配内存空间并初始化,然后执行父类静态块。(2)然后再给子类静态变量分配内存空间并初始化,然后执行子类静态块,所以会得到以下输出: Static Block Employee Company:china soft Sub Static Block Manager department:sale
(3)然后在内存中为父类Employee分配内存空间,并做默认初始化;再(4)为子类Manager分配内存空间,并做默认初始化。 3默认初始化完成后,从父类开始显示初始化并执行非静态方法块和构造函数,然后再子类开始显示初始化并执行非静态方法块和构造函数。因此会产生以下的输出: Non-Static Block Employee phone:0755-51595599 Employee(String) Empoloyee() Sub Non-Static Block Manager salary:8000 Manager(String) Manager()
总结以上内容,可以得到对象初始化过程:
1、如果存在继承关系,就先父类后子类;
2 、如果在类内有静态变量和静态代码块,就先静态后非静态代码块,最后才是构造函数;
3 、继承关系中,必须要父类初始化完成后,才初始化子类。
5.经典String str = new String("abc")内存分配问题
(1)String的特性
◆String类是final的,不可被继承。
◆String类是的本质是字符数组char[], 并且其值不可改变。
◆String类对象有个特殊的创建的方式,就是直接指定比如String x = "abc","abc"就表示一个字符串对象。而x是"abc"对象的地址,也叫做"abc"对象的引用。
◆String对象可以通过“+”串联。串联后会生成新的字符串。
◆Java运行时会维护一个String Pool(String池),即字符串常量池。String池用来存放运行时中产生的各种字符串,并且池中的字符串的内容不重复。而一般对象不存在这个缓冲池,并且创建的对象仅仅存在于方法的堆栈区。
◆创建字符串的方式很多,归纳起来有三类:
其一,使用new关键字创建字符串,比如String s1 = new String("abc");
其二,直接指定。比如String s2 = "abc";
其三,使用串联生成新的字符串。比如String s3 = "ab" + "c";
(2)String对象的创建
总体原则:无论使用哪种方式创建一个字符串对象s,运行中的JVM会检查字符串常量池中是否存在内容相同的字符串对象,不存在,则在池中创建一个字符串对象s,即重新分配内存,否则,不在池中添加。
1. String str1 = "abc";
System.out.println(str1 == "abc");
步骤:
1) 栈中开辟一块空间存放引用str1;
2) String池中开辟一块空间,存放String常量"abc";
3) 引用str1指向池中String常量"abc";
4) str1所指代的地址即常量"abc"所在地址,输出为true;
2. String str2 = new String("abc");
System.out.println(str2 == "abc");
步骤:
1) 栈中开辟一块空间存放引用str2;
2) 堆中开辟一块空间存放一个新建的String对象"abc";
3) 引用str2指向堆中的新建的String对象"abc";
4) str2所指代的对象地址为堆中地址,而常量"abc"地址在池中,输出为false;
3. String str3 = new String("abc");
System.out.println(str3 == str2);
步骤:
1) 栈中开辟一块空间存放引用str3;
2) 堆中开辟一块新空间存放另外一个(不同于str2所指)新建的String对象;
3) 引用str3指向另外新建的那个String对象 ;
4) str3和str2指向堆中不同的String对象,地址也不相同,输出为false;
4. String str4 = "a" + "b";
//等价于final String s1 = “a”;
// final String s2 = "b";
// String str4 = s1+s2;
System.out.println(str4 == "ab");
步骤:
1) 栈中开辟一块空间存放引用str4;
2) 根据编译器合并已知量的优化功能,池中开辟一块空间,存放合并后的String常量"ab";
3) 引用str4指向池中常量"ab";
4) str4所指即池中常量"ab",输出为true;
5. final String s = "a"; //注意:这里s用final修饰,相当于一个常量
String str5 = s + "b";
System.out.println(str5 == "ab");
步骤:
同四
6.
String s1 = "a";步骤:
String s2 = "b";
String str6 = s1 + s2;
System.out.println(str6 == "ab");
1) 栈中开辟一块中间存放引用s1,s1指向池中String常量"a",
2) 栈中开辟一块中间存放引用s2,s2指向池中String常量"b",
3) 栈中开辟一块中间存放引用str6,
4) s1 + s2通过StringBuilder的最后一步toString()方法还原一个新的String对象"ab",因此堆中开辟一块空间存放此对象,
5) 引用str6指向堆中(s1 + s2)所还原的新String对象,
6) str6指向的对象在堆中,而常量"ab"在池中,输出为false
7. String str7 = "abc".substring(0, 2);
步骤:
1) 栈中开辟一块空间存放引用str7,
2) substring()方法还原一个新的String对象"ab"(不同于str6所指),堆中开辟一块空间存放此对象,
3) 引用str7指向堆中的新String对象,
8. String str8 = "abc".toUpperCase();
步骤:
1) 栈中开辟一块空间存放引用str6,
2) toUpperCase()方法还原一个新的String对象"ABC",池中并未开辟新的空间存放String常量"ABC",
3) 引用str8指向堆中的新String对象
9.String s="abc";
String s1=s;
System.out.println(s1=="abc");
s=s+"hello";
System.out.println(s1=="abc");
System.out.println(s=="abc");
步骤:
1)栈中开辟一块空间存放s;
2)Sting池中开辟一块空间用于存放"abc",栈中开辟一块空间存放变量s1;
3)系统输出true,在堆中开辟一块空间用于存放"abchello";
4)引用s指向堆中的"abchello";
5)系统输出true,然后输出false;
典型实例:
6.堆与栈的区别
Java把内存划分成两种:一种是栈内存,一种是堆内存。 栈与堆都是Java用来在Ram中存放数据的地方。与C++不同,Java自动管理栈和堆,程序员不能直接地设置栈或堆。
(1) Java的堆:
◆分配:是一个运行时数据区,堆内存用来存放由new创建的对象和数组。
◆释放:堆内存不需要程序代码来显式的释放,堆是由垃圾回收来负责的。
◆优点:是可以动态地分配内存大小,生存期也不必事先告诉编译器,因为它是在运行时动态分配内存的,Java的垃圾收集器会自动收走这些不再使用的数据。
◆缺点:由于要在运行时动态分配内存,存取速度较慢。
(2)Java的栈
◆分配:是一个运行时数据区,在函数中定义的一些基本类型(8种)的变量和对象的引用变量都在函数的栈内存中分配。当在一段代码块定义一个变量时,Java就在栈中为这个变量分配内存空间。
◆释放:当超过变量的作用域后,Java会自动释放掉为该变量所分配的内存空间,该内存空间可以立即被另作他用。
◆优点:无需动态分配,存取速度比堆要快,仅次于寄存器,栈数据可以共享。
◆缺点:存在栈中的数据大小与生存期必须是确定的,缺乏灵活性。
参考:http://blog.csdn.net/ycwload/article/details/2650059
http://www.nowcoder.com/questionTerminal/4148c53d7e284f19b0f61bc0ada248a8
7.Handler实现原理
Android的消息传递机制是另一种形式的"事件处理",由于Android应用规定只允许UI线程修改Activity里的UI组件,而这种机制主要是为了解决Android应用的多线程并发操作UI导致安全问题。Andriod提供了Handler和Looper来满足线程间的通信.Handler先进先出原则.Looper类用来管理特定线程内对象之间的消息交换(Message Exchange).
一、Handler工作原理
1.Handler消UI线程中息传递机制类介绍
(1)Handler:使用Handler对象来与Looper沟通,即它把消息发送给给Looper管理的MessageQueue,并负责处理Looper分给它的消息。
(2)Message:Handler接收和处理的消息对象;
(3)Looper:一个线程可以产生一个Looper对象,由它来管理此线程里的MessageQueue(消息队列)。Looper会调用prepare()和loop()方法,在当前执行的线程中保存一个Looper实例,这个实例会保存一个MessageQueue对象,然后当前线程进入一个无限循环中去,不断从MessageQueue中读取Handler发来的消息。
private Looper(){可知,其他类无法通过Looper的构造器创建Looper对象,只能通过Looper的静态方法prepare()来创建子线程的Looper对象。
mQueue = new MessageQueue();
mRun = true;
mThread = Thread.currentThread();
}
//prepare的作用是将Looper中的空线程指向当前线程
//空消息队列指向当前线程的消息队列
public static final void prepare(){
if(sThreadLocal.get()!=null){
throw new RuntimeException("");
}
sThreadLocal.set(new Looper());
}
(4)MessageQueue:消息队列。用来存放线程放入的消息,它采用先进先出的方式来管理Message(消息)。
2.Android Handler机制工作原理
(1)Handler创建消息
每一个消息都需要被指定的Handler处理,通过Handler创建消息便可以完成此功能。Android消息机制中引入了消息池。Handler创建消息时首先查询消息池中是否有消息存在,如果有直接从消息池中取得,如果没有则重新初始化一个消息实例。使用消息池的好处是:消息不被使用时,并不作为垃圾回收,而是放入消息池,可供下次Handler创建消息时使用。消息池提高了消息对象的复用,减少系统垃圾回收的次数。消息的创建流程如图所示。
(2)Handler发送消息
UI主线程初始化第一个Handler时会通过ThreadLocal创建一个Looper,该Looper与UI主线程一一对应。使用ThreadLocal的目的是保证每一个线程只创建唯一一个Looper。之后其他Handler初始化的时候直接获取第一个Handler创建的Looper。Looper初始化的时候会创建一个消息队列MessageQueue。至此,主线程、消息循环、消息队列之间的关系是1:1:1。
Handler、Looper、MessageQueue的初始化流程如图所示:
Hander持有对UI主线程消息队列MessageQueue和消息循环Looper的引用,子线程可以通过Handler将消息发送到UI线程的消息队列MessageQueue中。
(3)Handler处理消息
UI主线程通过Looper循环查询消息队列UI_MQ,当发现有消息存在时会将消息从消息队列中取出。首先分析消息,通过消息的参数判断该消息对应的Handler,然后将消息分发到指定的Handler进行处理。 子线程通过Handler、Looper与UI主线程通信的流程如图所示。
二、源码解析
1、Looper
对于Looper主要是prepare()---创建唯一的Looper对象,并在构造函数中创建与Looper对象相对应的消息队列两个方法;Loop()方法循环的查找消息队列中是否有消息对象。
首先看prepare()方法
public static final void prepare() {sThreadLocal是一个ThreadLocal对象,可以在一个线程中存储变量。可以看到,在第5行,将一个Looper的实例放入了ThreadLocal,并且2-4行判断了sThreadLocal是否为null,否则抛出异常。这也就说明了Looper.prepare()方法不能被调用两次,同时也保证了一个线程中只有一个Looper实例~相信有些哥们一定遇到这个错误。
if (sThreadLocal.get() != null) { //判断线程(如UI线程是否已经创建了Looper对象)
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(true)); //创建一个Looper对象,并将其保存到ThreadLocal变量中
}
下面看Looper的构造方法:
private Looper(boolean quitAllowed) {在构造方法中,创建了一个MessageQueue(消息队列)。
mQueue = new MessageQueue(quitAllowed);
mRun = true;
mThread = Thread.currentThread();
}
然后我们看loop()方法:
public static void loop() {第2行:
final Looper me = myLooper(); //获得Looper对象
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
final MessageQueue queue = me.mQueue; //获得消息队列
// Make sure the identity of this thread is that of the local process,
// and keep track of what that identity token actually is.
Binder.clearCallingIdentity();
final long ident = Binder.clearCallingIdentity();
for (;;) {
Message msg = queue.next(); // 获得消息(先进先出)
if (msg == null) { //消息为空循环等待
// No message indicates that the message queue is quitting.
return;
}
// This must be in a local variable, in case a UI event sets the logger
Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
msg.target.dispatchMessage(msg);//将消息发送到相应的Handler对象
if (logging != null) {
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}
// Make sure that during the course of dispatching the
// identity of the thread wasn't corrupted.
final long newIdent = Binder.clearCallingIdentity();
if (ident != newIdent) {
Log.wtf(TAG, "Thread identity changed from 0x"
+ Long.toHexString(ident) + " to 0x"
+ Long.toHexString(newIdent) + " while dispatching to "
+ msg.target.getClass().getName() + " "
+ msg.callback + " what=" + msg.what);
}
msg.recycle();
}
}
public static Looper myLooper() {方法直接返回了sThreadLocal存储的Looper实例,如果me为null则抛出异常,也就是说looper方法必须在prepare方法之后运行。
return sThreadLocal.get();
}
第6行:拿到该looper实例中的mQueue(消息队列)
13到45行:就进入了我们所说的无限循环。
14行:取出一条消息,如果没有消息则阻塞。
27行:使用调用 msg.target.dispatchMessage(msg);把消息交给msg的target的dispatchMessage方法去处理。Msg的target是什么呢?其实就是handler对象,下面会进行分析。
44行:释放消息占据的资源。
(1)Looper()构造方法主要作用:
与当前线程绑定,保证一个线程只会有一个Looper实例,同时一个Looper实例也只有一个MessageQueue。
(2)loop()方法,不断从MessageQueue中去取消息,交给消息的target(Handler对象)的dispatchMessage去处理。好了,我们的异步消息处理线程已经有了消息队列(MessageQueue),也有了在无限循环体中取出消息的哥们,现在缺的就是发送消息的对象了,于是乎:Handler登场了。
2、Handler
使用Handler之前,我们都是初始化一个实例,比如用于更新UI线程,我们会在声明的时候直接初始化,或者在onCreate中初始化Handler实例。所以我们首先看Handler的构造方法,看其
(1)如何与MessageQueue联系上的,它在子线程中发送的消息(一般发送消息都在非UI线程)
(2)怎么发送到MessageQueue中的。
*****************将Handler对象与Looper、MessageQueue关联14行:通过Looper.myLooper()获取了当前线程保存的Looper实例,然后在19行又获取了这个Looper实例中保存的MessageQueue(消息队列),这样就保证了handler的实例与我们Looper实例中MessageQueue关联上了。
public Handler() {
this(null, false);
}
/*构造方法*/
public Handler(Callback callback, boolean async) {
if (FIND_POTENTIAL_LEAKS) {
final Class<? extends Handler> klass = getClass();
if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
(klass.getModifiers() & Modifier.STATIC) == 0) {
Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
klass.getCanonicalName());
}
}
mLooper = Looper.myLooper(); //01.获得当前Looper实例
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue; //02.获得Looper相关联的消息队列MessageQueue
mCallback = callback;
mAsynchronous = async;
}
*****************发送消息分析辗转反则最后调用了sendMessageAtTime,在此方法内部有直接获取MessageQueue然后调用了enqueueMessage方法,我们再来看看此方法:
然后看我们最常用的sendMessage方法
public final boolean sendMessage(Message msg)
{
return sendMessageDelayed(msg, 0);
}
public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {
Message msg = Message.obtain(); //创建消息对象
msg.what = what; //将消息保存到消息对象的what变量中
return sendMessageDelayed(msg, delayMillis);
}
public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
MessageQueue queue = mQueue; //将Looper对应的消息队列绑定到本地消息队列
if (queue == null) {
RuntimeException e = new RuntimeException(
this + " sendMessageAtTime() called with no mQueue");
Log.w("Looper", e.getMessage(), e);
return false;
}
return enqueueMessage(queue, msg, uptimeMillis); //将消息存放到消息队列中去
}
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {enqueueMessage中首先为meg.target赋值为this,【如果大家还记得Looper的loop方法会取出每个msg然后交给msg,target.dispatchMessage(msg)去处理消息】,也就是把当前的handler作为msg的target属性。最终会调用queue的enqueueMessage的方法,也就是说handler发出的消息,最终会保存到消息队列中去。
msg.target = this; //handler对象,即msg.target就是handler对象
if (mAsynchronous) {
msg.setAsynchronous(true);
}
//uptimeMillis后将消息存放到消息队列中
return queue.enqueueMessage(msg, uptimeMillis);
}
*****************处理消息分析
现在已经很清楚了Looper会调用prepare()和loop()方法,在当前执行的线程中保存一个Looper实例,这个实例会保存一个MessageQueue对象,然后当前线程进入一个无限循环中去,不断从MessageQueue中读取Handler发来的消息。然后再回调创建这个消息的handler中的dispathMessage方法,下面我们赶快去看一看这个方法:即
msg,target.dispatchMessage(msg)可以看到,第10行,调用了handleMessage方法,下面我们去看这个方法:
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg); //是个空方法,有用户实现
}
}
/**可以看到这是一个空方法,为什么呢,因为消息的最终回调是由我们控制的,我们在创建handler的时候都是复写handleMessage方法,然后根据msg.what进行消息处理。
* Subclasses must implement this to receive messages.
*/
public void handleMessage(Message msg) {
}
例如:
private Handler mHandler = new Handler()到此,这个流程已经解释完毕,让我们首先总结一下
{
public void handleMessage(android.os.Message msg)
{
switch (msg.what)
{
case value:
break;
default:
break;
}
};
};
(1)首先Looper.prepare()在本线程中保存一个Looper实例,然后该实例中保存一个MessageQueue对象;因为Looper.prepare()在一个线程中只能调用一次,所以MessageQueue在一个线程中只会存在一个。
(2)Looper.loop()会让当前线程进入一个无限循环,不断从MessageQueue的实例中读取消息,然后回调msg.target.dispatchMessage(msg)方法,该方法包含调用handleMessage()。
(3)Handler的构造方法,会首先得到当前线程中保存的Looper实例,进而与Looper实例中的MessageQueue想关联。
(4)Handler的sendMessage方法,会给msg的target赋值为handler自身,然后加入MessageQueue中。
(5)在构造Handler实例时,我们会重写handleMessage方法,也就是msg.target.dispatchMessage(msg)最终调用的方法。
在Activity中,我们并没有显示的调用Looper.prepare()和Looper.loop()方法,为啥Handler可以成功创建呢,这是因为在Activity的启动代码中,已经在当前UI线程默认调用了Looper.prepare()和Looper.loop()方法,为UI线程创建与之相关的Looper对象及配套的消息队列MessageQueue.
3、Handler post
今天有人问我,你说Handler的post方法创建的线程和UI线程有什么关系?
其实这个问题也是出现这篇博客的原因之一;这里需要说明,有时候为了方便,我们会直接写如下代码:
mHandler.post(new Runnable()然后run方法中可以写更新UI的代码,其实这个Runnable并没有创建什么线程,而是发送了一条消息,下面看源码:
{
@Override
public void run()
{
Log.e("TAG", Thread.currentThread().getName());
mTxt.setText("yoxi");
}
});
public final boolean post(Runnable r)可以看到,在getPostMessage中,得到了一个Message对象,然后将我们创建的Runable对象作为callback属性,赋值给了此message.
{
return sendMessageDelayed(getPostMessage(r), 0);
}
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r; //将Runnable对象保存到Message对象中,由Handler处理消息
return m;
}
注:产生一个Message对象,可以new ,也可以使用Message.obtain()方法;两者都可以,但是更建议使用obtain方法,因为Message内部维护了一个Message池用于Message的复用,避免使用new 重新分配内存。
public final boolean sendMessageDelayed(Message msg, long delayMillis)最终和handler.sendMessage一样,调用了sendMessageAtTime,然后调用了enqueueMessage方法,给msg.target赋值为handler,最终加入MessagQueue.
{
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
MessageQueue queue = mQueue;
if (queue == null) {
RuntimeException e = new RuntimeException(
this + " sendMessageAtTime() called with no mQueue");
Log.w("Looper", e.getMessage(), e);
return false;
}
return enqueueMessage(queue, msg, uptimeMillis);
}
可以看到,这里msg的callback和target都有值,那么会执行哪个呢?
其实上面已经贴过代码,就是dispatchMessage方法:
public void dispatchMessage(Message msg) {第2行,如果不为null,则执行callback回调,也就是我们的Runnable对象。
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
参考:http://blog.csdn.net/itachi85/article/details/8035333
Looper、Handler、Message三者关系:http://blog.csdn.net/lmj623565791/article/details/38377229
8.AsyncTask工作原理
1.AsyncTask实现原理
AsyncTask(异步任务),是android提供的轻量级封装过的后台任务类,AsyncTask异步任务允许开发者定义一个运行在单独线程中的任务,还能在任务的不同阶段提供回调函数。这些回调函数被设计成无需使用runOnUiThread方法即可更新UI。AsyncTask的本质是一个线程池,所有提交的异步任务都会在这个静态线程池中的工作线程内的doInBackground(mParams)方法执行,当工作线程需要跟UI线程交互时,工作线程会通过向在UI线程创建的Handler(AsyncTask内部的InternalHandler)传递消息的方式,调用相关的回调函数,从而实现UI界面的更新。
-----源码
2.AsyncTask优缺点(先介绍实现原理)
(1)优点:AsyncTask实现容易,通过AsyncTask类开发者可以很容易在其他线程中执行耗时任务,也可以在需要时很方便地和主线程(UI线程)通信。
(2)缺点:
◆该类的实例只能使用一次,每次执行操作都要新建一个MyAsyncTask对象。因此,它不适合哪些频繁的操作,因为这么快速聚集需要垃圾回收的对象,并最终导致应用程序卡顿。◆AsyncTask不能对操作设置执行时间,也无法间隔一段时间执行操作,它适合文件下载。
◆最大并发线程数目不超过5个,因为在使用多个异步操作并需要进行UI通信时,会变得很复杂。
3.注意事项
使用AsyncTask类,以下是几条必须遵守的准则:
(1)Task的实例必须在UI thread中创建;
(2)execute方法必须在UI thread中调用;
(3)不要手动的调用onPreExecute(), onPostExecute(Result),doInBackground(Params...), onProgressUpdate(Progress...)这几个方法;
(4)该task只能被执行一次,否则多次调用时将会出现异常
二、源码分析
1.实例演示
AsyncTask.java
public class AsyncTaskActivity extends Activity {(1) AsyncTask<Params,Progress,Result>定义了三种泛型类型
private ImageView mImageView;
private Button mButton;
private ProgressBar mProgressBar;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
mImageView= (ImageView) findViewById(R.id.imageView);
mButton = (Button) findViewById(R.id.button);
mProgressBar = (ProgressBar) findViewById(R.id.progressBar);
mButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
GetCSDNLogoTask task = new GetCSDNLogoTask();
task.execute("http://csdnimg.cn/www/images/csdnindex_logo.gif");
}
});
}
class GetCSDNLogoTask extends AsyncTask<String,Integer,Bitmap> {//继承AsyncTask
/处理后台执行的任务,在后台线程执行
@Override
protected Bitmap doInBackground(String... params) {/
publishProgress(0);//将会调用onProgressUpdate(Integer... progress)方法
HttpClient hc = new DefaultHttpClient();
publishProgress(30);
HttpGet hg = new HttpGet(params[0]);//获取csdn的logo
final Bitmap bm;
try {
HttpResponse hr = hc.execute(hg);
bm = BitmapFactory.decodeStream(hr.getEntity().getContent());
} catch (Exception e) {
return null;
}
publishProgress(100);
//mImageView.setImageBitmap(result); 不能在后台线程操作ui
return bm;
}
//在调用publishProgress之后被调用,在ui线程执行
protected void onProgressUpdate(Integer... progress) {
mProgressBar.setProgress(progress[0]);//更新进度条的进度
}
//后台任务执行完之后被调用,在ui线程执行
protected void onPostExecute(Bitmap result) {
if(result != null) {
Toast.makeText(AsyncTaskActivity.this, "成功获取图片", Toast.LENGTH_LONG).show();
mImageView.setImageBitmap(result);
}else {
Toast.makeText(AsyncTaskActivity.this, "获取图片失败", Toast.LENGTH_LONG).show();
}
}
protected void onPreExecute () {//在 doInBackground(Params...)之前被调用,在ui线程执行
mImageView.setImageBitmap(null);
mProgressBar.setProgress(0);//进度条复位
}
protected void onCancelled () {//在ui线程执行
mProgressBar.setProgress(0);//进度条复位
}
}
}
◆Params-启动任务执行的输入参数类型,比如HTTP请求的URL;
◆Progress-后台任务执行的百分比,一般为Integer类型
◆Result-后台执行任务最终返回的结果类型
(2)AsyncTask实现异步义任务
◆onPreExecute():在执行后台任务之前被UI线程调用,完成一些初始化工作
◆doInBackground(Params…):执行后台耗时任务。在子线程中调用,不能直接操作UI.
◆onProgressUpdate(Integer... progress):在调用publicProgress(Progress…)后被调用,用
于更新任务的进度,在UI线程中被调用。
◆onPostExecute(Result):将doInBackgraound得到的结果操作UI。在UI线程中被调用,相
当于Handler方式处理UI.
2.源码分析
在分析实现流程之前,我们先了解一下AsyncTask有哪些成员变量。
private static final int CORE_POOL_SIZE =5;//5个核心工作线程
private static final int MAXIMUM_POOL_SIZE = 128;//最多128个工作线程
private static final int KEEP_ALIVE = 1;//空闲线程的超时时间为1秒
private static final BlockingQueue<Runnable> sWorkQueue =
new LinkedBlockingQueue<Runnable>(10);//等待队列
private static final ThreadPoolExecutorsExecutor = new ThreadPoolExecutor(CORE_POOL_SIZE,
MAXIMUM_POOL_SIZE, KEEP_ALIVE, TimeUnit.SECONDS, sWorkQueue,sThreadFactory);//线程池是静态变量,所有的异步任务都会放到这个线程池的工作线程内执行。
回到例子中,点击按钮之后会新建一个GetCSDNLogoTask对象:
GetCSDNLogoTask task = new GetCSDNLogoTask();
此时会调用父类AsyncTask的构造函数:
AsyncTask.java
public AsyncTask() {WorkerRunnable类实现了callable接口的call()方法,该函数会调用我们在AsyncTask子类中实现的doInBackground(mParams)方法,由此可见,WorkerRunnable封装了我们要执行的异步任务。FutureTask中的protected void done() {}方法实现了异步任务状态改变后的操作。当异步任务被取消,会向UI线程传递MESSAGE_POST_CANCEL消息,当任务成功执行,会向UI线程传递MESSAGE_POST_RESULT消息,并把执行结果传递到UI线程。
mWorker = new WorkerRunnable<Params, Result>() {
public Result call() throws Exception {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
return doInBackground(mParams);
}
};
mFuture = new FutureTask<Result>(mWorker) {
@Override
protected void done() {
Message message;
Result result = null;
try {
result = get(); //获得异步任务执行的结果
} catch (InterruptedException e) {
android.util.Log.w(LOG_TAG, e);
} catch (ExecutionException e) {
throw new RuntimeException("An error occured while executing doInBackground()",
e.getCause());
} catch (CancellationException e) { //通过Handler对象向UI线程发送结果对应的消息
message = sHandler.obtainMessage(MESSAGE_POST_CANCEL,
new AsyncTaskResult<Result>(AsyncTask.this, (Result[]) null));
message.sendToTarget();//取消任务,发送MESSAGE_POST_CANCEL消息
return;
} catch (Throwable t) {
throw new RuntimeException("An error occured while executing "
+ "doInBackground()", t);
}
message = sHandler.obtainMessage(MESSAGE_POST_RESULT,
new AsyncTaskResult<Result>(AsyncTask.this, result));//完成任务,发送MESSAGE_POST_RESULT消息并传递result对象
message.sendToTarget();
}
};
}
由此可知,AsyncTask在构造的时候已经定义好要异步执行的方法doInBackground(mParams)和任务状态变化后的操作(包括失败和成功)。
当创建完GetCSDNLogoTask对象后,执行
task.execute("http://csdnimg.cn/www/images/csdnindex_logo.gif");
此时会调用AsyncTask的execute(Params...params)方法
AsyncTask.java
public final AsyncTask<Params,Progress, Result> execute(Params... params) {当任务正在执行或者已经完成,会抛出IllegalStateException,由此可知我们不能够重复调用execute(Params...params)方法。在提交任务到线程池之前,调用了onPreExecute()方法。然后才执行sExecutor.execute(mFuture)是任务提交到线程池。
if (mStatus != Status.PENDING) {
switch (mStatus) {
/*由如下代码可知,execute只能被执行一次
* 否则回报"Cannot execute task:the taskis already running"错误
*/
case RUNNING:
throw newIllegalStateException("Cannot execute task:"
+ " the taskis already running.");
case FINISHED:
throw newIllegalStateException("Cannot execute task:"
+ " the taskhas already been executed "
+ "(a task canbe executed only once)");
}
}
mStatus = Status.RUNNING;
onPreExecute();//运行在ui线程,在提交任务到线程池之前执行
mWorker.mParams = params;
sExecutor.execute(mFuture);//提交任务到线程池
return this;
}
前面我们说到,当任务的状态发生改变时(1、执行成功2、取消执行3、进度更新),工作线程会向UI线程的Handler传递消息。在《Android异步处理三:Handler+Looper+MessageQueue深入详解》一文中我们提到,Handler要处理其他线程传递过来的消息。在AsyncTask中,InternalHandler是在UI线程上创建的,它接收来自工作线程的消息,当接收到消息之后,AsyncTask会调用自身相应的回调方法。实现代码如下:
AsyncTask.java
private static class InternalHandler extends Handler {参考:
@SuppressWarnings({"unchecked","RawUseOfParameterizedType"})
@Override
public voidhandleMessage(Message msg) {
AsyncTaskResult result =(AsyncTaskResult) msg.obj;
switch (msg.what) {
case MESSAGE_POST_RESULT:
// There is onlyone result
result.mTask.finish(result.mData[0]);//执行任务成功
break;
caseMESSAGE_POST_PROGRESS:
result.mTask.onProgressUpdate(result.mData);//进度更新
break;
caseMESSAGE_POST_CANCEL:
result.mTask.onCancelled();//取消任务
break;
}
}
}
AsyncTask的实现原理:http://blog.csdn.net/mylzc/article/details/6774131#comments