一、实现多线程
Android提供了良种创建线程的方法:一种是通过Thread类的构造方法创建线程对象,并重写run()方法实现;另一种是通过实现Runnable接口实现。
1.通过Thread类的构造方法创建线程
①class MyThread extends Thread{
public void run(){
//处理具体的逻辑
}
}
new MyThread().start();
②Thread(Runnable runnable)
即
class MyThread implements Runnable{
public void run(){
}
}
MyThread myThread = new MyThread();
new MyThread(myThread).start();
eg:
Thread thread = new Thread(new Runnable(){
@Override
public void run(){
//要执行的操作
}
});
2.通过实现Runnable接口创建线程
public class ClassName extends Object implements Runnable
当一个类实现Runnable接口后,还需要实现其run()方法,在run()方法中,可以编写要执行的操作的代码。
eg.
public class MainActivity entends Activity implements Runnable{
private Thread thread;
int i;
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
Button startButton = (Button)findViewById(R.id.button1);
startButton.setOnClickListener(new OnClickListener(){
public void onClick(View v){
i=0;
thread = new Thread(MainActivity.this);
thread.start();
}
});
Button stopButton = (Button) findViewById(R.id.button02);
stopButton.setOnClickListener(new OnClickListener(){
public void onClick(View v){
if(thread!=null){
thread.interrupt();
thread=null;
}
}
});
}
public void run(){
//要执行的操作
while(!Thread.currentThread().isInterrupted){
i++;
Log.i("循环变量:String.valueOf(i));
}
}
protected void onDestroy(){
if(thread!=null){
thread.interrupt();
thread=null;
}
super.onDestroy();
}
}
3.开启线程
thread.start();
4.线程的休眠
线程的休眠就是让线程暂停一段时间后再次执行。
Thread.sleep(long time);
5.中断线程
使用interrupt()方法可以向指定的线程发送一个中断请求,并将该线程标记为中断状态。
另外,由于当线程执行wait()、join()或sleep()方法时,线程的中断状态将被清除并抛出InterruptedException,所以,如果想在线程中执行了wait()、join()或sleep()方法时中断线程,就需要使用一个boolean型的标记变量来记录线程的中断状态,并通过该标记变量来控制循环的执行与停止。
eg;通过名称为isInterrupt的boolean型变量来标记线程的中断,关键代码如下:
private boolean isInterrupt=false;
public void run(){
while(!isInterrupt){
}
}
二、Handler消息传递机制
Android中的异步消息处理主要由4个部分组成:Message、Handler、MessageQueue和Looper。
1.循环者(Looper)简介
在Android中,一个线程对应一个Looper对象,而一个Looper对象又对应一个MessageQueue(消息队列).MessageQueue用于存放Message(消息),在MessageQueue中,存放的消息按照FIFO(先进先出)原则执行,MessageQueue被封装在Looper里面。
Looper对象用来为一个线程开启一个消息循环,从而操作MessageQueue。默认情况下,Android中新创建的线程是没有开启消息循环的,但是主线程除外。系统自动为主线程创建Looper对象,开启消息循环。所以,当在主线程中应用Handler handler2 = new Handler();创建Handler对象时不会出错,而如果在新创建的非主线程中应用该代码创建Handler对象,将产生异常信息。
如果想要在非主线程中创建Handler对象,首先需要使用Looper类的prepare()方法来初始化一个Looper对象,然后创建该Handler对象,再使用Looper类的loop()方法启动Looper,从消息队列中获取和处理消息。
eg:
public class LooperThread extends Thread{
public Handler handler1;
public void run(){
super.run();
Looper.prepare();
handler1=new Handler(){
public void handleMessage(Message msg){
Log.i("Looper",String.valueOf(msg.what));
}
};
Message m = handler1.obtainMessage();
m.what=0x11;
handler1.sendMessage(m);
Looper.loop();
}
}
Looper类提供的常用方法
prepare()
loop()
myLooper()
getThread()
quit()
注意:写在Looper.loop()之后的代码不会被执行,该函数内部是一个循环,当调用Handler.getLooper().quit()方法后,loop()方法才会中止,其后代码才能运行。
2.消息处理类(Handler)简介
消息处理类(Handler)允许发送和处理Message或Runnable对象到其所在线程的MessageQueue中。Handler主要有以下两个作用。
1)将Message或Runnable应用post()或sendMessage()方法发送到MessageQueue中,在发送时可以指定延迟时间、发送时间及要携带的Bundle数据。当MessageQueue循环到该Message时,调用相应的Handler对象的handlerMessage()方法对其进行处理。
2)在子线程中与主线程进行通信,也就是在工作线程中与UI线程进行通信。
说明:在一个线程中,只能有一个Looper和MessageQueue,但是可以有多个handler,而且这些Handler可以共享同一个Looper和MessageQueue.
Handler类提供的常用方法:
handlerMessage(Message msg)
post(Runnable r)
postAtTime(Runnable r,long uptimeMillis)
postDelayed(Runnable r,long delayMillis)
sendEmptyMessage(int what)
sendMessage(Message msg)
sendMessageAtTime(Message msg,long uptimeMillis)
sendMessageDelayed(Message msg,long delayMillis)
eg.
Handler handler = new Handler(){
public void handleMessage(Message msg){
switch(msg.what){
case 0:break;
case 1:break;
default:break;
}
}
}
3.消息类(Message)简介
消息类(Message)被存放在MessageQueue中,一个MessageQueue中可以包含多个Message对象。每个Message对象可以通过Message.obtain()或Handler.obtainMessage()方法获得。一个Message对象具有如下个属性。
arg1 int
arg2 int
obj Object
replyTo Messenger
what int
说明:使用Message类的属性可以携带int型数据,如果要携带其他类型的数据,可以先将要携带的数据保存到Bundle对象中,然后通过Message类的setData()方法将其添加到Message中。
Message类使用时需注意以下3三点:
(1)尽管Message有public的默认构造方法,但是通常情况下,需要使用Message.obtain()或Handler.obtainMessage()方法来从消息池中获得空消息对象,以节省资源。
(2)如果一个Message只需要携带简单的int型信息,应优先使用Message.arg1和Message.arg2属性来传递信息,这比用Bundle更节省内存。
(3)尽可能使用Message.what来标识信息,以便用不同方式处理Message。
eg:
iv=(ImageView)findViewById(R.id.imageView1);
new Thread(new Runnable(){
public void run(){
final Bitmap bitmap=getPicture("http://192.168.1.66:8081/test/images/android.png");//自定义根据网址获取图片对应的Bitmap对象
try{
Thread.sleep(2000);
}catch(){
e.printStackTrace();
}
iv.post(new Runnable(){
public void run(){
iv.setImageBitmap(bitmap);
}
});
}
}).start();
三、HandlerThread
使用步骤
HandlerThread
的本质:继承Thread
类 & 封装Handler
类HandlerThread
的使用步骤分为5步
// 步骤1:创建HandlerThread实例对象
// 传入参数 = 线程名字,作用 = 标记该线程
HandlerThread mHandlerThread = new HandlerThread("handlerThread");
// 步骤2:启动线程
mHandlerThread.start();
// 步骤3:创建工作线程Handler & 复写handleMessage()
// 作用:关联HandlerThread的Looper对象、实现消息处理操作 & 与其他线程进行通信
// 注:消息处理操作(HandlerMessage())的执行线程 = mHandlerThread所创建的工作线程中执行
Handler workHandler = new Handler( handlerThread.getLooper() ) {
@Override
public boolean handleMessage(Message msg) {
...//消息处理
return true;
}
});
// 步骤4:使用工作线程Handler向工作线程的消息队列发送消息
// 在工作线程中,当消息循环时取出对应消息 & 在工作线程执行相关操作
// a. 定义要发送的消息
Message msg = Message.obtain();
msg.what = 2; //消息的标识
msg.obj = "B"; // 消息的存放
// b. 通过Handler发送消息到其绑定的消息队列
workHandler.sendMessage(msg);
// 步骤5:结束线程,即停止线程的消息循环
mHandlerThread.quit();
Looper是通过调用loop方法驱动着消息循环的进行: 从MessageQueue中阻塞式地取出一个消息,然后让Handler处理该消息,周而复始,loop方法是个死循环方法。
那如何终止消息循环呢?我们可以调用Looper的quit方法或quitSafely方法,二者稍有不同。
相同点:
将不在接受新的事件加入消息队列。
不同点
当我们调用Looper的quit方法时,实际上执行了MessageQueue中的removeAllMessagesLocked方法,该方法的作用是把MessageQueue消息池中所有的消息全部清空,无论是延迟消息(延迟消息是指通过sendMessageDelayed或通过postDelayed等方法发送的需要延迟执行的消息)还是非延迟消息。
当我们调用Looper的quitSafely方法时,实际上执行了MessageQueue中的removeAllFutureMessagesLocked方法,通过名字就可以看出,该方法只会清空MessageQueue消息池中所有的延迟消息,并将消息池中所有的非延迟消息派发出去让Handler去处理,quitSafely相比于quit方法安全之处在于清空消息之前会派发所有的非延迟消息。
无论是调用了quit方法还是quitSafely方法只会,Looper就不再接收新的消息。即在调用了Looper的quit或quitSafely方法之后,消息循环就终结了,这时候再通过Handler调用sendMessage或post等方法发送消息时均返回false,表示消息没有成功放入消息队列MessageQueue中,因为消息队列已经退出了。
需要注意的是Looper的quit方法从API Level 1就存在了,但是Looper的quitSafely方法从API Level 18才添加进来。
HandlerThread的特点
-
HandlerThread将loop转到子线程中处理,说白了就是将分担MainLooper的工作量,降低了主线程的压力,使主界面更流畅。
-
开启一个线程起到多个线程的作用。处理任务是串行执行,按消息发送顺序进行处理。HandlerThread本质是一个线程,在线程内部,代码是串行处理的。
-
但是由于每一个任务都将以队列的方式逐个被执行到,一旦队列中有某个任务执行时间过长,那么就会导致后续的任务都会被延迟处理。
-
HandlerThread拥有自己的消息队列,它不会干扰或阻塞UI线程。
-
对于网络IO操作,HandlerThread并不适合,因为它只有一个线程,还得排队一个一个等着。
总结
-
如果经常要开启线程,接着又是销毁线程,这是很耗性能的,
HandlerThread
很好的解决了这个问题; -
HandlerThread
由于异步操作是放在Handler
的消息队列中的,所以是串行的,但只适合并发量较少的耗时操作。
-
HandlerThread
用完记得调用退出方法。 -
注意使用 handler 避免出现内存泄露
1、Activity的 runOnUiThread
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
android Activity runOnUiThread() 方法使用
2、Handler sendEmptyMessage()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
|
3、Handler post()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
|
在子线程中切换到主线程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
结果
1 2 3 4 5 |
|
可见这种方式可以快速切换线程,从log日志来看,切换到主线程不会阻塞子线程。
4、view Post()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
总结:
1、其实上面的四种方式都可归结于一种方式:handler 用于Android线程之间的通信。
2、为什么android要求只能在UI线程进行UI操作? 主要还是为了避免多线程造成的并发的问题。在单线程操作UI是安全的。
什么是内存泄漏?
- 内存泄漏是当程序不再使用到的内存时,释放内存失败而产生了无用的内存消耗。内存泄漏并不是指物理上的内存消失,这里的内存泄漏是值由程序分配的内存但是由于程序逻辑错误而导致程序失去了对该内存的控制,使得内存浪费。
怎样会导致内存泄漏?
- 资源对象没关闭造成的内存泄漏,如查询数据库后没有关闭游标cursor
- 构造Adapter时,没有使用 convertView 重用
- Bitmap对象不在使用时调用recycle()释放内存
- 对象被生命周期长的对象引用,如activity被静态集合引用导致activity不能释放
内存泄漏有什么危害?
-
内存泄漏对于app没有直接的危害,即使app有发生内存泄漏的情况,也不一定会引起app崩溃,但是会增加app内存的占用。内存得不到释放,慢慢的会造成app内存溢出。所以我们解决内存泄漏的目的就是防止app发生内存溢出。
1、新建线程引起的Activity内存泄漏
例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
|
运行上面的代码后,点击finish按钮,过一会儿发生了内存泄漏的问题。
为什么Activity6会发生内存泄漏?
进入Activity6 界面,然后点击finish按钮,Activity6销毁,但是Activity6里面的线程还在运行,匿名内部类Runnable对象引用了Activity6的实例,导致Activity6所占用的内存不能被GC及时回收。
如何改进?
Runnable改为静态非匿名内部类即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
|
2、Activity添加监听器造成Activity内存泄漏
1 2 3 4 5 6 7 8 9 10 11 12 |
|
这个是在开发中经常会犯的错误,NastyManager.getInstance() 是一个单例,当我们通过 addListener(this) 将 Activity 作为 Listener 和 NastyManager 绑定起来的时候,不好的事情就发生了。
如何改进?
想要修复这样的 Bug,其实相当简单,就是在你的 Acitivity 被销毁的时候,将他和 NastyManager 取消掉绑定就好了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
3、Handler 匿名内部类造成内存溢出?
先看着一段代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
|
这段代码运行起来后,立即点击 finish 按钮,通过检测,发现 HandlerActivity 出现了内存泄漏。当Activity finish后,延时消息会继续存在主线程消息队列中8秒钟,然后处理消息。而该消息引用了Activity的Handler对象,然后这个Handler又引用了这个Activity。这些引用对象会保持到该消息被处理完,这样就导致该Activity对象无法被回收,从而导致了上面说的 Activity泄露。Handler 是个很常用也很有用的类,异步,线程安全等等。如果有下面这样的代码,会发生什么呢? handler.postDeslayed ,假设 delay 时间是几个小时… 这意味着什么?意味着只要 handler 的消息还没有被处理结束,它就一直存活着,包含它的 Activity 就跟着活着。我们来想办法修复它,修复的方案是 WeakReference ,也就是所谓的弱引用。垃圾回收器在回收的时候,是会忽视掉弱引用的,所以包含它的 Activity 会被正常清理掉。
如何避免
- 使用静态内部类
- 使用弱引用
修改后代码是这样的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
|
这个Handler已经使用了静态内部类,并且使用了弱引用。但是这个并没有完全解决 HandlerActivity 内存泄漏的问题,罪魁祸首是线程创建的方式出了问题,就像本文的第一个例子一样。改进的方式,是把Runnable类写成静态内部类。
最终完整的代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 |
|
等等,还没完呢?
上面这个代码已经有效的解决了Handler,Runnable 引用Activity实例从而导致内存泄漏的问题,但是这不够。因为内存泄漏的核心原因就是这个某个对象应该被系统回收内存的时候,却被其他对象引用,造成该内存无法回收。所以我们在写代码的时候,要始终绷着这个弦。再回到上面这个问题,当当前Activity调用finish销毁的时候,在这个Activity里面所有线程是不是应该在OnDestory()方法里,取消线程。当然是否取消异步任务,要看项目具体的需求,比如在Activity销毁的时候,启动一个线程,异步写log日志到本地磁盘,针对这个需求却需要在OnDestory()方法里开启线程。所以根据当前环境做出选择才是正解。
所以我们还可以修改代码为:在onDestroy() 里面移除所有的callback 和 Message 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 |
|
4、AsyncTask造成内存泄漏
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
|
为什么?
上面代码在activity中创建了一个匿名类AsyncTask,匿名类和非静态内部类相同,会持有外部类对象,这里也就是activity,因此如果你在Activity里声明且实例化一个匿名的AsyncTask对象,则可能会发生内存泄漏,如果这个线程在Activity销毁后还一直在后台执行,那这个线程会继续持有这个Activity的引用从而不会被GC回收,直到线程执行完成。
怎么解决?
- 自定义静态AsyncTask类
- AsyncTask的周期和Activity周期保持一致。也就是在Activity生命周期结束时要将AsyncTask cancel掉。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
|
5、Timer Tasks 造成内存泄漏
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
|
为什么?
这里内存泄漏在于Timer和TimerTask没有进行Cancel,从而导致Timer和TimerTask一直引用外部类Activity。
怎么解决?
- 在适当的时机进行Cancel。
- TimerTask用静态内部类
注意:在网上看到一些资料说,解决TimerTask内存泄漏可以使用在适当的时机进行Cancel。经过测试,证明单单使用在适当的时机进行Cancel , 还是有内存泄漏的问题。所以一定要用静态内部类配合使用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
|
六、使用AsyncTask
AsyncTask是一个抽象类,如果我们向使用它,就必须要创建一个子类去继承它。在继承时我们可以为AsyncTask类指定3个泛型参数,这3个参数的用途如下:
Params.
在执行AsyncTask时需要传入的参数,可用于后台任务中使用。
Progress.
后台任务执行时,如果需要在界面上显示当前的进度,则使用这里指定的泛型作为进度单位。
Result.
当任务执行完毕时,如果需要对结果进行返回,则使用这里指定的泛型作为返回值类型。
因此,一个最简单的自定义AsyncTask就可以写成如下方式:
class DownloadTask extends AsyncTask<Void,Integer,Boolean>{
...
}
这里我们把AsyncTask的第一个泛型参数指定为Void,表示在执行AsyncTask的时候不需要传入参数给后台任务。第二个泛型参数指定为Integer,表示使用整型数据来作为进度显示单位。第三个泛型参数指定为Boolean,则表示使用布尔型数据来反馈执行结果。
当然,目前我们自定义的DownloadTask还是一个空任务,并不能进行任何实际的操作,我们还需要去重写AsyncTask中的几个方法才能完成对任务的定制。经常需要去重写的方法有以下4个。
1.onPreExecute()
这个方法会在后台任务开始执行之前调用,用于进行一些界面上的初始化操作,比如显示一个进度条对话框等。
2.doInBackground(Params ...)
这个方法中的所有代码都会在子线程中运行,我们应该在这里去处理所有的耗时任务。任务一旦完成就可以通过return语句来将任务的执行结果返回,如果AsyncTask的第三个泛型参数指定的时Void,就可以不反悔任务执行结果。注意,在这个方法中时不可以进行UI操作的,如果需要更新UI元素,比如说反馈当前任务的执行进度,可以调用publishProgress(Progress...)方法来完成。
3.onProgressUpdate(Progress...)
当在后台任务中调用了publishProgress(Progress...)方法后,onProgressUpdate(Progress...)方法就会很快被调用,该方法中携带的参数就是在后台任务中传递过来的。在这个方法中可以对UI进行操作,利用参数中的数值就可以对界面元素进行相应的更新。
4.onPostExecute(Result)
当后台任务执行完毕并通过return语句进行返回时,这个方法就很快会被调用。返回的数据会作为参数传递到此方法中,可以利用返回的数据进行一些UI操作,比如说提醒任务执行的结果,以及关闭掉进度条对话框等。
因此,一个比较完整的自定义AsyncTask就可以写成如下方式:
class DownloadTask extends AsyncTask<Void,Integer,Boolean>{
protected void onPreExecute(){
progressDialog.show();
}
protected Boolean doInBackground(Void... params){
try{
while(true){
int downloadPercent=doDownload();//
publishProgress(downloadPercent);
if(downloadPercent>=100){
break;
}
}
}catch(Exception e){
return false;
}
return true;
}
protected void onProgressUpdate(Integer... values){
progressDialog.setMessage("Download"+values[0]+"%");
}
protected void onPostExecute(Boolean result){
progressDialog.dismiss();
if(result){
Toast.makeText(context,"Download succeeded",Toast.LENGTH_SHORT).show();
}else{
Toast.makeText(context,"Download failed",Toast.LENGTH_SHORT).show();
}
}
}
如果想要启动这个任务,只需要编写以下代码即可:
new DownloadTask().execute();
内存溢出和内存泄漏的区别
内存溢出 out of memory,是指程序在申请内存时,没有足够的内存空间供其使用,出现out of memory;比如申请了一个integer,但给它存了long才能存下的数,那就是内存溢出。
内存泄露 memory leak,是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光。
memory leak会最终会导致out of memory!
内存溢出就是你要求分配的内存超出了系统能给你的,系统不能满足需求,于是产生溢出。