赵雅智_java的多线程下载移植到android客户端

根据java的多线程下载,我们将进行对java的多线程下载移植到android客户端。

java多线程在客户端的移植步骤

1.添加用户权限:发送网络请求,sd卡的读写

2.点击按钮触发访问网络请求的时间,必须开启一个线程,在线程中去启动主线程中不能发送网络请求

new Thread(){
	@Override
	public void run() {
	}
}.start();

3.path路径需要指定为sd卡路径

a) 获取sd卡根路径:Environment.getExternalStorageDirectory();

b) 创建临时文件:new File(sd卡目录,创建的文件名);

4.更改所创建的sd目录下的临时文件名称

涉及知识点:

1.杀死程序

在Devices下 找到所开启的模拟器,点击你所创建的项目,右上角有红色 的stop,点击便可杀死程序。如下图

2.Android 更新UI的两种方法——handlerrunOnUiThread()

Android开发过程中,常需要更新界面的UI。而更新UI是要主线程来更新的,即UI线程更新。如果在主线线程之外的线程中直接更新页面显示常会报错。抛出异常:android.view.ViewRoot$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.

只有原始创建这个视图层次(view hierachy)的线程才能修改它的视图(view)

方法一:

Activity.onCreate(Bundle savedInstanceState)中创建一个Handler类的实例, 在这个Handler实例的handleMessage回调函数中调用更新界面显示的函数。

方法二:

利用Activity.runOnUiThread(Runnable)把更新ui的代码创建在Runnable中,然后在需要更新ui时,把这个Runnable对象传给Activity.runOnUiThread(Runnable)。 这样Runnable对像就能在ui程序中被调用。如果当前线程是UI线程,那么行动是立即执行。如果当前线程不是UI线程,操作是发布到事件队列的UI线程

runOnUiThread(new Runnable() {
	@Override
	public void run() {
		Toast.makeText(MainActivity.this, "服务器端返回错误", 1).show();
	}
});

 

3.substring() 

substring() 方法用于提取字符串中介于两个指定下标之间的字符。

1.语法:stringObject.substring(start,stop)

a) start:必须;一个非负的整数,规定要提取的子串的第一个字符在 stringObject 中的位置

b) stop可选。一个非负的整数,比要提取的子串的最后一个字符在 stringObject 中的位置多1。如果省略该参数,那么返回的子串会一直到字符串的结尾。

2.返回值

一个新的字符串,该字符串值包含 stringObject 的一个子字符串,其内容是从 start 处到 stop-1 处的所有字符,其长度为 stop start

3.说明

substring() 方法返回的子串包括 start 处的字符,但不包括 stop 处的字符。

如果参数 start 与 stop 相等,那么该方法返回的就是一个空串(即长度为 的字符串)。如果 start 比 stop 大,那么该方法在提取子串之前会先交换这两个参数。

 

 程序主要代码 

1.添加权限

<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"/>

2.布局文件 

 

<RelativeLayout 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"
    tools:context=".MainActivityss" >

    <TextView
        android:id="@+id/textView1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="请输入下载文件的路径" />

    <EditText
        android:id="@+id/et_url"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@+id/textView1"
        android:layout_marginTop="14dp"
        android:text="http://172.16.232.237:8080/viedo/DSC_1495.JPG">
    </EditText>

    <Button
        android:id="@+id/btn_download"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@+id/et_url"
        android:onClick="downLoadFile"
        android:text="下载" />

</RelativeLayout>

3.StreamTools工具

 

package com.example.util;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;

public class StreamTools {
	public static String streamToStr(InputStream is) {
		String value = null;
		try {
			ByteArrayOutputStream baos = new ByteArrayOutputStream();
			int len = 0;
			byte buffer[] = new byte[1024];
			while ((len = is.read(buffer)) != -1) {
				baos.write(buffer, 0, len);

			}
			baos.close();
			is.close();
			value = new String(baos.toByteArray());
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return value;
	}
}

4.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 EditText et_url;
 

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		et_url = (EditText) findViewById(R.id.et_url);

	}

 

	// 下载文件(得到服务器端的文件大小 )
	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) {
							/**
							 * 第一步:得到服务器下载文件的大小,然后在本地设置一个临时文件和服务器端文件大小一致
							 */
							// 获取文件长度
							int fileLength = httpURLConnection
									.getContentLength();
							//判断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");
								// 设置临时文件与服务器文件大小一致
								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() {
									Toast.makeText(MainActivity.this, "服务器端返回错误", 1).show();
								}
							});
						}

					} catch (Exception e) {
						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];
					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;// 已经下载大小
					}
					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, "服务器端返回错误", 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();
				}
			}
		}
	}

	
}

 

运行输出结果

1.输出线程开始位置,结束位置,线程大小,和当前下载位置

 

2.当文件进行下载时,sdcard会缓存文件信息和临时记录下载位置的存储文件

 

3.当杀死线程时,导出图片,图片显示不全

 

4.当我截止线程时,观察可以得出线程1,2,3的下载位置,当我们在启动时继续上次的下载

  

5.完全下载后,保存下载位置的临时txt被删除

 

6.当把下载成功的图片转出,图片显示成功

 

五、常见问题

内存溢出

 

有时候我们为了方便测试下载。将缓存区的值设置的很大,例如byte buffer[] = new byte[1024*1024*100];如果这样设置的话会内存溢出的错误,所以我们一般情况下定义缓冲区为byte buffer[] = new byte[1024];

 

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

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

赵雅智_java的多线程下载移植到android客户端,,5-wow.com

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。