zoukankan      html  css  js  c++  java
  • 赵雅智_android多线程下载带进度条

    progressBar说明

    在某些操作的进度中的可视指示器,为用户呈现操作的进度,还它有一个次要的进度条,用来显示中间进度,如在流媒体播放的缓冲区的进度。

    一个进度条也可不确定其进度。在不确定模式下,进度条显示循环动画。这样的模式经常使用于应用程序使用任务的长度是未知的。

    XML重要属性

    android:progressBarStyle:默认进度条样式

    android:progressBarStyleHorizontal:水平样式

    progressBar重要方法

    getMax():返回这个进度条的范围的上限

    getProgress():返回进度

    getSecondaryProgress():返回次要进度

    incrementProgressBy(int diff):指定添加的进度

    isIndeterminate():指示进度条是否在不确定模式下

    setIndeterminate(boolean indeterminate):设置不确定模式下

    setVisibility(int v):设置该进度条是否可视

    progressBar重要事件

    onSizeChanged(int w, int h, int oldw, int oldh):当进度值改变时引发此事件

    项目实现步骤分析

    1.给进度调设置最大值

    2.不管哪一个线程下载都要记录进度条当前位置,并设置值

    3.给textview设置显示当前下载值

    4.创建暂时文件把曾经下载好的位置记录起来

    5.当线程下载完后删除暂时保存进度条位置的文件

    主要代码:

    加入权限

    <uses-permission android:name="android.permission.INTERNET"/>

    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

    <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>

    布局文件 

     

    <?xml version="1.0" encoding="utf-8"?>
    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="com.example.android_download"
        android:versionCode="1"
        android:versionName="1.0" >
    
        <uses-sdk
            android:minSdkVersion="8"
            android:targetSdkVersion="17" />
    	 <uses-permission android:name="android.permission.INTERNET"/>
         <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
         <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>
        <application
            android:allowBackup="true"
            android:icon="@drawable/ic_launcher"
            android:label="@string/app_name"
            android:theme="@style/AppTheme" >
            <activity
                android:name="com.example.android_download.MainActivity"
                android:label="@string/app_name" >
                <intent-filter>
                    <action android:name="android.intent.action.MAIN" />
    
                    <category android:name="android.intent.category.LAUNCHER" />
                </intent-filter>
            </activity>
        </application>
    
    </manifest>

    StreamTools工具

    android线程下载的工具一样http://blog.csdn.net/zhaoyazhi2129/article/details/27189465

    Activity主要代码

    package com.example.android_download;
    
    import java.io.File;
    import java.io.FileInputStream;
    import java.io.FileNotFoundException;
    import java.io.InputStream;
    import java.io.RandomAccessFile;
    import java.net.HttpURLConnection;
    import java.net.URL;
    
    import android.app.Activity;
    import android.os.Bundle;
    import android.os.Environment;
    import android.text.TextUtils;
    import android.view.View;
    import android.widget.EditText;
    import android.widget.ProgressBar;
    import android.widget.TextView;
    import android.widget.Toast;
    
    import com.example.util.StreamTools;
    
    public class MainActivity extends Activity {
    	private int threadNum = 3;// 线程开启的数量
    	private int threadRunning = 3;// 正在执行的线程
    	private TextView tv_pb;
    	private EditText et_url;
    	private ProgressBar pb_download;
    	private int currentPb;//当前值
    	
    
    	@Override
    	protected void onCreate(Bundle savedInstanceState) {
    		super.onCreate(savedInstanceState);
    		setContentView(R.layout.activity_main);
    		findView();
    
    		File sdDir = Environment.getExternalStorageDirectory();
    		File pdFile = new File(sdDir, "pb.txt");
    		InputStream is = null;
    		try {
    			// 推断文件是否存在
    			if (pdFile.exists()) {
    				is = new FileInputStream(pdFile);
    			}
    
    		} catch (FileNotFoundException e) {
    			// TODO Auto-generated catch block
    			e.printStackTrace();
    		}
    		if (is != null) {
    			String value = StreamTools.streamToStr(is);
    			//拆分
    			String arr[] = value.split(";");
    
    			pb_download.setMax(Integer.valueOf(arr[0]));// 最大值
    			currentPb = Integer.valueOf(arr[1]);// 当前值
    			pb_download.setProgress(currentPb);
    			tv_pb.setText("当前的进度是:"+arr[2]);
    		}
    	}
    
    	/**
     * 
     */
    	public void findView() {
    		tv_pb = (TextView) findViewById(R.id.tv_pb);
    		et_url = (EditText) findViewById(R.id.et_url);
    		pb_download = (ProgressBar) findViewById(R.id.pb_download);
    	}
    
    	// 下载文件(得到server端的文件大小 )
    	public void downLoadFile(View v) {
    		// 获取下载路径
    		final String spec = et_url.getText().toString();
    		if (TextUtils.isEmpty(spec)) {
    			Toast.makeText(this, "下载地址不能为空", 0).show();
    		} else {
    			new Thread() {
    				@Override
    				public void run() {
    					// 訪问网络地址
    					try {
    						// 依据下载的地址构建url对象
    						URL url = new URL(spec);
    						// 通过URL对象的openConnection()方法打开连接,返回一个连接对象
    						HttpURLConnection httpURLConnection = (HttpURLConnection) url
    								.openConnection();
    						// 设置请求头
    						httpURLConnection.setRequestMethod("GET");
    						httpURLConnection.setConnectTimeout(5000);
    						httpURLConnection.setReadTimeout(5000);
    
    						// 推断是否响应成功
    						if (httpURLConnection.getResponseCode() == 200) {
    							/**
    							 * 第一步:得到server下载文件的大小,然后在本地设置一个暂时文件和server端文件大小一致
    							 */
    							// 获取文件长度
    							int fileLength = httpURLConnection
    									.getContentLength();
    							//给进度条设置最大值
    							pb_download.setMax(fileLength);
    							//推断SD卡是否可用
    							if(Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)){
    								// 外部存储设备的路径
    								File sdFile = Environment
    										.getExternalStorageDirectory();
    								//获取文件名
    								String fileName = spec.substring(spec.lastIndexOf("/")+1);
    								// 随机訪问文件的读取与写入RandomAccessFile(file, mode)
    								RandomAccessFile accessFile = new RandomAccessFile(
    										new File(sdFile, fileName), "rwd");
    								// 设置暂时文件与server文件大小一致
    								accessFile.setLength(fileLength);
    								// 关闭暂时文件
    								accessFile.close();
    
    								/**
    								 * 第二步:计算出每一个线程下载的大小(開始位置,结束位置)
    								 */
    								// 计算出每一个线程下载的大小
    								int threadSize = fileLength / threadNum;
    								// for循环,计算出每一个线程的開始和结束位置
    								for (int threadId = 1; threadId <= 3; threadId++) {
    									int startIndex = (threadId - 1) * threadSize;// 開始位置
    									int endIndex = threadId * threadSize - 1;// 结束位置
    									if (threadId == threadNum) {// 最后一个 线程
    										endIndex = fileLength - 1;
    									}
    
    									System.out.println("当前线程--" + threadId
    											+ "-----開始位置" + startIndex + "----结束位置"
    											+ endIndex + "-----线程大小" + threadSize);
    									/**
    									 * 第三步:每创建好一次就要开启线程下载
    									 */
    									new DownLoadThread(threadId, startIndex,
    											endIndex, spec,fileName).start();
    
    								}
    							}else {
    								runOnUiThread(new Runnable() {
    									
    									@Override
    									public void run() {
    										// TODO Auto-generated method stub
    										Toast.makeText(MainActivity.this, "SD卡不存在", 1).show();
    									}
    								});
    							}
    						} else {
    							runOnUiThread(new Runnable() {
    								
    								@Override
    								public void run() {
    									// TODO Auto-generated method stub
    									Toast.makeText(MainActivity.this, "server端返回错误", 1).show();
    								}
    							});
    						}
    
    					} catch (Exception e) {
    						// TODO Auto-generated catch block
    						e.printStackTrace();
    					}
    				}
    			}.start();
    
    		}
    	}
    
    	/**
    	 * 每创建好一次就要开启线程下载
    	 * 
    	 * @author zhaoyazhi
    	 * 
    	 */
    	class DownLoadThread extends Thread {
    		// 成员变量
    		private int threadId;
    		private int startIndex;
    		private int endIndex;
    		private String path;
    		private String fileName;
    
    		File sdFile = Environment.getExternalStorageDirectory();
    
    		/**
    		 * 
    		 * @param threadId
    		 *            线程的序号
    		 * @param startIndex
    		 *            线程下载開始位置
    		 * @param endIndex
    		 *            线程下载结束位置
    		 * @param path
    		 *            线程下载保存文件的路径
    		 */
    		public DownLoadThread(int threadId, int startIndex, int endIndex,
    				String path,String fileName) {
    			super();
    			this.threadId = threadId;
    			this.startIndex = startIndex;
    			this.endIndex = endIndex;
    			this.path = path;
    			this.fileName = fileName;
    		}
    
    		@Override
    		public void run() {
    			// 能够通过每一个线程去下载文件
    			try {
    				/**
    				 * 第四步:从本地文件上读取已经下载文件的開始位置
    				 */
    				File recordFile = new File(sdFile, threadId + ".txt");
    				if (recordFile.exists()) {
    					// 读取文件
    					InputStream is = new FileInputStream(recordFile);
    					// 利用工具类转换
    					String value = StreamTools.streamToStr(is);
    					// 获取记录的位置
    					int recordIndex = Integer.parseInt(value);
    					// 把记录的位置付给開始位置
    					startIndex = recordIndex;
    				}
    				// 通过path对象构造URL 对象
    				URL url = new URL(path);
    				// 通过URL对象openConnection
    				HttpURLConnection httpURLConnection = (HttpURLConnection) url
    						.openConnection();
    				// 设置请求头
    				httpURLConnection.setRequestMethod("GET");
    				httpURLConnection.setConnectTimeout(5000);
    				// 设置下载文件的開始位置和结束位置
    				httpURLConnection.setRequestProperty("Range", "bytes="
    						+ startIndex + "-" + endIndex);
    				// 获取状态码
    				int code = httpURLConnection.getResponseCode();
    				// System.out.println(code);
    				// 推断是否成功 仅仅要设置"Range"头,返回的状态码就是206
    				if (code == 206) {
    					// 获取每一个线程返回的流对象
    					InputStream is = httpURLConnection.getInputStream();
    					// 创建随机訪问的对象
    					RandomAccessFile accessFile = new RandomAccessFile(
    							new File(sdFile, fileName), "rwd");
    					// 指定開始位置
    					accessFile.seek(startIndex);
    					// 定义读取的长度
    					int len = 0;
    					// 定义缓冲区
    					byte buffer[] = new byte[1024*1024];
    					int total = 0;
    					// 循环读取
    					while ((len = is.read(buffer)) != -1) {
    						System.out.println("当前线程--" + threadId
    								+ "-----当前下载的位置是" + (startIndex + total));
    						// 保存每一个线程的下载位置
    						RandomAccessFile threadFile = new RandomAccessFile(
    								new File(sdFile, threadId + ".txt"), "rwd");
    						// 记录每次下载位置
    						threadFile.writeBytes((startIndex + total) + "");
    						threadFile.close();
    						accessFile.write(buffer, 0, len);
    						total += len;// 已经下载大小
    						
    						/**
    						 * 当程序有多个线程訪问一个变量。能够用synchronized解决
    						 */
    						synchronized (MainActivity.this) {
    							//进度条当前进度
    							currentPb += len;
    							pb_download.setProgress(currentPb);
    							final String percent= currentPb*100l/pb_download.getMax()+"%";
    							runOnUiThread(new Runnable() {
    								
    								@Override
    								public void run() {
    									//计算百分比操作
    									
    									tv_pb.setText("当前的进度是:"+percent);
    								}
    							});
    							//创建保存当前 进度和百分比
    							RandomAccessFile pbFile = new RandomAccessFile(
    									new File(sdFile,  "pb.txt"), "rwd");
    							pbFile.writeBytes(pb_download.getMax()+";"+currentPb+";"+percent);
    							pbFile.close();
    						}
    						
    					}
    					accessFile.close();
    					is.close();
    
    					runOnUiThread(new Runnable() {
    						
    						@Override
    						public void run() {
    							// TODO Auto-generated method stub
    							Toast.makeText(MainActivity.this, "当前线程" + threadId + "---完成下载", 1).show();
    						}
    					});
    					/**
    					 * 第五步:当你的n个线程都完成下载 的时候我才进行删除记录下载位置的缓存文件
    					 */
    					deleteRecordFile();
    				} else {
    					runOnUiThread(new Runnable() {
    						
    						@Override
    						public void run() {
    							// TODO Auto-generated method stub
    							Toast.makeText(MainActivity.this, "server端返回错误", 1).show();
    						}
    					});
    				}
    				// 设置你下载文件
    			} catch (Exception e) {
    				e.printStackTrace();
    			}
    
    		}
    
    	}
    
    	/**
    	 * synchronized避免线程同步 下载完删除存储文件下载位置的暂时文件
    	 */
    	public synchronized void deleteRecordFile() {
    		// 外部存储设备的路径
    		File sdFile = Environment.getExternalStorageDirectory();
    		// 线程下载完就减去
    		threadRunning--;
    		// 当没有正在执行的线程
    		if (threadRunning == 0) {
    			for (int i = 1; i <= 3; i++) {
    				File recordFile = (new File(sdFile, i + ".txt"));
    				if (recordFile.exists()) {
    					recordFile.delete();
    				}
    				File pbFile = (new File(sdFile, "pb.txt"));
    				if (pbFile.exists()) {
    					pbFile.delete();
    				}
    			}
    			
    		}
    	}
    
    	
    }
    

    补充知识点

    不用在给进度条设置值的时候考虑同步。由于android定义progressBar的时候已经 设置了同步

     

    2.当程序有多个线程訪问一个变量,能够用synchronized解决

    synchronized (MainActivity.this) {
    							//进度条当前进度
    							currentPb += len;
    							pb_download.setProgress(currentPb);
    							
    							runOnUiThread(new Runnable() {
    								
    								@Override
    								public void run() {
    									tv_pb.setText("当前的进度是:"+currentPb*100/pb_download.getMax()+"%");
    								}
    							});


    执行结果

    1.当文件下载时,进度条显示和当前进度显示

     

    2.当又一次载入时继续上次下载

     

    3.当文件进行下载时,暂时存储下载文件位置的pb.txt被创建

     

    4.当线程完成下载后,在主线程Toast完成下载

     

    5.当下在完毕后进度条走满,当前进度为100%

     

    6.下载完后,暂时文件销毁



    源码下载地址:http://download.csdn.net/detail/zhaoyazhi2129/7406731

    转发请标明出处:http://blog.csdn.net/zhaoyazhi2129/article/details/27192169

  • 相关阅读:
    重新学习MySQL数据库开篇:数据库的前世今生
    Java网络编程和NIO详解9:基于NIO的网络编程框架Netty
    测试小鲸鱼
    golang
    技术篇
    请求报文和响应报文
    编程
    测试
    PHP
    centos7.1 从源码升级安装Python3.5.2
  • 原文地址:https://www.cnblogs.com/gavanwanggw/p/6696550.html
Copyright © 2011-2022 走看看