安卓断点下载apk以及安装

1 篇文章 0 订阅
1 篇文章 0 订阅

安卓断点下载apk以及安装

前言

一般app内部都有更新功能,其实很多app下载都没有断点下载的功能,此功能比较复杂性价比不高,现在大家流量又多一般都是直接一次性下载完,要么就跳转应用市场和网页一劳永逸,毕竟人家浏览器和应用市场自带的断点下载功能他不香么。不过手撕一个功能还是比较有意思的毕竟也不难。

知识点

这个需要的知识点无非就是以下几个

  1. 普通下载
  2. 本地储存文件权限
  3. HTTP协议请求头(Range) 响应码206
  4. RandomAccessFile
  5. 数据库(不用greendao)对应下载文件关系,以及下载状态处理
  6. OKhttp和HttpURLConnection的区别
  7. 安装页面权限配置跳转

流程

  1. 通过content-length响应头获取文件的总长度
  2. 配合RandomAccessFile、响应码206、请求头range,以及安卓HttpURLConnection和Rxjava2.0实现下载功能
  3. 使用数据库管理类完善下载节点

普通下载

可以参考下面的文章,无非就是流的处理
https://blog.csdn.net/arios171/article/details/81127458
普通下载先提供一个java版本的代码,以下是javabean代码
普通下载中主要的知识点是content-length,获取这个请求头的方法有三种:

HttpURLConnection.getHeaderField("content-length");
HttpURLConnection.getContentLength();
HttpURLConnection.getContentLengthLong();

这里建议用HttpURLConnection.getHeaderField(“content-length”);获取到字符串之后自己去解析成long类型的,getContentLength默认是int类型如果下载大文件可能会超出范围,getContentLengthLong只有在min sdk android 24才能使用。

public class ProgressBean {
    /**
     * 名称
     */
    private String fileName;
    /**
     * 路径
     */
    private String path;
    /**
     * 进度条
     */
    private long progress;
    /**
     * 大小
     */
    private long size;
    /**
     * 类型
     */
    private int type;

    public String getFileName() {
        return fileName;
    }

    public void setFileName(String fileName) {
        this.fileName = fileName;
    }

    public String getPath() {
        return path;
    }

    public void setPath(String path) {
        this.path = path;
    }

    public long getProgress() {
        return progress;
    }

    public void setProgress(long progress) {
        this.progress = progress;
    }

    public long getSize() {
        return size;
    }

    public void setSize(long size) {
        this.size = size;
    }

    public int getType() {
        return type;
    }

    public void setType(int type) {
        this.type = type;
    }
}

下面是下载的代码,里面封装了Rxjava2的HttpURLConnection下载


import android.util.Log;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;

import io.reactivex.Observable;
import io.reactivex.ObservableOnSubscribe;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.annotations.NonNull;
import io.reactivex.schedulers.Schedulers;
/**
 * @author hskun20
 */
public class DownloadManager {
    private final IHttpDownload listener;
    public static final String TAG = "hskun";

    public DownloadManager(@NonNull IHttpDownload listener) {
        this.listener = listener;
    }

    public interface IHttpDownload {
        /**
         * 下载成功,主线程
         *
         * @param progressBean 进度条数据
         */
        void onSuccess(ProgressBean progressBean);

        /**
         * 进度条,此方法在主线程中调用
         *
         * @param progressBean 进度条数据
         */
        void onProgress(ProgressBean progressBean);

        /**
         * 进度条,此方法在IO线程中调用
         *
         * @param progressBean 进度条数据
         */
        void onIOThreadProgress(ProgressBean progressBean);

        /**
         * 下载失败,此方法在主线程中调用
         *
         * @param error 错误信息
         * @param type  下载分类标识
         */
        void onError(Throwable error, int type);

        /**
         * 下载失败,此方法在computation线程中调用
         *
         * @param error 错误信息
         * @param type  下载分类标识
         */
        void onComputationThreadError(Throwable error, int type);

        /**
         * 下载操作结束,不管是失败还是成功,主线程
         *
         * @param type 下载类型标识
         */
        void onHttpDown(int type);
    }

    //HTTP请求方式
    public static final String GET = "GET";
    public static final String POST = "POST";
    public static final String HEAD = "HEAD";
    public static final String OPTIONS = "OPTIONS";
    public static final String PUT = "PUT";
    public static final String DELETE = "DELETE";
    public static final String TRACE = "TRACE";

    public void downloadGet(String url, File file, int type) {
        download(url, file, GET, type);
    }

    public void downloadPost(String url, File file, int type) {
        download(url, file, POST, type);
    }

    /**
     * @param url    下载的连接
     * @param file   目标下载的位置
     * @param method 请求方法
     * @param type   请求的类型作为接口回调的标识
     */
    public void download(final String url, final File file, final String method, final int type) {
        Observable.create((ObservableOnSubscribe<ProgressBean>) emitter -> {
            createFile(file);
            URL mUrl = new URL(url);
            mUrl.openConnection(null);
            HttpURLConnection connection = (HttpURLConnection) mUrl.openConnection();
            connection.setRequestMethod(method);
            connection.setConnectTimeout(1000);
            connection.setReadTimeout(1000);
            connection.connect();
            InputStream bytes = connection.getInputStream();
            String header = connection.getHeaderField("Content-Length");
            int size = 0;
            try {
                size = Integer.parseInt(header);
            } catch (NumberFormatException ignored) {
            }
            if (file.exists()) {
                FileOutputStream fos = new FileOutputStream(file);
                byte[] b = new byte[102400];
                long progress = 0;
                int len;
                while ((len = bytes.read(b)) != -1) {
                    progress += len;
                    fos.write(b, 0, len);
                    ProgressBean progressBean = new ProgressBean();
                    progressBean.setFileName(file.getName());
                    progressBean.setPath(file.getAbsolutePath());
                    progressBean.setProgress(progress);
                    progressBean.setSize(size);
                    progressBean.setType(type);
                    emitter.onNext(progressBean);
                    //这里就是关键的实时更新进度了!
                }
            }
            emitter.onComplete();
            //整个异常出错会自动调用onError
        })
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .doOnNext(progressBean -> {
                    Log.i(TAG, "onNext accept " + Thread.currentThread().toString());
                    if (listener != null) {
                        listener.onProgress(progressBean);
                    }
                })
                .observeOn(Schedulers.io())
                .doOnNext(progressBean -> {
                    Log.i(TAG, "onNext accept " + Thread.currentThread().toString());
                    if (listener != null) {
                        listener.onIOThreadProgress(progressBean);
                    }
                })
                .observeOn(AndroidSchedulers.mainThread())
                .doOnError(throwable -> {
                    Log.i(TAG, "onError accept " + Thread.currentThread().toString());
                    if (listener != null) {
                        listener.onError(throwable, type);
                        listener.onHttpDown(type);
                    }
                })
                .observeOn(Schedulers.computation())
                .doOnError(throwable -> {
                    Log.i(TAG, "onError accept " + Thread.currentThread().toString());
                    if (listener != null) {
                        listener.onComputationThreadError(throwable, type);
                        listener.onHttpDown(type);
                    }
                })
                .observeOn(AndroidSchedulers.mainThread())
                .doOnComplete(() -> {
                    if (listener != null) {
                        ProgressBean progressBean = new ProgressBean();
                        progressBean.setFileName(file.getName());
                        progressBean.setPath(file.getAbsolutePath());
                        progressBean.setProgress(file.length());
                        progressBean.setSize(file.length());
                        progressBean.setType(type);
                        listener.onSuccess(progressBean);
                        listener.onHttpDown(type);
                    }
                })
                .subscribe();
    }

    /**
     * 创建文件顺便创建父目录
     *
     * @param file file类
     */
    public static void createFile(File file) {
        if (file.exists() && file.isFile()) {
            file.delete();
            try {
                file.createNewFile();
            } catch (IOException e) {
                e.printStackTrace();
            }
            return;
        }
        File parentFile = file.getParentFile();
        if (parentFile.exists()) {
            if (parentFile.isFile()) {
                parentFile.delete();
                parentFile.mkdirs();
            }
        } else {
            parentFile.mkdirs();
        }
        try {
            file.createNewFile();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

HTTP相关请求头和响应码

以上内容比较通用的主要就是HTTP协议,HTTP协议默认实现了断点下载的功能。其中请求头为range,响应码为206

断点下载的时候,需要设置请求头的“Range”

  • 表示头100个字节:Range:bytes=0-99
  • 表示第二个100个字节:Range:bytes=100-199
  • 表示最后100个字节:Range:bytes=-100
  • 表示200个字节以后的所有字节:Range:bytes=200-

配合RandomAccessFile、响应码206、请求头range,以及安卓HttpURLConnection和Rxjava2.0实现下载功能

import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.util.Log;

import androidx.core.content.FileProvider;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.URL;

import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable;
import io.reactivex.schedulers.Schedulers;

/**
 * @author hskun20
 */
public class DownloadManager {

    private IHttpDownload listener;
    public static final String TAG = "hskun";
    private final Context mContext;

    public DownloadManager(Context context, IHttpDownload listener) {
        this.mContext = context;
        this.listener = listener;
    }

    /**
     * 取消下载
     */
    public void cancel() {
        this.listener = null;
    }

    public abstract static class HttpDownLoad implements IHttpDownload {

        @Override
        public void onIOThreadProgress(ProgressBean progressBean) {

        }

        @Override
        public void onError(Throwable error, int type) {

        }

        @Override
        public void onComputationThreadError(Throwable error, int type) {

        }

        @Override
        public void onHttpDown(int type) {

        }
    }

    public interface IHttpDownload {
        /**
         * 下载成功,主线程
         *
         * @param progressBean 进度条数据
         */
        void onSuccess(ProgressBean progressBean);

        /**
         * 进度条,此方法在主线程中调用
         *
         * @param progressBean 进度条数据
         */
        void onProgress(ProgressBean progressBean);

        /**
         * 进度条,此方法在IO线程中调用
         *
         * @param progressBean 进度条数据
         */
        void onIOThreadProgress(ProgressBean progressBean);

        /**
         * 下载失败,此方法在主线程中调用
         *
         * @param error 错误信息
         * @param type  下载分类标识
         */
        void onError(Throwable error, int type);

        /**
         * 下载失败,此方法在computation线程中调用
         *
         * @param error 错误信息
         * @param type  下载分类标识
         */
        void onComputationThreadError(Throwable error, int type);

        /**
         * 下载操作结束,不管是失败还是成功,主线程
         *
         * @param type 下载类型标识
         */
        void onHttpDown(int type);
    }

    //HTTP请求方式
    public static final String GET = "GET";
    public static final String POST = "POST";
    public static final String HEAD = "HEAD";
    public static final String OPTIONS = "OPTIONS";
    public static final String PUT = "PUT";
    public static final String DELETE = "DELETE";
    public static final String TRACE = "TRACE";

    public void downloadGet(String url, File file, int type) {
        download(url, file, GET, type);
    }

    public void downloadPost(String url, File file, int type) {
        download(url, file, POST, type);
    }

    /**
     * @param url    下载的连接
     * @param file   目标下载的位置
     * @param method 请求方法
     * @param type   请求的类型作为接口回调的标识
     */
    public void download(final String url, final File file, final String method, final int type) {
        DownloadDbHelper helper = new DownloadDbHelper(mContext);
        Observable<ProgressBean> observable = Observable.create(emitter -> {
            ProgressBean progressBean = helper.findProgressByUrl(url);
            if (progressBean == null) {//如果没有那么说明就没有这个ProgressBean
                progressBean = new ProgressBean();
            } else {//如果直接杀死进程状态来不及改
                if (progressBean.getSize() != 0 && progressBean.getSize() == progressBean.getProgress()) {
                    emitter.onNext(progressBean);
                    emitter.onComplete();
                    return;
                }
            }
            if (!file.exists() || !file.isFile())//如果这个文件不存在才重新创建
                createFile(file);
            URL mUrl = new URL(url);
            long size = -1;
            RandomAccessFile raf = new RandomAccessFile(file, "rwd");
            if (progressBean.getSize() != 0) {
                size = progressBean.getSize();
            } else {
                //拿到总长度
                HttpURLConnection lengthConnection = (HttpURLConnection) mUrl.openConnection();
                lengthConnection.setRequestMethod(method);
                lengthConnection.setConnectTimeout(1000);
                lengthConnection.setReadTimeout(1000);
                lengthConnection.connect();
                String header = lengthConnection.getHeaderField("content-length");
                try {
                    size = Long.parseLong(header);
                } catch (Exception ignored) {
                }
                raf.setLength(size);//设置长度,设置长度会破坏已下载的文件
            }
            //设置文件写入位置
            raf.seek(progressBean.getProgress());//设置光标位置
            //开始断点下载
            HttpURLConnection connection = (HttpURLConnection) mUrl.openConnection();
            connection.setRequestMethod(method);
            connection.setConnectTimeout(1000);
            connection.setReadTimeout(1000);
            connection.setRequestProperty("Range", "bytes=" + progressBean.getProgress() + "-" + size);//表示下载start以后的所有字节
//            connection.setRequestProperty("Range", "bytes=" + start + "-" + threadInfo.getEnd());//不做分片所以这里没有用
            int responseCode = connection.getResponseCode();//这个方法可以替代正常下载的 connection.connect();方法做请求操作
            if (responseCode == HttpURLConnection.HTTP_PARTIAL) {//这边响应码必须等于206
                InputStream inputStream = connection.getInputStream();
                try {
                    if (file.exists()) {
                        byte[] b = new byte[1024 * 8];//len最大也就8192
                        long progress = progressBean.getProgress();
                        int len;
                        while ((len = inputStream.read(b)) != -1) {
                            progress += len;
                            raf.write(b, 0, len);
//                            raf.write(b, 0, len);
                            progressBean.setFileName(file.getName());
                            progressBean.setUrl(url);
                            progressBean.setIsRunning(1);
                            progressBean.setFinished(0);
                            progressBean.setPath(file.getAbsolutePath());
                            progressBean.setProgress(progress);
                            progressBean.setSize(size);
                            progressBean.setType(type);
                            //插入到进度条,取消订阅这里会继续下载,所以为了不浪费在Observable中继续操作不要在观察者中操作
                            helper.insertOrReplace(progressBean);
                            //回显进度
                            emitter.onNext(progressBean);
                            //这里就是关键的实时更新进度了!
                        }
                    }
                    emitter.onComplete();
                } catch (Exception e) {
                    e.printStackTrace();
                    throw e;
                } finally {
                    connection.disconnect();
                    try {
                        if (raf != null)
                            raf.close();
                        inputStream.close();
                    } catch (IOException e) {
                        emitter.onError(e);
                    }
                }
            }

            //整个异常出错会自动调用onError
        });
        Disposable subscribe = observable
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .doOnNext(progressBean -> {
                    Log.i(TAG, "onNext accept " + Thread.currentThread().toString());
                    if (listener != null) {
                        listener.onProgress(progressBean);
                    }
                })
                .observeOn(Schedulers.io())
                .doOnNext(progressBean -> {
                    Log.i(TAG, "onNext accept " + Thread.currentThread().toString());
                    if (listener != null) {
                        listener.onIOThreadProgress(progressBean);
                    }
                })
                .observeOn(AndroidSchedulers.mainThread())
                .doOnError(throwable -> {
                    Log.i(TAG, "onError accept " + Thread.currentThread().toString());
                    if (listener != null) {
                        listener.onError(throwable, type);
                        listener.onHttpDown(type);
                    }
                })
                .observeOn(Schedulers.computation())
                .doOnError(throwable -> {
                    Log.i(TAG, "onError accept " + Thread.currentThread().toString());
                    if (listener != null) {
                        ProgressBean progressBean = new ProgressBean();
                        progressBean.setFileName(file.getName());
                        progressBean.setFinished(0);
                        progressBean.setUrl(url);
                        progressBean.setRunning(0);
                        progressBean.setPath(file.getAbsolutePath());
                        progressBean.setSize(file.length());
                        progressBean.setType(type);
                        helper.insertOrReplaceNoProgress(progressBean);

                        listener.onComputationThreadError(throwable, type);
                        listener.onHttpDown(type);
                    }
                })
                .observeOn(AndroidSchedulers.mainThread())
                .doOnComplete(() -> {
                    if (listener != null) {
                        ProgressBean progressBean = new ProgressBean();
                        progressBean.setFileName(file.getName());
                        progressBean.setFinished(1);
                        progressBean.setUrl(url);
                        progressBean.setRunning(0);
                        progressBean.setPath(file.getAbsolutePath());
                        progressBean.setProgress(file.length());
                        progressBean.setSize(file.length());
                        progressBean.setType(type);
                        helper.insertOrReplace(progressBean);

                        listener.onSuccess(progressBean);
                        listener.onHttpDown(type);
                        Log.e(TAG, "OnComplete " + progressBean.toString());
                    }
                })
                .subscribe(progressBean -> {

                }, throwable -> {
                    Log.e(TAG, throwable.getMessage(), throwable);
                });
    }

    public static void installAPK(Context context, File file) {
        Intent intent = new Intent(Intent.ACTION_VIEW);
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { // 7.0+以上版本
            Uri apkUri = FileProvider.getUriForFile(context, context.getApplicationContext().getPackageName(), file);  //包名.fileprovider
            intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
            intent.setDataAndType(apkUri, "application/vnd.android.package-archive");
        } else {
            intent.setDataAndType(Uri.fromFile(file), "application/vnd.android.package-archive");
        }
        context.startActivity(intent);
    }

    /**
     * 创建文件顺便创建父目录
     *
     * @param file file类
     */
    public static void createFile(File file) {
        if (file.exists() && file.isFile()) {
            file.delete();
            try {
                file.createNewFile();
            } catch (IOException e) {
                e.printStackTrace();
            }
            return;
        }
        File parentFile = file.getParentFile();
        if (parentFile.exists()) {
            if (parentFile.isFile()) {
                parentFile.delete();
                parentFile.mkdirs();
            }
        } else {
            parentFile.mkdirs();
        }
        try {
            file.createNewFile();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

数据库处理

为什么不用greendao和其他的第三方库,因为这些写起来比较难移植,处理起来比较麻烦,原生的库可以很方便作为库依赖到别的项目里面

import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.text.TextUtils;

import androidx.annotation.Nullable;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;


public class DownloadDbHelper extends SQLiteOpenHelper {
    /**
     * 备注如下,主键唯一值是url
     */
    private final static String DOWNLOAD_INFO = "CREATE TABLE download_info(" +
            "url TEXT PRIMARY KEY NOT NULL," +//文件网络路径
            "file_name TEXT," +//文件名称
            "path TEXT," +//文件路径
            "finished INTEGER," +//是否下载完成0否1是
            "progress INTEGER," +//进度条
            "is_running INTEGER," +//是否下载中0否1是
            "size INTEGER" +//文件大小
            ")";
    /**
     * 下载列名称
     */
    private final static String[] DOWNLOAD_COLUMN = {
            "url",
            "file_name",
            "path",
            "finished",
            "progress",
            "is_running",
            "size"
    };

    public DownloadDbHelper(@Nullable Context context) {
        super(context, "download.db", null, 1);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        createAllTable(db);
        createIndex(db);
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        if (newVersion > oldVersion) {
            clearAll(db);
        }
    }

    private void clearAll(SQLiteDatabase db) {
        db.beginTransaction();
        try {
            dropAllTable(db);
            createAllTable(db);
            createIndex(db);
            db.setTransactionSuccessful();
        } catch (Exception e) {
            e.printStackTrace();
            throw e;
        } finally {
            db.endTransaction();
        }
    }

    private void createIndex(SQLiteDatabase db) {
        db.execSQL("CREATE INDEX url ON download_info(url)");
    }

    /**
     * 这里增加两个索引,一个是id另一个是网络路径
     */
    private void dropAllTable(SQLiteDatabase db) {
        db.execSQL("DROP TABLE download_info");
    }


    private void createAllTable(SQLiteDatabase db) {
        db.execSQL(DOWNLOAD_INFO);
    }

    /**
     * 搜索对应的链接下载信息,如果没有查到就给null
     *
     * @param url 下载链接
     */
    public ProgressBean findProgressByUrl(String url) {
        SQLiteDatabase db = getWritableDatabase();
        String selection = "url = ?";
        String[] selectionArgs = {url};
        try {
            Cursor cursor = db.query(
                    "download_info",
                    DOWNLOAD_COLUMN,
                    selection,
                    selectionArgs,
                    null,
                    null,
                    null,
                    null
            );
            Map<String, Object> map = queryMap(cursor, DOWNLOAD_COLUMN);
            if (map != null) {
                ProgressBean progressBean = new ProgressBean();
                progressBean.setFileName((String) map.get("file_name"));
                progressBean.setPath((String) map.get("path"));
                progressBean.setUrl((String) map.get("url"));
                progressBean.setFinished((Long) map.get("finished"));
                progressBean.setProgress((Long) map.get("progress"));
                progressBean.setRunning((Long) map.get("is_running"));
                progressBean.setSize((Long) map.get("size"));
                return progressBean;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 插入一条下载信息
     */
    public void insertOrReplace(ProgressBean progressBean) {
        if (TextUtils.isEmpty(progressBean.getUrl())) {//如果主键url是空的那么就不做操作
            return;
        }
        SQLiteDatabase db = getWritableDatabase();
        String sql = "INSERT OR REPLACE INTO download_info (url,file_name,path,finished,progress,is_running,size)" +
                "VALUES ('" + progressBean.getUrl() + "','" +
                progressBean.getFileName() +
                "','" +
                progressBean.getPath() +
                "'," +
                progressBean.getFinished() +
                "," +
                progressBean.getProgress() +
                "," +
                progressBean.getIsRunning() +
                "," +
                progressBean.getSize() +
                ")";
        db.execSQL(sql);
    }

    /**
     * 错误的时候插入只更新除了进度的状态
     */
    public void insertOrReplaceNoProgress(ProgressBean progressBean) {
        if (TextUtils.isEmpty(progressBean.getUrl())) {//如果主键url是空的那么就不做操作
            return;
        }
        SQLiteDatabase db = getWritableDatabase();
        String sql = "INSERT OR REPLACE INTO download_info (url,file_name,path,finished,is_running,size)" +
                "VALUES ('" + progressBean.getUrl() + "','" +
                progressBean.getFileName() +
                "','" +
                progressBean.getPath() +
                "'," +
                progressBean.getFinished() +
                "," +
                progressBean.getIsRunning() +
                "," +
                progressBean.getSize() +
                ")";
        db.execSQL(sql);
    }

    /**
     * 设置好cursor然后直接将cursor转成list
     *
     * @param cursor 设置好的cursor里面包含了查询条件
     * @return 返回list集合
     */
    private List<Map<String, Object>> queryList(Cursor cursor,
                                                String[] names) {
        String[] columNames = names;
        List<Map<String, Object>> list = new ArrayList<>();
        while (cursor.moveToNext()) {
            String[] columnNames = cursor.getColumnNames();
            if (columNames == null || columNames.length == 0 || columNames.length > columnNames.length) {//如果是空的就直接用cursor的列
                columNames = columnNames;
            }
            Map<String, Object> map = new HashMap<>();
            for (String name : columNames) {
                int columnIndex = cursor.getColumnIndex(name);
                switch (cursor.getType(columnIndex)) {
                    case Cursor.FIELD_TYPE_INTEGER:
                        map.put(name, cursor.getLong(columnIndex));
                        break;
                    case Cursor.FIELD_TYPE_STRING:
                        map.put(name, cursor.getString(columnIndex));
                        break;
                    case Cursor.FIELD_TYPE_FLOAT:
                        map.put(name, cursor.getFloat(columnIndex));
                        break;
                    case Cursor.FIELD_TYPE_NULL:
                        map.put(name, null);
                        break;
                }
            }
            list.add(map);
        }
        cursor.close();
        return list;
    }

    /**
     * 设置好cursor然后直接将cursor转成map只解析一行
     *
     * @param cursor 设置好的cursor里面包含了查询条件
     * @return 返回list集合
     */
    private Map<String, Object> queryMap(Cursor cursor, String[] names) {
        String[] columNames = names;
        String[] columnNames = cursor.getColumnNames();
        if (columNames == null || columNames.length == 0 || columNames.length > columnNames.length) {//如果是空的就直接用cursor的列
            columNames = columnNames;
        }
        if (!cursor.moveToFirst()) { //没有查到一个直接返回null
            return null;
        }
        Map<String, Object> map = new HashMap<>();
        for (String name : columNames) {
            int columnIndex = cursor.getColumnIndex(name);
            switch (cursor.getType(columnIndex)) {
                case Cursor.FIELD_TYPE_INTEGER:
                    map.put(name, cursor.getLong(columnIndex));
                    break;
                case Cursor.FIELD_TYPE_STRING:
                    map.put(name, cursor.getString(columnIndex));
                    break;
                case Cursor.FIELD_TYPE_FLOAT:
                    map.put(name, cursor.getFloat(columnIndex));
                    break;
                case Cursor.FIELD_TYPE_NULL:
                    map.put(name, null);
                    break;
            }
        }
        cursor.close();
        return map;
    }

}


ProgressBean

progressBean中url设置为主键,type只为请求的时候使用

public class ProgressBean {

    /**
     * 名称
     */
    private String fileName;
    /**
     * 路径
     */
    private String path;
    /**
     * 网络路径
     */
    private String url;
    /**
     * 是否完成进度100%并不能代表完成,实际文件长度和contentLength不一定一样
     */
    private long finished;
    /**
     * 是否下载中
     */
    private long isRunning;
    /**
     * 进度条,作用很多判断从哪里下载,实际的字节数progress,单位对应contentLength
     */
    private long progress;
    /**
     * 大小,实际的字节数size,单位对应contentLength
     */
    private long size;
    /**
     * 类型
     */
    private int type;

    public String getFileName() {
        return fileName;
    }

    public void setFileName(String fileName) {
        this.fileName = fileName;
    }

    public String getPath() {
        return path;
    }

    public void setPath(String path) {
        this.path = path;
    }

    public long getProgress() {
        return progress;
    }

    public void setProgress(long progress) {
        this.progress = progress;
    }

    public long getSize() {
        return size;
    }

    public void setSize(long size) {
        this.size = size;
    }

    public int getType() {
        return type;
    }

    public void setType(int type) {
        this.type = type;
    }

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }


    public void setFinished(long finished) {
        this.finished = finished;
    }

    public long getFinished() {
        return finished;
    }

    public long getIsRunning() {
        return isRunning;
    }

    public void setIsRunning(long isRunning) {
        this.isRunning = isRunning;
    }

    public void setRunning(long running) {
        isRunning = running;
    }

    @Override
    public String toString() {
        return "ProgressBean{" +
                ", fileName='" + fileName + '\'' +
                ", path='" + path + '\'' +
                ", url='" + url + '\'' +
                ", finished=" + finished +
                ", isRunning=" + isRunning +
                ", progress=" + progress +
                ", size=" + size +
                ", type=" + type +
                '}';
    }
}

OKhttp和HttpURLConnection的区别

OKhttp和HttpURLConnection的区别,为啥不用OKhttp,因为我使用OKhttp的过程中发现默认OKhttp是直接把流全部读完了再给我们使用的,大文件下载的时候进入会一直是0,如果下载完成会突然拉满进度。如果要使用OKhttp需要使用ok的内核Okio中的流来做下载,不然的话Okhttp会帮你下载完成了之后才把流返回给你。不得不说okhttp的缓存确实挺不错,但是用在下载大文件的时候这个策略成了鸡肋。

下载调用

下载调用主要是用到了如下方法,主要是初始化DownloadManager,然后在确定独写权限之后下载即可,可用downloadGet和downloadPost,如果有参数可以修改上面的下载类。

private DownloadManager manager;
   private void initData() {
        manager = new DownloadManager(this, new DownloadManager.HttpDownLoad() {
            @Override
            public void onSuccess(ProgressBean progressBean) {
                DownloadManager.installAPK(UpdateDialogActivity.this, new File(progressBean.getPath()));
            }

            @Override
            public void onProgress(ProgressBean progressBean) {
                long progress = progressBean.getProgress();
                double size = (double) progressBean.getSize();
                int percentProgress = (int) (progress / size * 100f);
                bp.setProgress(percentProgress);
                int progressMB = (int) (progress / 1024f / 1024f);
                int sizeMB = (int) (size / 1024f / 1024f);
                ratio.setText(percentProgress + "%" + progressMB + "MB/" + sizeMB + "MB");
            }
        });
        startDownload();
    }

    /**
     * 下载apk文件
     */
    private void startDownload() {

        if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {
            manager.downloadGet(
                    "https://gyxz2.243ty.com/com.iflytek.iflyapp.apk",
                    new File(Environment.getExternalStorageDirectory() + "/xxx/download", "xxx.apk"),
                    0
            );
        } else {
            View view = getWindow().getDecorView();
            Snackbar.make(
                    view,
                    "没有检查到sd卡无法下载",
                    Snackbar.LENGTH_SHORT
            ).show();
        }
    }

权限管理

权限管理用的是XXPermissions,可以自行在github搜索,示例代码如下

com.hjq.permissions.XXPermissions.with((Activity) mContext)
                    .permission(Permission.Group.STORAGE)
                    .request(new OnPermission() {
                        @Override
                        public void hasPermission(List<String> granted, boolean isAll) {
                            if (isAll) {
                                Intent intent = new Intent(mContext, XXXActivity.class);
                                startActivity(intent);
                            }
                        }

                        @Override
                        public void noPermission(List<String> denied, boolean quick) {
                            showToastTop("请授权之后再使用此功能");
                        }
                    });

最后就是安装

基于7.0以上安装需要做一些操作,代码如下,需要写一个xml文件放在res xml文件夹下

   public static void installAPK(Context context, File file) {
        Intent intent = new Intent(Intent.ACTION_VIEW);
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { // 7.0+以上版本
            Uri apkUri = FileProvider.getUriForFile(context, context.getApplicationContext().getPackageName(), file);  //包名.fileprovider
            intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
            intent.setDataAndType(apkUri, "application/vnd.android.package-archive");
        } else {
            intent.setDataAndType(Uri.fromFile(file), "application/vnd.android.package-archive");
        }
        context.startActivity(intent);
    }

manifest需要配置如下,需要注意10以上需要android:requestLegacyExternalStorage=“true”,因为10以上写权限有很大变化,这里直接跳过用原来的老权限方式,配置provider则如下就可以

    <application
            android:requestLegacyExternalStorage="true"    >
       <provider
            android:name="androidx.core.content.FileProvider"
            android:authorities="${applicationId}"
            android:grantUriPermissions="true"
            android:exported="false">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/paths" />
        </provider>
    </application>

@xml/paths文件如下,这样可以存放任意apk到任意sd卡位置使用

<?xml version="1.0" encoding="utf-8"?>
<paths>
    <external-path name="external_files" path="."/>
    <external-path path="path" name="download"/>
    <!--<external-path name="external_path" path=""/>-->
</paths>
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值