安卓客户端的多线程断点下载(SharedPreferences版)
来源:互联网 发布:基尼系数的算法 编辑:程序博客网 时间:2024/04/29 16:46
题记:从百度百科上面我们知道,SharedPreferences是不支持多线程的,但是这次使用SharedPreferences实现了多线程断点下载。点解?
服务器端:
使用的是tomcat服务器,在C:\apache-tomcat-7.0.59\webapps\ROOT目录下存放pp.zip文件(这个文件随便,但是要跟代码中url的path后面的参数对应)开启tomcat服务器,先用浏览器访问下,看浏览器能不能提示下载或者直接在浏览器界面显示。可以的话再执行下面的。
布局文件如下:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" android:orientation="vertical" > <TextView android:id="@+id/tv_content" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="25dp" android:text="pp.zip文件,快来下载吧" /> <EditText android:id="@+id/et_threadtotal" android:layout_width="wrap_content" android:hint="请输入线程数量" android:layout_height="wrap_content" /> <Button android:onClick="click" android:id="@+id/btn" android:layout_margin="10dp" android:text="点我下载" android:layout_width="match_parent" android:layout_height="wrap_content"/> <ProgressBar android:id="@+id/pb" android:max="100" style="?android:attr/progressBarStyleHorizontal" android:layout_width="match_parent" android:layout_height="15dp" /> <TextView android:id="@+id/tv_number" android:layout_margin="5dp" android:text="显示进度" android:layout_width="match_parent" android:layout_height="wrap_content"/></LinearLayout >
MainActivity代码如下:
package com.example.downloader;import java.io.BufferedInputStream;import java.io.File;import java.io.RandomAccessFile;import java.net.HttpURLConnection;import java.net.URL;import android.os.Bundle;import android.os.Environment;import android.os.Handler;import android.os.Looper;import android.os.Message;import android.app.Activity;import android.content.SharedPreferences;import android.content.SharedPreferences.Editor;import android.text.TextUtils;import android.view.View;import android.widget.Button;import android.widget.EditText;import android.widget.ProgressBar;import android.widget.TextView;import android.widget.Toast;public class MainActivity extends Activity { private EditText et; private static long length; private static File file; private static String path; private static ProgressBar pb; private static long reallength; private static SharedPreferences sp; private static TextView tv_number; private static long reallengthrecord=0; private static int number; private static Button btn; private static Handler handler=new Handler(){ public void handleMessage(android.os.Message msg) { switch (msg.what) { case 0://reallengthrecord是从sp中获取的,让进度条续加,如果sp文件没能获取到这个值,默认就是0; long data=(Long) msg.obj; int result=(int)(data*100/length);//进度条的最大值已经在布局文件中设置为100了//这里有个坑,setProgress()和setText()里面的参数一定要匹配,要传字符串的却传int值,错误无提示的哦,只会运行时报Resources$NotFoundException: String resource ID #0x0 pb.setProgress(result); if(pb.getProgress()==100){//进度到了100%,按钮设置为不可点击 btn.setEnabled(false); } tv_number.setText(result+"%"); break; default: break; } }; }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); et = (EditText)findViewById(R.id.et_threadtotal); btn = (Button)findViewById(R.id.btn); pb = (ProgressBar)findViewById(R.id.pb); tv_number = (TextView)findViewById(R.id.tv_number);//这里路径写死,一访问服务器,就可以获得下载 path = "http://192.168.1.109:8080/pp.zip"; new Thread(){ public void run() { try { URL url=new URL(path); HttpURLConnection conn=(HttpURLConnection) url.openConnection(); conn.setRequestMethod("GET"); length = conn.getContentLength();//文件名可以从path中获得,访问网络获取文件的大小,第一次的话创建一个与目标文件大小名称一致的空文件 String file_name=path.substring(path.lastIndexOf("/")+1); file = new File(Environment.getExternalStorageDirectory(),file_name); if(!file.exists()){ RandomAccessFile rf=new RandomAccessFile(file, "rwd"); rf.setLength((long) length);//子线程中土司可以使用Looper.prepare()和Looper.loop();子线程土司的数量不要超过50 Looper.prepare(); Toast.makeText(MainActivity.this, "创建文件成功", 0).show(); Looper.loop(); } } catch (Exception e) { Looper.prepare(); Toast.makeText(MainActivity.this, "创建文件失败", 0).show(); Looper.loop(); } }; }.start();//使用sp回显,线程数量,如果用户不输入,默认为空,吐司提示需要输入线程//生成的sp文件名是跟下载的文件名是对应的 String str=path.substring(path.lastIndexOf("/")+1,path.lastIndexOf(".")); sp=getSharedPreferences(str, 0); String threadcount=sp.getString("number",""); int progress_result=sp.getInt("progress_result", 0); reallengthrecord=sp.getLong("reallength", 0); tv_number.setText(progress_result+"%"); pb.setProgress(progress_result); et.setText(threadcount); } public void click(View v){//在做下载前,先判断SD卡是否处于挂载,然后再判断sd卡剩余容量,至少给用户留出100M的空间 String status=Environment.getExternalStorageState(); if(Environment.MEDIA_MOUNTED.equals(status)){ long freespace=Environment.getExternalStorageDirectory().getFreeSpace(); if(freespace>(file.length()+1024*1024*100)){ String threadcount=et.getText().toString().trim(); if(TextUtils.isEmpty(threadcount)){ Toast.makeText(MainActivity.this, "需要输入线程数量呢", 0).show(); return; }else{ number = Integer.parseInt(et.getText().toString().trim()); }//这里每个线程下载数量分配得很好(length/number)+1,这种情况见于(总大小15分4个线程来搞,//每个大小如果按照length/number来算,3,3,3,6,显然不好,应该是4,4,4,3) long blockSize=length%number==0?length/number:((length/number)+1); System.out.println(blockSize); for (int threadId = 0; threadId < number; threadId++) { long startIndex = threadId*blockSize; long endIndex; if(threadId==number-1){ endIndex = length-1; }else{ endIndex = (threadId+1)*blockSize-1; }//启动线程去下载,这样写的好处是让线程ID,起始结束位置跟线程捆绑,使用多线程断点下载必须这样,如果只是多线程下载,可以不用这样 new DownloadThread(threadId, startIndex, endIndex).start(); } }else{//内存不足,会有提示 Toast.makeText(getApplicationContext(), "sd卡的内存不足", 0).show(); } } } private static class DownloadThread extends Thread{ private int threadId; private long startIndex; private long endIndex; private long curlength; private File file2; public DownloadThread(int threadId, long startIndex, long endIndex) { this.threadId = threadId; this.startIndex = startIndex; this.endIndex = endIndex;//this.curlength = startIndex;这行代码的关键作用不解释 this.curlength = startIndex; } @Override public void run() { System.out.println("第 " + threadId+" 号 线程开始下载了 , 下载 : " + startIndex+"~"+endIndex); try{ URL url = new URL(path); HttpURLConnection conn =(HttpURLConnection) url.openConnection();//判断sp文件是否存在,存在就读取里面保存的每个线程跑了的进度,请求数据以该进度开始,以及设置RandomAccessFile开始写的位置 String str2="/data/data/com.example.downloader/shared_prefs"; file2 = new File(str2,path.substring(path.lastIndexOf("/")+1 ,path.lastIndexOf("."))+".xml"); RandomAccessFile rf=new RandomAccessFile(file, "rwd"); if(file2!=null&&file2.length()>0){ long recordlength=sp.getLong(threadId+"curlength",0); curlength=recordlength; conn.setRequestProperty("Range", "bytes="+curlength+"-"+endIndex); System.out.println("bytes="+curlength+"-"+endIndex+"aaaaaaaaa"); rf.seek(curlength); }else{//sp文件不存在,就从默认的开始位置请求数据 conn.setRequestProperty("Range", "bytes="+startIndex+"-"+endIndex); rf.seek(startIndex); } int code = conn.getResponseCode(); System.out.println("code :" + code);//请求的数据Range如果范围越界或者不存在,则请求码为416,206表示请求部分数据成功 if(code==206){ BufferedInputStream in = new BufferedInputStream(conn.getInputStream()); int len=0; byte[] buf = new byte[1024*1024]; while((len=in.read(buf))>0){ rf.write(buf, 0, len);//curlength是该内部类 的成员变量,reallength是MainActivity的成员变量//curlength是该内部类 的成员变量可用于统计每个线程跑的进度//reallength可以记录所有的线程跑得进度,但不能确定是否为实际下载的进度(如果不断点那就是实际下载的进度,断点就不是实际下载的进度)//程序运行一次,reallength就会记录,如果关闭再打开,reallength又重新开始记录,所以需要我们自己给它续加进度,才是文件下载的最最真实的进度 curlength+=len; reallength+=len;//sp文件的生成也是很即时的,在while循环中只要本地文件一写入数据,这里sp文件就可以即时生成,保存数据,数据即时更新(有时生成的sp文件还有bak备份文件)//reallengthrecord是MainActivity的成员变量,这是续加续加续加的问题 int progress_result=(int) (((reallength+reallengthrecord)*100)/length); Editor edit=sp.edit(); edit.putInt("progress_result",progress_result); edit.putLong(threadId+"curlength",curlength); edit.putString("number", number+""); edit.putLong("reallength",reallength+reallengthrecord); edit.commit();//handler需要传入reallength+reallengthrecord(续加,第一次没有这个, // 就是0,关掉程序再打开界面续传就要加上sp文件里面保存的之前的下载的真实进度) Message msg=Message.obtain(); msg.obj=reallength+reallengthrecord; msg.what=0; handler.sendMessage(msg); }//关流是必须的,关流是必须的,关流是必须的,还有rf的模式设置为rwd,不然下载到99%就不动了,那就悲剧了 in.close(); rf.close(); } }catch(Exception e){ System.out.println("下载失败"); }//线程下载完就该把sp文件给删了 System.out.println("第 " + threadId+"线程下载完了 ..."); file2.delete(); } } }
界面:
问题1.:我下载1.2G文件的无压力,但是下载2.63G的文件,在创建文件的时候就失败了,难道是随机访问流在创建文件有大小限制?
问题2.:SharedPreferences不支持多线程如何理解,是在哪一种多线程环境下SharedPreferences不支持?
跪求大神指导
源码下载链接:
链接:http://pan.baidu.com/s/1qXSERLU 密码:rw73
1 0
- 安卓客户端的多线程断点下载(SharedPreferences版)
- 安卓系统下的多线程断点下载实现
- 安卓 多任务 多线程 断点 下载
- 安卓系统下的多线程断点下载实现2利用开源框架XUtils
- 多线程断点下载的实现
- javase的多线程断点下载
- 多线程下载-上(断点)
- 多线程下载-下(断点)
- 安卓开发之多线程断点下载(三)
- 安卓开发-多线程常规实现+xUtils-master开源框架实现 断点下载
- 多线程下载(断点下载)java代码
- 多线程下载断点下载
- 多线程下载,断点下载
- 安卓多线程下载
- 安卓-----多线程下载
- 安卓多线程下载
- OSS实现多文件多线程的断点下载(java)
- 多线程下载,以及断点的实现
- Android开发解决加载图片OOM问题(非常全面 兼顾4.0以下系统)(by 星空武哥)
- 海盗分珠宝
- HDU-ACM2018
- git常用命令汇总(命令行模式)
- 视图动画效果
- 安卓客户端的多线程断点下载(SharedPreferences版)
- YOLO训练之标注数据转化XML matlab编程
- 代码风格
- c++基础回顾
- HDU-ACM2019
- 两两交换链表中的节点
- ACE框架[推荐||精品]
- HDU-ACM2020
- 关于matplotlib 记录