1、添加权限
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>
2、布局文件
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity"
android:orientation="vertical">
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/btn"
android:text="more"/>
<ProgressBar
style="?android:attr/progressBarStyleHorizontal"
android:id="@+id/pb1"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<ProgressBar
style="?android:attr/progressBarStyleHorizontal"
android:id="@+id/pb2"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<ProgressBar
style="?android:attr/progressBarStyleHorizontal"
android:id="@+id/pb3"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
3、Activity,动态访问SD卡的权限
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initSD();
initView();
}
private void initView() {
btn = (Button) findViewById(R.id.btn);
pb1 = (ProgressBar) findViewById(R.id.pb1);
pb2 = (ProgressBar) findViewById(R.id.pb2);
pb3 = (ProgressBar) findViewById(R.id.pb3);
btn.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn:
moreThread();
break;
}
}
private void initSD() {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) ==
PackageManager.PERMISSION_GRANTED) {
openSD();
} else {
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 100);
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
@NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == 100 && grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
openSD();
}
}
// 判断 SD 卡的状态并得到 SD 卡的根目录
private void openSD() {
if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
sd = Environment.getExternalStorageDirectory();
}
}
4、ThreadManager (创建线程池需要的工具类)
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class ThreadManager {
private static ThreadManager threadManager;
private final ThreadPoolExecutor mExecutor;
private ThreadManager(){
mExecutor = new ThreadPoolExecutor(5, // 核心线程数量,核心池的大小
20, // 线程池最大线程数
30, // 表示线程没有任务执行时最多保持多久时间会终止
TimeUnit.SECONDS, // 时间单位
new LinkedBlockingQueue<Runnable>(), // 任务队列,用来存储等待执行的任务
Executors.defaultThreadFactory(), // 线程工厂,如何去创建线程的
new ThreadPoolExecutor.AbortPolicy());
}
public static ThreadManager getThreadManager() {
if (threadManager == null){
synchronized (ThreadManager.class){
if (threadManager == null){
threadManager = new ThreadManager();
}
}
}
return threadManager;
}
/**
* 执行任务
*/
public void execute(Runnable runnable){
if(runnable==null)return;
mExecutor.execute(runnable);
}
/**
* 从线程池中移除任务
*/
public void remove(Runnable runnable){
if(runnable==null)return;
mExecutor.remove(runnable);
}
}
5、获取到每个线程要下载的文件大小
// 要用的线程数
private static final int THREAD_COUNT = 3;
// 下载的文件的保存路径
private String Path = "/storage/sdcard0" + "/" + "abc456.apk";
private void moreThread() {
ThreadManager.getThreadManager().execute(new Runnable() {
@Override
public void run() {
try {
URL url = new URL("http://cdn.banmi.com/banmiapp/apk/banmi_330.apk");
HttpURLConnection con = (HttpURLConnection) url.openConnection();
//1.获取文件的长度
int responseCode = con.getResponseCode();
if (responseCode == 200) {
int contentLength = con.getContentLength();
Log.d(TAG, "contentLength: " + contentLength);
//2.创建与下载文件同样大小的空壳文件
RandomAccessFile randomAccessFile = new RandomAccessFile(Path, "rw");
randomAccessFile.setLength(contentLength);
//3.划分多线程下载的开始位置和结束位置
long block = contentLength / THREAD_COUNT;
for (int i = 0; i < THREAD_COUNT; i++) {
long start = i * block;
long end = (i + 1) * block - 1;
if (i == THREAD_COUNT - 1) {
end = contentLength - 1;
}
//4.开启线程分块下载
//down(i, start, end);//多线程下载
downContinue(i, start, end);//多线程断点下载
}
}
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
});
}
6、正常下载(不能出现中途断网或关闭应用,如果出现断网或关闭应用,重新联网或重启应用会重新下载)
private void down(final int ThreadId, final long start, final long end) {
ThreadManager.getThreadManager().execute(new Runnable() {
@Override
public void run() {
try {
URL url = new URL("http://cdn.banmi.com/banmiapp/apk/banmi_330.apk");
HttpURLConnection con = (HttpURLConnection) url.openConnection();
// 设置请求部分数据资源:Range:bytes=10-30
con.setRequestProperty("Range", "bytes=" + start + "-" + end);
int responseCode = con.getResponseCode();
Log.d(TAG, "responseCode: " + responseCode);
if (responseCode == 206) {
InputStream inputStream = con.getInputStream();
RandomAccessFile rw = new RandomAccessFile(Path, "rw");
// 设置当前线程写入的位置
rw.seek(start);
// 当前线程中需要下载的分段总长度:
long max = end - start;
// 当前线程中需要下载的分段当前进度:
long count = 0;
int length = -1;
byte[] bys = new byte[1024 * 10];
while ((length = inputStream.read(bys)) != -1) {
rw.write(bys, 0, length);
count += length;
Log.d(TAG, "线程: " + ThreadId + "总长度:" + max + "下载:" + count);
if (ThreadId == 0) {
pb1.setMax((int) max);
pb1.setProgress((int) count);
}
if (ThreadId == 1) {
pb2.setMax((int) max);
pb2.setProgress((int) count);
}
if (ThreadId == 2) {
pb3.setMax((int) max);
pb3.setProgress((int) count);
}
}
inputStream.close();
rw.close();
Log.d(TAG, "线程: " + ThreadId + "下载完毕");
// 安装
InstallUtil.installApk(MainActivity.this,Path);
}
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
});
}
7、断点下载(可以解决断网或关闭应用造成的下载中断,重新联网或重启应用会继续上一次的下载)
private void downContinue(final int ThreadId, final long start, final long end) {
Log.d(TAG, "线程: " + ThreadId + ",下载范围:" + start + "--" + end);
ThreadManager.getThreadManager().execute(new Runnable() {
@Override
public void run() {
//记录当前线程下载位置
long currentPosition = start;
//获取sp中的下载位置,如果为0,说明没有下载过或者下载完成过
long position = (long) SharedPreferencesUtils.getParam(MainActivity.this,
"" + ThreadId, 0L);
if (position == 0) {//没有下载记录
currentPosition = start;
Log.d(TAG, "非断点续传: " + ThreadId + ",currentPosition:" + currentPosition);
} else {//有级录
currentPosition = position;
Log.d(TAG, "断点续传: " + ThreadId + ",currentPosition:" + currentPosition);
}
try {
URL url = new URL("http://cdn.banmi.com/banmiapp/apk/banmi_330.apk");
HttpURLConnection con = (HttpURLConnection) url.openConnection();
//设置请求部分数据资源:Range:bytes=10-30
con.setRequestProperty("Range", "bytes=" + currentPosition + "-" + end);
int responseCode = con.getResponseCode();
Log.d(TAG, "responseCode: " + responseCode);
if (responseCode == 206) {
InputStream inputStream = con.getInputStream();
RandomAccessFile rw = new RandomAccessFile(Path, "rw");
//设置当前线程写入的位置
rw.seek(currentPosition);
int length = -1;
byte[] bys = new byte[1024 * 10];
while ((length = inputStream.read(bys)) != -1) {
rw.write(bys, 0, length);
currentPosition += length;
//将当前写入的位置保存
SharedPreferencesUtils.setParam(MainActivity.this, ThreadId + "", currentPosition);
if (ThreadId == 0) {
pb1.setMax((int) end);
pb1.setProgress((int) currentPosition);
}
if (ThreadId == 1) {
pb2.setMax((int) end);
pb2.setProgress((int) currentPosition);
}
if (ThreadId == 2) {
pb3.setMax((int) end);
pb3.setProgress((int) currentPosition);
}
Log.d(TAG, "线程: " + ThreadId + "最后位置:" + end + "下载:" + currentPosition);
}
inputStream.close();
rw.close();
Log.d(TAG, "线程: " + ThreadId + "下载玩完毕");
//完成下载后将当前的写入位置置零
SharedPreferencesUtils.setParam(MainActivity.this, ThreadId + "", 0L);
// 安装
InstallUtil.installApk(MainActivity.this,Path);
}
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
});
}
8、SharedPreferencesUtils (断点下载需要用到的类)
import android.content.Context;
import android.content.SharedPreferences;
public class SharedPreferencesUtils {
/**
* 保存在手机里面的文件名
*/
private static final String FILE_NAME = "share_date";
/**
* 保存数据的方法,我们需要拿到保存数据的具体类型,然后根据类型调用不同的保存方法
*
* @param context
* @param key
* @param object
*/
public static void setParam(Context context, String key, Object object) {
String type = object.getClass().getSimpleName();
SharedPreferences sp = context.getSharedPreferences(FILE_NAME, Context.MODE_PRIVATE);
SharedPreferences.Editor editor = sp.edit();
if ("String".equals(type)) {
editor.putString(key, (String) object);
} else if ("Integer".equals(type)) {
editor.putInt(key, (Integer) object);
} else if ("Boolean".equals(type)) {
editor.putBoolean(key, (Boolean) object);
} else if ("Float".equals(type)) {
editor.putFloat(key, (Float) object);
} else if ("Long".equals(type)) {
editor.putLong(key, (Long) object);
}
editor.commit();
}
/**
* 得到保存数据的方法,我们根据默认值得到保存的数据的具体类型,然后调用相对于的方法获取值
*
* @param context
* @param key
* @param defaultObject
* @return
*/
public static Object getParam(Context context, String key, Object defaultObject) {
String type = defaultObject.getClass().getSimpleName();
SharedPreferences sp = context.getSharedPreferences(FILE_NAME, Context.MODE_PRIVATE);
if ("String".equals(type)) {
return sp.getString(key, (String) defaultObject);
} else if ("Integer".equals(type)) {
return sp.getInt(key, (Integer) defaultObject);
} else if ("Boolean".equals(type)) {
return sp.getBoolean(key, (Boolean) defaultObject);
} else if ("Float".equals(type)) {
return sp.getFloat(key, (Float) defaultObject);
} else if ("Long".equals(type)) {
return sp.getLong(key, (Long) defaultObject);
}
return null;
}
}
9、InstallUtil(下载完成后安装需要用的类)
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.provider.Settings;
import android.support.annotation.RequiresApi;
import android.support.v4.content.FileProvider;
import java.io.File;
public class InstallUtil {
public static final int UNKNOWN_CODE = 2019;
public static void installApk(Context context, String path) {
if (Build.VERSION.SDK_INT>=Build.VERSION_CODES.O){
startInstallO(context,path);
}else if (Build.VERSION.SDK_INT>=Build.VERSION_CODES.N){
startInstallN(context,path);
}else {
startInstall(context,path);
}
}
/**
* android 1.x ~ 6.x
*@param path 文件的路径
*/
private static void startInstall(Context context, String path) {
Intent install = new Intent(Intent.ACTION_VIEW);
install.setDataAndType(Uri.parse("file://" + path), "application/vnd.android.package-archive");
install.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(install);
}
/**
* android 7.x
* @param path 文件路径
*/
@RequiresApi(api = Build.VERSION_CODES.N)
private static void startInstallN(Context context, String path) {
// 参数 1 上下文,参数 2 在 AndroidManifest 中的 android:authorities 值,参数 3 共享的文件
Uri apkUri = FileProvider.getUriForFile(context, "com.baidu.download.provider", new File(path));
Intent install = new Intent(Intent.ACTION_VIEW);
// 由于没有在 Activity 环境下启动 Activity,设置下面的标签
install.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
// 添加这一句表示对目标应用临时授权该 Uri 所代表的文件
install.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
install.setDataAndType(apkUri, "application/vnd.android.package-archive");
context.startActivity(install);
}
/**
* android 8.x
*/
@RequiresApi(api = Build.VERSION_CODES.O)
private static void startInstallO(final Context context, String path) {
boolean isGranted = context.getPackageManager().canRequestPackageInstalls();
if (isGranted) startInstallN(context,path); // 安装应用的逻辑(写自己的就可以)
else new AlertDialog.Builder(context)
.setCancelable(false)
.setTitle("安装应用需要打开未知来源权限,请去设置中开启权限")
.setPositiveButton("确定", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface d, int w) {
Intent intent = new Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES);
Activity act = (Activity) context;
act.startActivityForResult(intent, UNKNOWN_CODE);
}
})
.show();
}
}