结合上面的分析,我们知道问题似乎是线程队列中某个操作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 (IOException e){
Log.e(TAG, e.getMessage(), e);
Toast.makeText(mContext,
mContext.getString(R.string.insufficient_drm_rights),
Toast.LENGTH_SHORT).show();
}
}
从上面这段代码中,我们可以看出,在做播放器控制按钮(比如播放,暂停,停止)等操作的时候 , 是通过Handler.post(Runnable)来放到消息队列中 , 排序来处理 。 那么之所以这里出现了无响应,很有可能是因为某一项控制操作太耗时或者耗资源 。 这时候又接收到新的要处理的消息,就会处理不过来了 。 因此我试图让队列中同时只有一个控制播放器按钮的任务在 。 我对代码做了如下改动:
Runnable r = 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 (IOException e){
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=1 dsCount=0 s=N obj=0x4001d8d0 self=0xccc8
| sysTid=2922 nice=0 sched=0/0 cgrp=default handle=-1345017808
| schedstat=( 3497492306 15312897923 10358 )
at android.media.MediaPlayer._release(Native Method)
at android.media.MediaPlayer.release(MediaPlayer.java:1206)
at android.widget.VideoView.stopPlayback(VideoView.java:196)
at com.android.mms.ui.SlideView.stopVideo(SlideView.java:640)
很容易就知道了问题出在每次执行完了MediaPlayer.stop()方法调用之后会调用release()来释放播放器资源 。 而这个方法中又死在了_release()方法上 。 这是一个Native方法 。
因此,真相大白 ,问题是在Framework 层的MediaPlayer调用的Native方法_release()上 。