Java基础技术核心归纳(四)
来源:互联网 发布:阿里云icp备案系统 编辑:程序博客网 时间:2024/06/06 03:03
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 { public void say(){ System.out.println("1"); } public static void main(String[] args) { ((HelloWord)null).say(); }}说明:编译通过,运行时出现"java.lang.NullPointerException"异常。如果将say()方法修改为static静态方法时,执行" ((HelloWord)null).say(); "输出1。
(2)null本身不是对象,也不是Objcet的实例。
(3)在定义变量的时候,如果定义后没有给变量赋值,则Java在运行时会自动给变量赋值。赋值原则是整数类型int、byte、short、long的自动赋值为0,带小数点的float、double自动赋值为0.0,boolean的自动赋值为false,其他各供引用类型变量自动赋值为null(String s默认值为null)。
典型实例:
public class HelloWord { public static void main(String[] args) { String s; System.out.println("s="+s); }}说明:由于局部变量String s没有初始化,代码不能编译通过。如果将String s改为静态成员变量,那么当类加载时会自动给s赋初值null,输出"s=null"。
(4)容器类型与null。
List:允许重复元素,可以加入任意多个null。
Set:不允许重复元素,最多可以加入一个null。
Map:Map的key最多可以加入一个null,value字段没有限制。
数组:基本类型数组,定义后,如果不给定初始值,则java运行时会自动给定值。引用类型数
组,不给定初始值,则所有的元素值为null。
(5)释放内存。让一个非null的引用类型变量指向null。这样这个对象就不再被任何对象应用了。等待JVM垃圾回收机制去回收
3.Java构造方法被调用情况
当一个子类继承于父类时,实例化子类,首先会执行父类默认无参数构造方法,然后再执行子类用于实例化对象的构造方法。
class People{ 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"); }}4.Java的实例化对象过程
学习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(){ 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()); }可知,其他类无法通过Looper的构造器创建Looper对象,只能通过Looper的静态方法prepare()来创建子线程的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() { if (sThreadLocal.get() != null) { //判断线程(如UI线程是否已经创建了Looper对象) throw new RuntimeException("Only one Looper may be created per thread"); } sThreadLocal.set(new Looper(true)); //创建一个Looper对象,并将其保存到ThreadLocal变量中}sThreadLocal是一个ThreadLocal对象,可以在一个线程中存储变量。可以看到,在第5行,将一个Looper的实例放入了ThreadLocal,并且2-4行判断了sThreadLocal是否为null,否则抛出异常。这也就说明了Looper.prepare()方法不能被调用两次,同时也保证了一个线程中只有一个Looper实例~相信有些哥们一定遇到这个错误。
下面看Looper的构造方法:
private Looper(boolean quitAllowed) { mQueue = new MessageQueue(quitAllowed); mRun = true; mThread = Thread.currentThread(); }在构造方法中,创建了一个MessageQueue(消息队列)。
然后我们看loop()方法:
public static void loop() { 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(); } }第2行:
public static Looper myLooper() {return sThreadLocal.get();}方法直接返回了sThreadLocal存储的Looper实例,如果me为null则抛出异常,也就是说looper方法必须在prepare方法之后运行。
第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关联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; }14行:通过Looper.myLooper()获取了当前线程保存的Looper实例,然后在19行又获取了这个Looper实例中保存的MessageQueue(消息队列),这样就保证了handler的实例与我们Looper实例中MessageQueue关联上了。
*****************发送消息分析然后看我们最常用的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); //将消息存放到消息队列中去 }辗转反则最后调用了sendMessageAtTime,在此方法内部有直接获取MessageQueue然后调用了enqueueMessage方法,我们再来看看此方法:
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) { msg.target = this; //handler对象,即msg.target就是handler对象 if (mAsynchronous) { msg.setAsynchronous(true); } //uptimeMillis后将消息存放到消息队列中 return queue.enqueueMessage(msg, uptimeMillis); }enqueueMessage中首先为meg.target赋值为this,【如果大家还记得Looper的loop方法会取出每个msg然后交给msg,target.dispatchMessage(msg)去处理消息】,也就是把当前的handler作为msg的target属性。最终会调用queue的enqueueMessage的方法,也就是说handler发出的消息,最终会保存到消息队列中去。
*****************处理消息分析
现在已经很清楚了Looper会调用prepare()和loop()方法,在当前执行的线程中保存一个Looper实例,这个实例会保存一个MessageQueue对象,然后当前线程进入一个无限循环中去,不断从MessageQueue中读取Handler发来的消息。然后再回调创建这个消息的handler中的dispathMessage方法,下面我们赶快去看一看这个方法:即
msg,target.dispatchMessage(msg)public void dispatchMessage(Message msg) { if (msg.callback != null) { handleCallback(msg); } else { if (mCallback != null) { if (mCallback.handleMessage(msg)) { return; } } handleMessage(msg); //是个空方法,有用户实现 } }可以看到,第10行,调用了handleMessage方法,下面我们去看这个方法:
/** * Subclasses must implement this to receive messages. */ public void handleMessage(Message msg) { }可以看到这是一个空方法,为什么呢,因为消息的最终回调是由我们控制的,我们在创建handler的时候都是复写handleMessage方法,然后根据msg.what进行消息处理。
例如:
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() { @Override public void run() { Log.e("TAG", Thread.currentThread().getName()); mTxt.setText("yoxi"); } });然后run方法中可以写更新UI的代码,其实这个Runnable并没有创建什么线程,而是发送了一条消息,下面看源码:
public final boolean post(Runnable r) { return sendMessageDelayed(getPostMessage(r), 0); } private static Message getPostMessage(Runnable r) { Message m = Message.obtain(); m.callback = r; //将Runnable对象保存到Message对象中,由Handler处理消息 return m; }可以看到,在getPostMessage中,得到了一个Message对象,然后将我们创建的Runable对象作为callback属性,赋值给了此message.
注:产生一个Message对象,可以new ,也可以使用Message.obtain()方法;两者都可以,但是更建议使用obtain方法,因为Message内部维护了一个Message池用于Message的复用,避免使用new 重新分配内存。
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; 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); }最终和handler.sendMessage一样,调用了sendMessageAtTime,然后调用了enqueueMessage方法,给msg.target赋值为handler,最终加入MessagQueue.
可以看到,这里msg的callback和target都有值,那么会执行哪个呢?
其实上面已经贴过代码,就是dispatchMessage方法:
public void dispatchMessage(Message msg) { if (msg.callback != null) { handleCallback(msg); } else { if (mCallback != null) { if (mCallback.handleMessage(msg)) { return; } } handleMessage(msg); } }第2行,如果不为null,则执行callback回调,也就是我们的Runnable对象。
参考: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 { 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);//进度条复位 } } }(1) AsyncTask<Params,Progress,Result>定义了三种泛型类型
◆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() { 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(); } }; }WorkerRunnable类实现了callable接口的call()方法,该函数会调用我们在AsyncTask子类中实现的doInBackground(mParams)方法,由此可见,WorkerRunnable封装了我们要执行的异步任务。FutureTask中的protected void done() {}方法实现了异步任务状态改变后的操作。当异步任务被取消,会向UI线程传递MESSAGE_POST_CANCEL消息,当任务成功执行,会向UI线程传递MESSAGE_POST_RESULT消息,并把执行结果传递到UI线程。
由此可知,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) { 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; }当任务正在执行或者已经完成,会抛出IllegalStateException,由此可知我们不能够重复调用execute(Params...params)方法。在提交任务到线程池之前,调用了onPreExecute()方法。然后才执行sExecutor.execute(mFuture)是任务提交到线程池。
前面我们说到,当任务的状态发生改变时(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
阅读全文
0 0
- Java基础技术核心归纳(四)
- Android基础技术核心归纳(四)
- Java基础技术核心归纳(一)
- Java基础技术核心归纳(二)
- Java基础技术核心归纳(三)
- Android基础技术核心归纳(一)
- Android基础技术核心归纳(二)
- Android基础技术核心归纳(三)
- Java 核心基础技术(二)
- Android核心基础(四)
- Android核心基础四
- Java集合归纳-<四>List
- Java语言基础细节归纳
- 史上最全Java基础知识点归纳
- Java核心J2SE(四)
- Java高手真经. 编程基础卷:Java核心编程技术:Java基础+核心库+图形+网络+高级特性
- Java知识点归纳(Java基础部分)
- java知识点归纳(Java基础部分)
- 学习mysql事务
- Markdown总结:
- NOR flash和NAND flash区别,RAM 和ROM区别
- Draft.js 自己定义块组件
- java 中的线程研究笔记(一)
- Java基础技术核心归纳(四)
- 微信小程序实现滑动tab切换和点击tab切换并显示相应的数据(附源代码)
- RecyclerView--个人使用心得
- maven 设置代理
- 关于优惠券后台设计思考
- hibernate缓存机制详细分析
- 关于Parameter 'xxx' not found. Available parameters are [0, 1, 2, param3, param1, param2]错误
- Redis 事务
- JVM常见的启动参数