http://www.eoeandroid.com/thread-55761-1-1.html
三,如何通过Handler或者多线程来解决某操作执行时间过程的问题。(update on Jan.19)
结合上面的分析,我们知道问题似乎是线程队列中某个操作presentRegionMedia(view,(RegionMediaModel) model, dataChanged);执行时间太长所导致的界面无响应。因此比较典型的做法当然是控制线程队列。
在这里我们不得不提一下Handler .
Handler在Android中是什么样的作用和地位呢?
1,线程之间消息传递,通过sendMessage方法。我们通常用来后台子线程向主线程传递消息,主线程接到通知之后做更新界面等操作。
2,通过管理消息队列(MessageQueue)来安排计划任务。这个常常会被人忽略,很多书上也没有提到这个作用。
Handler这个单词中文意思是管理者,处理者的意思。通过这个意思顾名思义,我们知道这个对象就是个操作对象。那么要操作谁呢?
当然是消息队列(MessageQueue)。Android消息队列类似于Win32队列设计。都是采用线性结构,先进先出。其实在智能手机平台很久以前就用这种消息结构了。比如Palm,只不过Palm是整个进程共享一个消息队列,而Android是线程为单位的队列罢了。
那么是否每个线程或者子线程都有消息队列呢?
很遗憾,不是的,也没有必要。在Android中,只有使用了Looper的线程才有消息队列。当然如果你要简单建立一个有消息队列的线程也很方便,直接使用HandlerThread即可,这个类继承于Thread类。怎么用我就不多说了吧。你懂的!
Handler有两种方式来操作消息队列。
一种是通过sendMessage(Message)方法,发送消息体
另一种是通过post(Runnable)方法,发送Runnable对象。
注意:这点请注意,虽然发送方法含参不同,但他们使用的是同一个消息队列。
我记得Mars的视频教程上说有两个队列,一个是消息队列,一个是线程队列。
这种说法是错误的。事实上只有一个消息队列,没有所谓的线程队列。当然了,post(Runnable)也没有启动新的线程,仍然是在当前线程。
注意:还有一种说法
,说Handler对象在主线程,这种说法也是错误的,准确的说是在产生他的线程中。虽然常常我们是在主线程产生他的。
那么我们要在Android建立多线程程序该如何做呢?很简单,就是Java的多线程方式。要么实现Runnable接口,要么继承Thread类。
关于线程同步,线程锁定,线程异步,线程池这些概念也是一样的。我就不累述了。
好了,经过一点儿简单的介绍,我们有了一些Handler的基础,现在开始回到我们的问题开始来分析
:
mHandler.post(new Runnable() {
public void run() {
try {
presentRegionMedia(view, (RegionMediaModel) model, dataChanged);
} catch(OMADRMException e) {
Log.e(TAG, e.getMessage(), e);
Toast.makeText(mContext,
mContext.getString(R.string.insufficient_drm_rights),
Toast.LENGTH_SHORT).show();
} catch (IOExceptione){
Log.e(TAG, e.getMessage(), e);
Toast.makeText(mContext,
mContext.getString(R.string.insufficient_drm_rights),
Toast.LENGTH_SHORT).show();
}
}
从上面这段代码中,我们可以看出,在做播放器控制按钮(比如播放,暂停,停止)等操作的时候,是通过Handler.post(Runnable)来放到消息队列中,排序来处理。那么之所以这里出现了无响应,很有可能是因为某一项控制操作太耗时或者耗资源。这时候又接收到新的要处理的消息,就会处理不过来了。
因此我试图让队列中同时只有一个控制播放器按钮的任务在。
我对代码做了如下改动:
Runnabler = new Runnable(){
public void run() {
try {
presentRegionMedia(view, (RegionMediaModel) model, dataChanged);
} catch(OMADRMException e) {
Log.e(TAG, e.getMessage(), e);
Toast.makeText(mContext,
mContext.getString(R.string.insufficient_drm_rights),
Toast.LENGTH_SHORT).show();
} catch (IOExceptione){
Log.e(TAG, e.getMessage(), e);
Toast.makeText(mContext,
mContext.getString(R.string.insufficient_drm_rights),
Toast.LENGTH_SHORT).show();
}
}
mHandler.removeCallbacks(r);
mHandler.post(r) ;
代码慢慢看,思路很简单:其实就是在post Runnable之前先清除队列中已存的相同Runnable实例。
这样可以保证同时队列中只有一个操作在处理。
很遗憾,不生效。:(,改动之后,问题依然存在,欲哭无泪。
再来,我将整个模式改为message再试试,核心代码如下:
if(mHandler.hasMessages(MEDIA_PLAY_WHAT_MESSAGEFLAG))
{
return;
}
Message msg= mHandler.obtainMessage();
msg.what =this.MEDIA_PLAY_WHAT_MESSAGEFLAG;
msg.obj =mMeidaPlayMessageObj;
mHandler.sendMessageDelayed(msg, 1000);
代码慢慢看,思路也很简单,通过发消息的方式,先检测如果有相关消息队列,就直接跳出函数,不做任何处理,否则延迟一秒后再向队列发送一条消息。为何我用了1秒这个这么长的时间呢,因为这么长时间如果都处理不了,那就不是压力测试的问题了,而是方法本身的问题了,这也是通过排除法来试图排除是因为点击屏幕过快产生的问题。
编译,再试,很不辛,又不生效,不幸被我猜中了。仰望苍天!
现在问题很明显了:不是压力测试时候点击过快导致的ANR,而是某些方法本身有问题。
通过之前我们的日志
-----pid 2922 at 2011-01-13 13:51:07 -----
Cmd line: com.android.mms
DALVIK THREADS:
"main" prio=5 tid=1 NATIVE
| group="main" sCount=1dsCount=0 s=N obj=0x4001d8d0 self=0xccc8
| sysTid=2922 nice=0 sched=0/0cgrp=default handle=-1345017808
| schedstat=( 3497492306 1531289792310358 )
at android.media.MediaPlayer._release(NativeMethod)
atandroid.media.MediaPlayer.release(MediaPlayer.java:1206)
atandroid.widget.VideoView.stopPlayback(VideoView.java:196)
atcom.android.mms.ui.SlideView.stopVideo(SlideView.java:640)
很容易就知道了问题出在每次执行完了MediaPlayer.stop()方法调用之后会调用release()来释放播放器资源。而这个方法中又死在了_release()方法上。这是一个Native方法。因此,真相大白,问题是在Framework层的MediaPlayer调用的Native方法_release()上
。
(打的手酸了,休息一下,大家对下面的内容还感兴趣么?)