Handle和多线程断点下载

一般来讲服务器只允许同一IP同时开着3到5条线程去下载一个资源。所以客户开着许多线程去下载不一定就很快!
注意:
(1)为进度条设置样式:style="?android:attr/progressBarStyleHorizontal"其中?表示引用
<ProgressBar style="?android:attr/progressBarStyleHorizontal"
    android:layout_width="fill_parent"
    android:layout_height="18dip"
    android:id="@+id/progressBar"  
/>

(2)利用一个TextView而不是EditView来显示下载的百分比

(3)android中用户的输入事件(触摸屏幕,点击按钮……)是由主线程负责处理的,如果在主线程里面一个方法的执行时间很长导致主线程在此段时间内处于繁忙状态,如果在这段时间内用户触发的其他输入事件主线程是无法处理的。一般来说,只要在五秒内,主线程不能处理该输入事件,android系统会认为你的程序无响应,就会出现错误提示框——应用无响应提示框。所以在主线程中执行的工作不可以超过五秒。
解决办法:
耗时的工作应该交给子线程去做。

断点下载的思路:
1 获取网络文件的长度,然后在android客户端生成一个与网络文件长度相等的本地文件
2 开启N条线程下载文件,计算每条线程负责下载的数据量,公式为:int block=文件长度%N==0?文件长度/N:文件长度/N+1

3 开启多条线程分别从网络文件的不同位置下载数据,并从本地文件相同的位置写入数据.要计算每条线程从网络文件的什么位置开始下载数据,到什么位置结束
此过程主要利用的是HttpURLConnection

为下载速度和防止下载意外中断产生不必要的麻烦,在下载时采用多线程断点下载技术。先获取源文件的大小,然后依据线程条数计算出每条线程应下载的数据长度block,再根据线程ID和block计算出每条线程下载的开始和结束端,最后开启多线程进行下载。在下载过程中利用RandomAccessFile类的seek(long position)和writer( )方法将数据写入文件;与此同时不断更新数据库,实时记录每条线程的下载情况。在此过程中利用Handler和ProgressBar显示下载进度。

4 每条线程的下载情况都存放在数据库(如线程id,已下载的长度)
当路径和文件名和文件长度是一致的话,就说明下载的是同一个资源.于是从数据库取出对应的记录,从断点下载

5 在下载过程中不断更新进度条的显示.涉及到的问题:在子线程里更新的数据,不可以在主线程实现对于UI的重绘,分析见下


在handler对象调用方法handler.sendMessage(message)时,查看源码
可知实际调用的是方法sendMessageAtTime(Message message, long uptimeMillis)
此方法里面的message有一个成员target且msg.target = this;这里的this就是此handler对象本身
它的作用:当主线程的消息处理器Looper在处理Message的时候会查看Message的target,就知道把此消息交给哪一个Handle处理

源码如下:
public boolean sendMessageAtTime(Message msg, long uptimeMillis)
    {
        boolean sent = false;
        MessageQueue queue = mQueue;
        if (queue != null) {
            msg.target = this;
            sent = queue.enqueueMessage(msg, uptimeMillis);
        }
        else {
            RuntimeException e = new RuntimeException(
                this + " sendMessageAtTime() called with no mQueue");
               Log.w("Looper", e.getMessage(), e);
        }
        return sent;
}


handler的作用:用于往创建Handler对象所在的线程(此处为主线程)的消息队列发送信息
当消息被发送给主线程的消息队列后是这样处理的:
主线程有一个消息队列,handler.sendMessage(message)就向主线程的消息队列发送了一个消息messageText
主线程还有一个消息处理器(即Looper),当主线程消息队列里不存在消息时,其处于等待状态;一旦有消息进入到消息队列,消息处理器就会被唤醒
唤醒后,消息处理器首先会从消息队列获取消息(即此处的messageText),当主线程的消息处理器Looper在处理Message的时候
会查看Message的target,就知道把此消息交给哪一个Handle处理,然后调用Handler类的方法handleMessage(Message msg)来处理此消息messageText。
即可从messageText里面取得数据进行下载进度的更新!!
消息处理器Looper也运行在主线程,因为它是处理主线程的消息队列,它也就理所当然地应该在主线程中

总结:
问题来源——在子线程里更新的数据,不可以在主线程实现对于UI的重绘!
于是引入了Handler,它的主要作用——用于往创建Handler对象所在的线程(此处为主线程)的消息队列发送信息!
于是将目前要更新的数据存放到了一个Message中,然后发给主线程。
过程:Looper发现有新的Message,当主线程的消息处理器Looper在处理Message的时候会查看Message的target,就知道把此消息交给哪一个Handle处理
最后在Handler类的方法handleMessage(Message msg)里面处理此消息message带过来的数据(即实现下载进度更新)

通俗的描述:
子线程产生一些数据,且要依据这些数据完成一些功能(比如这里的实现UI控件更新),但是遗憾的是:此功能只能在主线程里面完成。
这个时候借用了主线程的工具(Handler),然后将这些数据封装到Message里面,由Handler发送给主线程。
尤其要注意:这个工具有个很重要的功能——可以处理Message!当主线程的消息处理器Looper在处理Message的时候会查看Message的target,就知道把此消息交给哪一个Handle处理;于是就在主线程里面利用此handler处理message!!!!!!!

 

以上是2011年1月5日总结,现将其继续完善如下:

 

主线程(比如MainActivity)创建后就有一个Looper和消息队列,但是其余线程线程(比如普通子线程)没有的!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
Looper是和消息队列绑定的,即与线程绑定的。
Handler用于往创建Handler对象所在的线程(此处为主线程)的消息队列发送信息或者实现Runnable接口的对象(比如子线程有一段代码放(放在了
run()里面)需要主线程去完成就可以这么做)
小结:Handler发送给主线程的东西有两类:Message(静态的)和Runnable(动态的)!!!!!!!!!!!!!!!!!!!!

有一点要特别注意:在Handler将Runnable传递给创建Handler对象所在的线程,然后执行里面的run()方法时并不会重新开启一个新的线程!!!
而就是在创建Handler对象所在的线程里调用run()方法!!!!!!!!!!!!!!!!!!!!!!!!!

 

总结:
其实这里的Looper就是上面提到的消息处理器!!!!!!!!!!专门处理线程的消息队列
根据以上的所有分析可知整个流程:Handler对象将Message或者Runnable发送给了创建Handler对象所在的线程,然后其被放置在了主线程的消息队列里面!
当有消息队列里有消息的时候,Looper对象被激活开始工作.
然后分析消息队列里最前面一个:
第一:若是Message则Looper将其广播出去,相应的Handler对象就会调用handleMessage()函数来处理消息
这一句是高焕堂说的.与我的理解差不多:Looper对象会取出Message对象里的target,就知道了是哪个Handler对象发送过来的,然后调用此Handler对象里的handleMessage()方法处理消息
第二:要是Runnable则在创建Handler对象所在的线程里面执行run()方法


这是一种异步消息处理机制,Handler对象发送消息或者Runnable后就返回了.
至于这个消息什么时候被执行,完全取决于Looper处理对消息队列的处理
而且Handler可以决定Looper对象处理Runnable时,将其延迟多久才执行.
这就是IPC(Inter-Process Conmmunication)

 

张凌华课程(Handler-不可缺少的异步)笔记:
Handle主要用于进行线程间通信
Handle衔接了Message(Message存放在MessageQueue中)和Looper这两大部分

Handle主要方法
(1)sendMessage (Message msg)
(2)post (Runnable r)
注意:post(Runnable r)最终调用的还是sendMessage (Message msg)所以只有一个消息队列!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

实验一:
一个Handle实例只能接受到自己发出的message.
若在一个MainActivity里定义了两个Handle1和Handle2,那么在子线程中Handle1发出消息
在MainActivity中只有Handle1收到消息Handle2收不到.这就是说虽然只有一个消息队列但是不同Handle发送和接收消息是彼此独立的.
老师说:这主要是Message中的what进行标记的,这样唯一标记了Message.但是这样有些牵强.
真正实现:同一个消息队列但是不同Handle发送和接收消息是彼此独立,是这样的:
(1)
在handler对象调用方法handler.sendMessage(message)时,查看源码
可知实际调用的是方法sendMessageAtTime(Message message, long uptimeMillis)
此方法里面的message有一个成员target且msg.target = this;这里的this就是此handler对象本身
它的作用:当主线程的消息处理器Looper在处理Message的时候会查看Message的target,就知道把此消息交给哪一个Handle处理
源码如下:
public boolean sendMessageAtTime(Message msg, long uptimeMillis)
    {
        boolean sent = false;
        MessageQueue queue = mQueue;
        if (queue != null) {
            msg.target = this;
            sent = queue.enqueueMessage(msg, uptimeMillis);
        }
        else {
            RuntimeException e = new RuntimeException(
                this + " sendMessageAtTime() called with no mQueue");
            Log.w("Looper", e.getMessage(), e);
        }
        return sent;
}
这样就实现了:哪个Handle发送的消息交给哪个Handle处理
(2)主要Message中的what是作为Message的唯一标示.因为同一个Handle可以收到好几个Message,而且每个Message
代表了不同的含义.所以Handle可以通过switch(message.what)对不同的消息,做出不同的响应
这样就实现了:Handle对不同的消息做出不同的处理

综合这两步实现:同一个消息队列但是不同Handle发送和接收消息是彼此独立

Looper的实现
因为Looper是一个死循环.while(true)不断监听消息队列,但是Looper不是额外的线程,因为Looper是由系统来控制的

Handle不建立新的线程


实验二:
我们在子线程中新建Handle并且发送消息,实验结果是:报错!!
因为在普通的子线程中是没有Looper的.如果非要这么做的话可以用public class HandlerThread代替普通的子线程.
因为HandlerThread在普通子线程的基础上增加了Looper和一些Handle的特性

实验三:
此实验实际使用价值不大,只为验证技术
在子线程中调用Looper的静态方法getMainLooper得到主线程的Looper,且以此为参数新建Handle
即Handle testHandle=new Handle(Looper.getMainLooper);
这样建立的Handle也可以实现消息的处理

实验四:
此实验实际使用价值不大,只为验证技术
可以在子线程中新建一个自己的Looper,也可实现消息的处理


总结:
1 Handle实例与消息处理是关联的,发送和接收要匹配
  即哪一个Handle发送消息就要用哪一个Handle来处理消息,其余的Handle是无法接收和处理消息Message的,即便这两个Handle在同一个主线程中,如实验一
2 最常用的模式:在子线程中使用的是主线程的消息队列
3 普通子线程是没有消息队列的

你可能感兴趣的:(Handle和多线程断点下载)