Android 版本更新(对话框和通知栏)下载和进度条下载(适合百分之80的项目)

成年人的世界:

1:在成年人的世界里,是没有“崩溃”这个选项的。

2:他们需要小心翼翼地发泄,精打细算地缓解,并在最短的时间范围内恢复到正常。

3:他们不会在真正的大事面前倒下,因为知道自己身后没有人,或者,身后有人。他们只不过是在平淡的琐事中感到悲伤与愤怒。

效果图:

图一:


超级截屏_20190731_094936.png

图二:


超级截屏_20190731_095002.png

图三:


超级截屏_20190731_095036.png

图四:

image.png

虽然代码很多, 我会为大家依次粘贴(按包粘贴)

项目目录:


image.png

依赖:
//retrofit2 + rxjava2 + okhttp3
implementation 'com.squareup.retrofit2:retrofit:2.4.0'
implementation 'com.squareup.okhttp3:logging-interceptor:3.9.0'
implementation 'io.reactivex.rxjava2:rxjava:2.1.12'
implementation 'io.reactivex.rxjava2:rxandroid:2.0.2'
implementation 'com.squareup.retrofit2:converter-gson:2.4.0'
implementation 'com.squareup.retrofit2:adapter-rxjava2:2.4.0'
implementation 'com.squareup.retrofit2:converter-scalars:2.4.0'

第一步:exception包

HttpTimeException类
/**
 * 自定义错误信息,统一处理返回处理
 */
public class HttpTimeException extends RuntimeException {

public static final int NO_DATA = 0x2;

public HttpTimeException(int resultCode) {
    this(getApiExceptionMessage(resultCode));
}

public HttpTimeException(String detailMessage) {
    super(detailMessage);
}

/**
 * 转换错误数据
 *
 * @param code 错误吗
 * @return 错误信息
 */
private static String getApiExceptionMessage(int code) {
    String message;
    switch (code) {
        case NO_DATA:
            message = "无数据";
            break;
        default:
            message = "error";
            break;
    }
    return message;
}
}
RetryWhenNetworkException 类
 /**
 * retry条件
 */
public class RetryWhenNetworkException implements Function<Observable<? extends Throwable>, ObservableSource<?>> {

//    retry次数
private int count = 3;
//    延迟
private long delay = 3000;
//    叠加延迟
private long increaseDelay = 3000;

public RetryWhenNetworkException() {
}

public RetryWhenNetworkException(int count, long delay) {
    this.count = count;
    this.delay = delay;
}

public RetryWhenNetworkException(int count, long delay, long increaseDelay) {
    this.count = count;
    this.delay = delay;
    this.increaseDelay = increaseDelay;
}

@Override
public Observable<?> apply(Observable<? extends Throwable> input) {
    return input.zipWith(Observable.range(1, count + 1), new BiFunction<Throwable, Integer, Wrapper>() {
        @Override
        public Wrapper apply(Throwable throwable, Integer integer) {
            return new Wrapper(throwable, integer);
        }
    }).flatMap(new io.reactivex.functions.Function<Wrapper, ObservableSource<?>>() {
        @Override
        public ObservableSource<?> apply(Wrapper wrapper) {
            if ((wrapper.throwable instanceof ConnectException
                    || wrapper.throwable instanceof SocketTimeoutException
                    || wrapper.throwable instanceof TimeoutException)
                    && wrapper.index < count + 1) { //如果超出重试次数也抛出错误,否则默认是会进入onCompleted
                Log.e("tag", "retry---->" + wrapper.index);
                return Observable.timer(delay + (wrapper.index - 1) * increaseDelay, TimeUnit.MILLISECONDS);
            }
            return Observable.error(wrapper.throwable);
        }
    });
}

private class Wrapper {
    private int index;
    private Throwable throwable;

    Wrapper(Throwable throwable, int index) {
        this.index = index;
        this.throwable = throwable;
    }
}

}

第二步:http 包

HttpService类
 /**
 * service统一接口数据
 */
public interface HttpService {

//断点续传下载接口
@Streaming//大文件需要加入这个判断,防止下载过程中写入到内存中
@Headers("Content-type:application/octet-stream")
@GET
Observable<ResponseBody> download(@Header("RANGE") String start, @Url String url);
}

第三步 httpdowonload包

DownloadInterceptor类
    /**
   * 成功回调处理
   * Created by pc12 on 2019/7/31.
   */

  public class DownloadInterceptor implements Interceptor {

private DownloadProgressListener listener;

public DownloadInterceptor(DownloadProgressListener listener) {
    this.listener = listener;
}

@Override
public Response intercept(@NonNull Chain chain) throws IOException {
    Response originalResponse = chain.proceed(chain.request());
    return originalResponse.newBuilder()
            .body(new DownloadResponseBody(originalResponse.body(), listener))
            .build();
}
}
DownloadProgressListener 类
  /**
 * 成功回调处理
 * Created by pc12 on 2019/7/31.
 */

public interface DownloadProgressListener {
/**
 * 下载进度
 * @param read 进度
 * @param count 总长度
 */
void update(long read, long count, boolean done);
  }
DownloadResponseBody 类
  /**
 * 自定义进度的body
 * Created by pc12 on 2019/7/31.
 */

public class DownloadResponseBody extends ResponseBody {

private ResponseBody responseBody;
private DownloadProgressListener progressListener;
private BufferedSource bufferedSource;

DownloadResponseBody(ResponseBody responseBody, DownloadProgressListener progressListener) {
    this.responseBody = responseBody;
    this.progressListener = progressListener;
}

@Override
public MediaType contentType() {
    return responseBody.contentType();
}

@Override
public long contentLength() {
    return responseBody.contentLength();
}

@Override
public BufferedSource source() {
    if (bufferedSource == null) {
        bufferedSource = Okio.buffer(source(responseBody.source()));
    }
    return bufferedSource;
}

private Source source(Source source) {
    return new ForwardingSource(source) {
        long totalBytesRead = 0L;

        @Override
        public long read(@NonNull Buffer sink, long byteCount) throws IOException {
            long bytesRead = super.read(sink, byteCount);
            // read() returns the number of bytes read, or -1 if this source is exhausted.
            totalBytesRead += bytesRead != -1 ? bytesRead : 0;
            if (null != progressListener) {
                progressListener.update(totalBytesRead, responseBody.contentLength(), bytesRead == -1);
            }
            return bytesRead;
        }
    };

}
}
DownInfo类
  public class DownInfo {
//存储位置
private String savePath;
//下载url
private String url;
//基础url
private String baseUrl;
//文件总长度
private long countLength;
//下载长度
private long readLength;
//下载唯一的HttpService
private HttpService service;
//回调监听
//private HttpProgressOnNextListener listener;
//超时设置
private int DEFAULT_TIMEOUT = 6;
//下载状态
private DownState state;
//public DownInfo(String url,HttpProgressOnNextListener listener) {
//setUrl(url);
//setBaseUrl(getBasUrl(url));
// setListener(listener);
//}

public DownState getState() {
    return state;
}

public void setState(DownState state) {
    this.state = state;
}

public DownInfo(String url) {
    setUrl(url);
    setBaseUrl(getBasUrl(url));
}

public DownInfo() {

}

public int getConnectionTime() {
    return DEFAULT_TIMEOUT;
}

public void setConnectionTime(int DEFAULT_TIMEOUT) {
    this.DEFAULT_TIMEOUT = DEFAULT_TIMEOUT;
}

//public HttpProgressOnNextListener getListener() {
//return listener;
//}
//
//public void setListener(HttpProgressOnNextListener listener) {
//this.listener = listener;
//}

public HttpService getService() {
    return service;
}

public void setService(HttpService service) {
    this.service = service;
}

public String getUrl() {
    return url;
}

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

public String getSavePath() {
    return savePath;
}

public void setSavePath(String savePath) {
    this.savePath = savePath;
}

public String getBaseUrl() {
    return baseUrl;
}

public void setBaseUrl(String baseUrl) {
    this.baseUrl = baseUrl;
}

public long getCountLength() {
    return countLength;
}

public void setCountLength(long countLength) {
    this.countLength = countLength;
}


public long getReadLength() {
    return readLength;
}

public void setReadLength(long readLength) {
    this.readLength = readLength;
}

/**
 * 读取baseurl
 */
private String getBasUrl(String url) {
    String head = "";
    int index = url.indexOf("://");
    if (index != -1) {
        head = url.substring(0, index + 3);
        url = url.substring(index + 3);
    }
    index = url.indexOf("/");
    if (index != -1) {
        url = url.substring(0, index + 1);
    }
    return head + url;
}
}

DownState类

/**
 * 下载状态
 */

public enum  DownState {
START,
DOWN,
PAUSE,
STOP,
ERROR,
FINISH,

}
HttpDownManager类
/**
 * http下载处理类
 * Created by pc12 on 2019/7/31.
 */

public class HttpDownManager {
//记录下载数据
private Set<DownInfo> downInfos;
//回调sub队列
private HashMap<String, ProgressDownSubscriber> subMap;
//进度监听队列
private HashMap<String, HttpProgressOnNextListener<DownInfo>> mProgressListenerHashMap;

//单利对象
private volatile static HttpDownManager INSTANCE;

private HttpDownManager() {
    downInfos = new HashSet<>();
    subMap = new HashMap<>();
    mProgressListenerHashMap = new HashMap<>();
}

/**
 * 获取单例
 */
public static HttpDownManager getInstance() {
    if (INSTANCE == null) {
        synchronized (HttpDownManager.class) {
            if (INSTANCE == null) {
                INSTANCE = new HttpDownManager();
            }
        }
    }
    return INSTANCE;
}

/**
 * 继续下载
 */
public void continueDownload(DownInfo downInfo) {
    //根据下载的url获取到进度监听队列中的监听器
    HttpProgressOnNextListener<DownInfo> httpProgressOnNextListener = mProgressListenerHashMap.get(downInfo.getUrl());
    if (httpProgressOnNextListener != null) {
        startDown(downInfo, httpProgressOnNextListener);
    }
}


/**
 * 开始下载
 */
public void startDown(final DownInfo info, HttpProgressOnNextListener<DownInfo> httpProgressOnNextListener) {
    //正在下载返回异常信息
    if (info.getState() == DownState.DOWN) {
        httpProgressOnNextListener.onError(new Exception("正在下载中"));
        return;
    }
    //添加回调处理类
    ProgressDownSubscriber<DownInfo> subscriber = new ProgressDownSubscriber<>(info, httpProgressOnNextListener);
    //将监听进度的器添加到队列中,以便于继续下载时获取
    mProgressListenerHashMap.put(info.getUrl(), httpProgressOnNextListener);
    //记录回调sub
    subMap.put(info.getUrl(), subscriber);
    //获取service,多次请求公用一个service
    HttpService httpService;
    //设置文件的状态为下载
    info.setState(DownState.START);
    if (downInfos.contains(info)) {
        httpService = info.getService();
    } else {
        DownloadInterceptor interceptor = new DownloadInterceptor(subscriber);
        OkHttpClient.Builder builder = new OkHttpClient.Builder();
        //手动创建一个OkHttpClient并设置超时时间
        builder.connectTimeout(info.getConnectionTime(), TimeUnit.SECONDS);
        builder.addInterceptor(interceptor);

        Retrofit retrofit = new Retrofit.Builder()
                .client(builder.build())
                .addConverterFactory(GsonConverterFactory.create())
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .baseUrl(info.getBaseUrl())
                .build();
        httpService = retrofit.create(HttpService.class);
        info.setService(httpService);
        downInfos.add(info);
    }
    //得到rx对象-上一次下载的位置开始下载
    httpService.download("bytes=" + info.getReadLength() + "-", info.getUrl())
            //指定线程
            .subscribeOn(Schedulers.io())
            .unsubscribeOn(Schedulers.io())
            //失败后的retry配置
            .retryWhen(new RetryWhenNetworkException())
            .map(new Function<ResponseBody, DownInfo>() {
                @Override
                public DownInfo apply(ResponseBody responseBody) {
                    try {
                        writeCache(responseBody, new File(info.getSavePath()), info);
                    } catch (IOException e) {
                        //失败抛出异常
                        throw new HttpTimeException(e.getMessage());
                    }
                    return info;
                }
            })//回调线程
            .observeOn(AndroidSchedulers.mainThread())
            //数据回调
            .subscribe(subscriber);
}


/**
 * 停止下载
 */
public void stopDown(DownInfo info) {
    if (info == null) return;
    info.setState(DownState.STOP);
    if (subMap.containsKey(info.getUrl())) {
        ProgressDownSubscriber subscriber = subMap.get(info.getUrl());
        subscriber.unSubscribe();
        subMap.remove(info.getUrl());
    }
}


/**
 * 删除
 */
@SuppressWarnings("unused")
public void deleteDown(DownInfo info) {
    stopDown(info);
}


/**
 * 暂停下载
 */
public void pause(DownInfo info) {
    if (info == null) return;
    info.setState(DownState.PAUSE);
    if (subMap.containsKey(info.getUrl())) {
        ProgressDownSubscriber subscriber = subMap.get(info.getUrl());
        subscriber.unSubscribe();
        subMap.remove(info.getUrl());
    }
}

/**
 * 停止全部下载
 */
@SuppressWarnings("unused")
public void stopAllDown() {
    for (DownInfo downInfo : downInfos) {
        stopDown(downInfo);
    }
    subMap.clear();
    downInfos.clear();
}

/**
 * 暂停全部下载
 */
@SuppressWarnings("unused")
public void pauseAll() {
    for (DownInfo downInfo : downInfos) {
        pause(downInfo);
    }
    subMap.clear();
    downInfos.clear();
}


/**
 * 返回全部正在下载的数据
 */
@SuppressWarnings("unused")
public Set<DownInfo> getDownInfos() {
    return downInfos;
}


/**
 * 写入文件
 */
private void writeCache(ResponseBody responseBody, File file, DownInfo info) throws IOException {
    if (!file.getParentFile().exists()) {
        boolean bol = file.getParentFile().mkdirs();
        if (!bol) {
            Log.e("TAG", "文件创建失败");
        }
    }
    long allLength;
    if (info.getCountLength() == 0) {
        allLength = responseBody.contentLength();
    } else {
        allLength = info.getCountLength();
    }
    FileChannel channelOut;
    RandomAccessFile randomAccessFile;
    randomAccessFile = new RandomAccessFile(file, "rwd");
    channelOut = randomAccessFile.getChannel();
    MappedByteBuffer mappedBuffer = channelOut.map(FileChannel.MapMode.READ_WRITE,
            info.getReadLength(), allLength - info.getReadLength());
    byte[] buffer = new byte[1024 * 8];
    int len;
    while ((len = responseBody.byteStream().read(buffer)) != -1) {
        mappedBuffer.put(buffer, 0, len);
    }
    responseBody.byteStream().close();
    channelOut.close();
    randomAccessFile.close();
}

}

ProgressDownSubscriberl类

    /**
   * 用于在Http请求开始时,自动显示一个ProgressDialog
   * 在Http请求结束是,关闭ProgressDialog
   * 调用者自己对请求数据进行处理
   */
  public class ProgressDownSubscriber<T> implements Observer<T>, DownloadProgressListener {
//弱引用结果回调
private WeakReference<HttpProgressOnNextListener<T>> mHttpProgressOnNextListener;

/*下载数据*/
private DownInfo downInfo;

private Disposable mDisposable;

public ProgressDownSubscriber(DownInfo downInfo, HttpProgressOnNextListener<T> httpProgressOnNextListener) {
    this.mHttpProgressOnNextListener = new WeakReference<>(httpProgressOnNextListener);
    this.downInfo = downInfo;
}

public void unSubscribe() {
    if (mDisposable != null) {
        mDisposable.dispose();
    }
}


@Override
public void onSubscribe(Disposable disposable) {
    mDisposable = disposable;
}

@Override
public void onNext(T t) {
    if (mHttpProgressOnNextListener.get() != null) {
        mHttpProgressOnNextListener.get().onNext(t);
    }
}

/**
 * 对错误进行统一处理
 * 隐藏ProgressDialog
 *
 * @param e 异常
 */
@Override
public void onError(Throwable e) {
    mDisposable.dispose();
    /*停止下载*/
    HttpDownManager.getInstance().stopDown(downInfo);
    if (mHttpProgressOnNextListener.get() != null) {
        mHttpProgressOnNextListener.get().onError(e);
    }
    downInfo.setState(DownState.ERROR);
}

/**
 * 完成,隐藏ProgressDialog
 */
@Override
public void onComplete() {
    mDisposable.dispose();
    if (mHttpProgressOnNextListener.get() != null) {
        mHttpProgressOnNextListener.get().onComplete();
    }
    downInfo.setState(DownState.FINISH);
}

@SuppressLint("CheckResult")
@Override
public void update(long read, long count, boolean done) {
    if (downInfo.getCountLength() > count) {
        read = downInfo.getCountLength() - count + read;
    } else {
        downInfo.setCountLength(count);
    }
    downInfo.setReadLength(read);
    if (mHttpProgressOnNextListener.get() != null) {
        /*接受进度消息,造成UI阻塞,如果不需要显示进度可去掉实现逻辑,减少压力*/
        Observable.just(read).observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Consumer<Long>() {
                    @Override
                    public void accept(Long aLong) {
                        /*如果暂停或者停止状态延迟,不需要继续发送回调,影响显示*/
                        if (downInfo.getState() == DownState.PAUSE || downInfo.getState() == DownState.STOP)
                            return;
                        downInfo.setState(DownState.DOWN);
                        mHttpProgressOnNextListener.get().updateProgress(aLong, downInfo.getCountLength());
                    }
                });
    }
}
}

第四步listener 包

HttpProgressOnNextListener类

/**
 * 下载过程中的回调处理
 */
public abstract class HttpProgressOnNextListener<T> {

  /**
 * 成功后回调方法
 *
 * @param t 下载成功之后文件信息
 */
public abstract void onNext(T t);


/**
 * 完成下载
 * 主动调用,更加灵活
 */
public void onComplete(){

}


/**
 * 下载进度
 *
 * @param readLength  当前下载进度
 * @param countLength 文件总长度
 */
public abstract void updateProgress(long readLength, long countLength);

/**
 * 失败或者错误方法
 * 主动调用,更加灵活
 *
 * @param e 下载失败异常
 */
public void onError(Throwable e) {

}






  ///**
  //* 开始下载
  //  */
  //public abstract void onStart();

  / /**
 //  * 暂停下载
//  */
// public void onPuase() {
//
// }
//
// /**
//  * 停止下载销毁
//  */
// public void onStop() {
//
// }
  }

第五步:entity包

VersionInfo 类
/**
 * 版本更新实体类
 */
public class VersionInfo implements Serializable {

private String downloadUrl;
private Integer versionCode;
private String versionName;
private String updateMessage;

public String getDownloadUrl() {
    return downloadUrl;
}

public void setDownloadUrl(String downloadUrl) {
    this.downloadUrl = downloadUrl;
}

public Integer getVersionCode() {
    return versionCode;
}

public void setVersionCode(Integer versionCode) {
    this.versionCode = versionCode;
}

public String getVersionName() {
    return versionName;
}

public void setVersionName(String versionName) {
    this.versionName = versionName;
}

public String getUpdateMessage() {
    return updateMessage;
}

public void setUpdateMessage(String updateMessage) {
    this.updateMessage = updateMessage;
}
}

第六步:request 包

ApiService 类

 public interface ApiService {

/**
 * 版本检测
 */
@GET(Constants.UPDATE_URL)
Observable<Response<VersionInfo>> checkVersion();

  }
OkHttpUtils 类
    public class OkHttpUtils {
/**
 * okhttp
 */
private static OkHttpClient okHttpClient;

/**
 * Retrofit
 */
private static Retrofit retrofit;

/**
 * 获取Retrofit的实例
 *
 * @return retrofit
 */
public static Retrofit getRetrofit() {
    if (retrofit == null) {
        retrofit = new Retrofit.Builder()
                .baseUrl("https://raw.githubusercontent.com/wj576038874/mvp-rxjava-retrofit-okhttp/master/")
                .addConverterFactory(GsonConverterFactory.create())
                .client(getOkHttpClient())
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .build();
    }
    return retrofit;
}

private static OkHttpClient getOkHttpClient() {
    if (okHttpClient == null) {
        OkHttpClient.Builder builder = new OkHttpClient.Builder();
        builder.connectTimeout(15 * 1000, TimeUnit.SECONDS);
        builder.readTimeout(15 * 1000, TimeUnit.MILLISECONDS);//超时时间
        okHttpClient = builder.build();
    }
    return okHttpClient;
}
}
RequestSubscriber类
/**
 * 请求被订阅者
 * @param <T>
 */
public abstract class RequestSubscriber<T> implements Observer<T> {

/**
 * 定义一个请求成功的抽象方法 子类必须实现并在实现中进行处理服务器返回的数据
 *
 * @param t 服务器返回的数据
 */
protected abstract void onSuccess(T t);

/**
 * 定义一个请求失败的抽象方法 子类必须实现并在实现中进行服务器返回数据的处理
 *
 * @param msg 服务器返回的错误信息
 */
protected abstract void onFailure(String msg);

@Override
public void onSubscribe(Disposable d) {

}

@Override
public void onNext(T t) {
    /*
     * 请求成功将数据发出去
     */
    onSuccess(t);
}

@Override
public void onError(Throwable e) {
    String msg;
    if (e instanceof SocketTimeoutException) {
        msg = "请求超时。请稍后重试!";
    } else if (e instanceof ConnectException) {
        msg = "请求超时。请稍后重试!";
    } else {
        msg = "请求未能成功,请稍后重试!";
    }
    if (!TextUtils.isEmpty(msg)) {
        onFailure(msg);
    }
}

@Override
public void onComplete() {

}
}

第七步: service 包

DownloadService类
  /**
 * 下载服务
 */
public class DownloadService extends Service {

private DownInfo downInfo;
private int oldProgress = 0;
private NotificationHelper notificationHelper;

@Override
public void onCreate() {
    super.onCreate();
    downInfo = new DownInfo();
    notificationHelper = new NotificationHelper(this);
}

@Override
public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
    assert intent != null;
    String urlStr = intent.getStringExtra(Constants.APK_DOWNLOAD_URL);
    downInfo.setUrl(urlStr);
    File dir = StorageUtils.getExternalCacheCustomDirectory(this);
  //File dir = StorageUtils.getExternalCacheDirectory(this);
  //File dir = StorageUtils.getCacheDirectory(this);
    String apkName = urlStr.substring(urlStr.lastIndexOf("/") + 1, urlStr.length());
    File apkFile = new File(dir, apkName);
    downInfo.setSavePath(apkFile.getAbsolutePath());
    downLoadFile();
    return super.onStartCommand(intent, flags, startId);
}


private void downLoadFile() {
    HttpDownManager.getInstance().startDown(downInfo, new HttpProgressOnNextListener<DownInfo>() {
        @Override
        public void onNext(DownInfo downInfo) {
            //收起通知栏
            NotificationBarUtil.setNotificationBarVisibility(DownloadService.this, false);
            //安装
            ApkUtils.installAPk(DownloadService.this, new File(downInfo.getSavePath()));
        }

        @Override
        public void onComplete() {
            notificationHelper.cancel();
            stopSelf();
        }

        @Override
        public void updateProgress(long readLength, long countLength) {
            int progress = (int) (readLength * 100 / countLength);
            // 如果进度与之前进度相等,则不更新,如果更新太频繁,否则会造成界面卡顿
            if (progress != oldProgress) {
                notificationHelper.updateProgress(progress);
            }
            oldProgress = progress;
        }

        @Override
        public void onError(Throwable e) {
            Toast.makeText(DownloadService.this, e.getMessage(), Toast.LENGTH_SHORT).show();
        }
    });
}


@Override
public void onDestroy() {
    super.onDestroy();
}

@Override
public IBinder onBind(Intent intent) {
    return null;
}
}

第八步:utils 包

ApkUtils 类

 public class ApkUtils {

/**
 * 安装apk
 * @param context context
 * @param apkFile 安装文件
 */
public static void installAPk(Context context, File apkFile) {
    Intent installAPKIntent = getApkInStallIntent(context, apkFile);
    context.startActivity(installAPKIntent);

}

/**
 *  获取安装文件意图
 * @param context context
 * @param apkFile 安装文件
 * @return 安装意图
 */
private static Intent getApkInStallIntent(Context context, File apkFile) {
    Intent intent = new Intent(Intent.ACTION_VIEW);
    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

    if (Build.VERSION.SDK_INT > Build.VERSION_CODES.M) {
        Uri uri = FileProvider.getUriForFile(context, "com.winfo.update.provider", apkFile);
        intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
        intent.setDataAndType(uri, "application/vnd.android.package-archive");
    } else {
        Uri uri = getApkUri(apkFile);
        intent.setDataAndType(uri, "application/vnd.android.package-archive");
    }
    return intent;
}

/**
 * 获取安装文件的Uri
 * @param apkFile 安装文件
 * @return Uri
 */
private static Uri getApkUri(File apkFile) {
    //如果没有设置 SDCard 写权限,或者没有 SDCard,apk 文件保存在内存中,需要授予权限才能安装
    try {
        String[] command = {"chmod", "777", apkFile.toString()};
        ProcessBuilder builder = new ProcessBuilder(command);
        builder.start();
    } catch (IOException ignored) {
    }
    return Uri.fromFile(apkFile);
}

}
AppUtils 类
    public class AppUtils {

/**
 * 获取当前应用的版本号
 * @param mContext context
 * @return 版本号
 */
public static int getVersionCode(Context mContext) {
    if (mContext != null) {
        try {
            return mContext.getPackageManager().getPackageInfo(mContext.getPackageName(), 0).versionCode;
        } catch (PackageManager.NameNotFoundException ignored) {
        }
    }
    return 0;
}

/**
 * 获取当前应用的版本名称
 * @param mContext context
 * @return 版本名称
 */
public static String getVersionName(Context mContext) {
    if (mContext != null) {
        try {
            return mContext.getPackageManager().getPackageInfo(mContext.getPackageName(), 0).versionName;
        } catch (PackageManager.NameNotFoundException ignored) {
        }
    }

    return "";
}
}
Constants类
public class Constants {

public static final String APK_DOWNLOAD_URL = "downloadUrl";
public static final String UPDATE_URL = "version.json";

}
NotificationBarUtil类
public class NotificationBarUtil {

/**
 * 设置通知栏是否收起
 * @param context context
 * @param isVisible 是否收起
 */
@RequiresPermission(EXPAND_STATUS_BAR)
public static void setNotificationBarVisibility(Context context, boolean isVisible) {
    String methodName;
    if (isVisible) {
        methodName = (Build.VERSION.SDK_INT <= 16) ? "expand" : "expandNotificationsPanel";
    } else {
        methodName = (Build.VERSION.SDK_INT <= 16) ? "collapse" : "collapsePanels";
    }
    invokePanels(context, methodName);
}

private static void invokePanels(Context context, String methodName) {
    try {
        @SuppressLint("WrongConstant")
        Object service = context.getSystemService("statusbar");
        @SuppressLint("PrivateApi")
        Class<?> statusBarManager = Class.forName("android.app.StatusBarManager");
        Method expand = statusBarManager.getMethod(methodName);
        expand.invoke(service);
    } catch (Exception e) {
        e.printStackTrace();
    }
}
}
NotificationHelper类
 public class NotificationHelper {

private NotificationManager manager;

private Context mContext;

private static String CHANNEL_ID = "dxy_app_update";

private static final int NOTIFICATION_ID = 0;

public NotificationHelper(Context context) {
    this.mContext = context;
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        NotificationChannel mChannel = new NotificationChannel(CHANNEL_ID, "应用更新", NotificationManager.IMPORTANCE_NONE);
        mChannel.setDescription("应用有新版本");
        mChannel.enableLights(true); //是否在桌面icon右上角展示小红点
        mChannel.setShowBadge(true);
        getManager().createNotificationChannel(mChannel);
    }
}

/**
 * 显示Notification
 */
public void showNotification(String content, String apkUrl) {

    Intent myIntent = new Intent(mContext, DownloadService.class);
    myIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    myIntent.putExtra(Constants.APK_DOWNLOAD_URL, apkUrl);
    PendingIntent pendingIntent = PendingIntent.getService(mContext, 0, myIntent, PendingIntent.FLAG_UPDATE_CURRENT);

    NotificationCompat.Builder builder = getNofity(content)
            .setContentIntent(pendingIntent);

    getManager().notify(NOTIFICATION_ID, builder.build());
}

/**
 * 不断调用次方法通知通知栏更新进度条
 */
public void updateProgress(int progress) {

    String text = mContext.getString(R.string.android_auto_update_download_progress, progress);

    PendingIntent pendingintent = PendingIntent.getActivity(mContext, 0, new Intent(), PendingIntent.FLAG_UPDATE_CURRENT);

    NotificationCompat.Builder builder = getNofity(text)
            .setProgress(100, progress, false)
            .setContentIntent(pendingintent);

    getManager().notify(NOTIFICATION_ID, builder.build());
}

private NotificationCompat.Builder getNofity(String text) {
    return new NotificationCompat.Builder(mContext.getApplicationContext(), CHANNEL_ID)
            .setTicker(mContext.getString(R.string.android_auto_update_notify_ticker))
            .setContentTitle("版本更新")
            .setContentText(text)
            .setSmallIcon(R.mipmap.ic_launcher)
            .setAutoCancel(true)
            .setOnlyAlertOnce(true)
            .setWhen(System.currentTimeMillis())
            .setPriority(NotificationCompat.PRIORITY_HIGH);

}

public void cancel() {
    getManager().cancel(NOTIFICATION_ID);
}


private NotificationManager getManager() {
    if (manager == null) {
        manager = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
    }
    return manager;
}
}
StorageUtils类
public class StorageUtils {


/*
 * context.getCacheDir()和context.getExternalCacheDir()
 * 目录的路径不同。
 * 前者的目录存在外部SD卡上的。在手机里可以直接看到
 * 后者的目录存在app的内部存储上,需要root以后,用Root Explorer 文件管理器才能看到
 */

/**
 * 获取应用的缓存目录
 * 路径需要root以后,用Root Explorer 文件管理器才能看到
 */
public static File getCacheDirectory(Context context) {
    File appCacheDir = context.getCacheDir();
    if (appCacheDir == null) {
        Log.w("StorageUtils", "Can't define system cache directory! The app should be re-installed.");
    }
    return appCacheDir;
}

/**
 * 获取应用的缓存目录 路径在手机里可以直接看到
 * apk下载路径为:SDCard/Android/data/com.winfo.update/cache/
 */
public static File getExternalCacheDirectory(Context context) {
    File appCacheDir = context.getExternalCacheDir();
    if (appCacheDir == null) {
        Log.w("StorageUtils", "Can't define system cache directory! The app should be re-installed.");
    }
    return appCacheDir;
}

/**
 * 在cache下新增自定义缓存路径
 * apk下载路径为:SDCard/Android/data/com.winfo.update/cache/update_file/
 */
public static File getExternalCacheCustomDirectory(Context context) {
    //在SDCard/Android/data/com.winfo.update/cache/update_file创建文件夹
    File appCacheDir = new File(context.getExternalCacheDir(), "update");
    //如果不存在就创建
    if (!appCacheDir.exists()) {
        if (appCacheDir.mkdirs()) {//创建成功就返回SDCard/Android/data/com.winfo.update/cache/update_file/
            return appCacheDir;
        } else {
            //创建失败就返回默认的SDCard/Android/data/com.winfo.update/cache/
            return context.getExternalCacheDir();
        }
    } else {
        //存在直接返回
        return appCacheDir;
    }
}
 }
UpdateChecker类
     /**
 * 对话框检测
 *
 * @param mContext context
 * @param dialog   加载框
 */
public static void checkForDialog(final Context mContext, final Dialog dialog) {
    dialog.show();
    Observable<Response<VersionInfo>> observable = OkHttpUtils.getRetrofit().create(ApiService.class).checkVersion();
    Observer<Response<VersionInfo>> observer = new RequestSubscriber<Response<VersionInfo>>() {
        @Override
        protected void onSuccess(Response<VersionInfo> versionInfoResponse) {
            dialog.dismiss();
            if (versionInfoResponse.isSuccessful()) {
                VersionInfo versionInfo = versionInfoResponse.body();
                if (versionInfo != null) {
                    int apkCode = versionInfo.getVersionCode();
                    int versionCode = AppUtils.getVersionCode(mContext);
                    if (apkCode > versionCode) {
                        UpdateDialog.show(mContext, versionInfo.getUpdateMessage(), versionInfo.getDownloadUrl());
                    } else {
                        Toast.makeText(mContext, mContext.getString(R.string.android_auto_update_toast_no_new_update), Toast.LENGTH_SHORT).show();
                    }
                } else {
                    Toast.makeText(mContext, "请求失败了", Toast.LENGTH_SHORT).show();
                }
            } else {
                Toast.makeText(mContext, "请求失败了", Toast.LENGTH_SHORT).show();
            }
        }

        @Override
        protected void onFailure(String msg) {
            dialog.dismiss();
            Toast.makeText(mContext, msg, Toast.LENGTH_SHORT).show();
        }
    };
    observable.subscribeOn(Schedulers.io())
            .unsubscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(observer);
}

/**
 * 通知栏通知
 *
 * @param mContext context
 * @param dialog   加载框
 */
public static void checkForNotification(final Context mContext, final Dialog dialog) {
    dialog.show();
    Observable<Response<VersionInfo>> observable = OkHttpUtils.getRetrofit().create(ApiService.class).checkVersion();
    Observer<Response<VersionInfo>> observer = new RequestSubscriber<Response<VersionInfo>>() {
        @Override
        protected void onSuccess(Response<VersionInfo> versionInfoResponse) {
            dialog.dismiss();
            if (versionInfoResponse.isSuccessful()) {
                VersionInfo versionInfo = versionInfoResponse.body();
                if (versionInfo != null) {
                    int apkCode = versionInfo.getVersionCode();
                    int versionCode = AppUtils.getVersionCode(mContext);
                    if (apkCode > versionCode) {
                        new NotificationHelper(mContext).showNotification(versionInfo.getUpdateMessage(), versionInfo.getDownloadUrl());
                    } else {
                        Toast.makeText(mContext, mContext.getString(R.string.android_auto_update_toast_no_new_update), Toast.LENGTH_SHORT).show();
                    }
                } else {
                    Toast.makeText(mContext, "请求没有成功", Toast.LENGTH_SHORT).show();
                }
            } else {
                Toast.makeText(mContext, "请求没有成功", Toast.LENGTH_SHORT).show();
            }
        }

        @Override
        protected void onFailure(String msg) {
            dialog.dismiss();
            Toast.makeText(mContext, msg, Toast.LENGTH_SHORT).show();
        }
    };
    observable.subscribeOn(Schedulers.io())
            .unsubscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(observer);
}
}
UpdateDialog类
  /**
 * 弹出对话框提示更新信息,可自定义
 */
 public class UpdateDialog {

/**
 * 显示对话框
 *
 * @param context     context
 * @param content     更新内容
 * @param downloadUrl apk下载地址
 */
public static void show(final Context context, String content, final String downloadUrl) {
    if (isContextValid(context)) {
        new AlertDialog.Builder(context)
                .setTitle(R.string.android_auto_update_dialog_title)
                .setMessage(content)
                .setPositiveButton(R.string.android_auto_update_dialog_btn_download, new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int id) {
                        goToDownload(context, downloadUrl);
                    }
                })
                .setNegativeButton(R.string.android_auto_update_dialog_btn_cancel, new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int id) {
                        dialog.dismiss();
                    }
                })
                .setCancelable(false)
                .show();
    }
}

/**
 * 检测context是否是Activity
 *
 * @param context 上下文
 * @return 是否
 */
private static boolean isContextValid(Context context) {
    return context instanceof Activity && !((Activity) context).isFinishing();
}

/**
 * 启动服务传递下载地址进行下载
 *
 * @param context     activity
 * @param downloadUrl 下载地址
 */
private static void goToDownload(Context context, String downloadUrl) {
    Intent intent = new Intent(context.getApplicationContext(), DownloadService.class);
    intent.putExtra(Constants.APK_DOWNLOAD_URL, downloadUrl);
    context.startService(intent);
}
}

第九步 xml

  <?xml version="1.0" encoding="utf-8"?>
  <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"
android:orientation="vertical"
tools:context=".MainActivity">


<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <Button
            android:id="@+id/btn_startDown_qq"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="下载QQ" />

        <Button
            android:id="@+id/btn_pauseDown_qq"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:tag="true"
            android:text="暂停下载" />
    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <ProgressBar
            android:id="@+id/progressBar_qq"
            style="?android:attr/progressBarStyleHorizontal"
            android:layout_width="0dp"
            android:layout_height="3dp"
            android:layout_gravity="center_vertical"
            android:layout_marginLeft="10dp"
            android:layout_marginRight="10dp"
            android:layout_weight="1" />

        <TextView
            android:id="@+id/tv_text_qq"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_vertical"
            android:text="0%" />

    </LinearLayout>

    <TextView
        android:id="@+id/tv_msg_qq"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center" />
</LinearLayout>


<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <Button
            android:id="@+id/btn_startDown_alipay"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="下载支付宝" />

        <Button
            android:id="@+id/btn_pauseDown_alipay"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:tag="true"
            android:text="暂停下载" />
    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <ProgressBar
            android:id="@+id/progressBar_alipay"
            style="?android:attr/progressBarStyleHorizontal"
            android:layout_width="0dp"
            android:layout_height="3dp"
            android:layout_gravity="center_vertical"
            android:layout_marginLeft="10dp"
            android:layout_marginRight="10dp"
            android:layout_weight="1" />

        <TextView
            android:id="@+id/tv_text_alipay"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_vertical"
            android:text="0%" />

    </LinearLayout>

    <TextView
        android:id="@+id/tv_msg_alipay"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center" />
</LinearLayout>

<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <Button
            android:id="@+id/btn_startDown_weixin"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="下载微信" />

        <Button
            android:id="@+id/btn_pauseDown_weixin"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:tag="true"
            android:text="暂停下载" />
    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <ProgressBar
            android:id="@+id/progressBar_weixin"
            style="?android:attr/progressBarStyleHorizontal"
            android:layout_width="0dp"
            android:layout_height="3dp"
            android:layout_gravity="center_vertical"
            android:layout_marginLeft="10dp"
            android:layout_marginRight="10dp"
            android:layout_weight="1" />

        <TextView
            android:id="@+id/tv_text_weixin"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_vertical"
            android:text="0%" />

    </LinearLayout>

    <TextView
        android:id="@+id/tv_msg_weixin"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center" />
</LinearLayout>

<Button
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_marginTop="50dp"
    android:onClick="updateDialog"
    android:text="对话框版本更新" />

<Button
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:onClick="updateNotification"
    android:text="通知栏版本更新" />


</LinearLayout>

最后一步 MainActivity

 public class MainActivity extends AppCompatActivity implements View.OnClickListener {

private ProgressBar qqProgressBar, alipayProgressBar, weixinProgressbar;
private TextView tvQQMsg, tvAlipayMsg, tvWeixinMsg;
private TextView tvQQProgress, tvAlipayProgress, tvWeixinProgress;
private Button btnQQStart, btnQQPause, btnAlipayStart, btnAlipayPause, btnWeixinStart, btnWeixinPause;

private DownInfo qqDownInfo, alipayDownInfo, weixinDownInfo;

private ProgressDialog dialog;


@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    dialog = new ProgressDialog(this);
    dialog.setMessage(getString(R.string.android_auto_update_dialog_checking));

    qqProgressBar = findViewById(R.id.progressBar_qq);
    alipayProgressBar = findViewById(R.id.progressBar_alipay);
    weixinProgressbar = findViewById(R.id.progressBar_weixin);

    tvQQProgress = findViewById(R.id.tv_text_qq);
    tvAlipayProgress = findViewById(R.id.tv_text_alipay);
    tvWeixinProgress = findViewById(R.id.tv_text_weixin);

    tvQQMsg = findViewById(R.id.tv_msg_qq);
    tvAlipayMsg = findViewById(R.id.tv_msg_alipay);
    tvWeixinMsg = findViewById(R.id.tv_msg_weixin);

    btnQQStart = findViewById(R.id.btn_startDown_qq);
    btnQQPause = findViewById(R.id.btn_pauseDown_qq);

    btnAlipayStart = findViewById(R.id.btn_startDown_alipay);
    btnAlipayPause = findViewById(R.id.btn_pauseDown_alipay);

    btnWeixinStart = findViewById(R.id.btn_startDown_weixin);
    btnWeixinPause = findViewById(R.id.btn_pauseDown_weixin);

    btnQQStart.setOnClickListener(this);
    btnQQPause.setOnClickListener(this);
    btnAlipayStart.setOnClickListener(this);
    btnAlipayPause.setOnClickListener(this);
    btnWeixinStart.setOnClickListener(this);
    btnWeixinPause.setOnClickListener(this);

    String weixinDwonloadUrl = "http://dldir1.qq.com/weixin/android/weixin667android1320.apk";
    weixinDownInfo = new DownInfo(weixinDwonloadUrl);
    String weixinApkName = weixinDwonloadUrl.substring(weixinDwonloadUrl.lastIndexOf("/") + 1, weixinDwonloadUrl.length());
    File weixinApkFile = new File(getExternalCacheDir(), weixinApkName);
    weixinDownInfo.setSavePath(weixinApkFile.getAbsolutePath());
    weixinDownInfo.setState(DownState.START);


    String qqDwonloadUrl = "https://qd.myapp.com/myapp/qqteam/AndroidQQ/mobileqq_android.apk";
    qqDownInfo = new DownInfo(qqDwonloadUrl);
    String qqApkName = qqDwonloadUrl.substring(qqDwonloadUrl.lastIndexOf("/") + 1, qqDwonloadUrl.length());
    File qqApkFile = new File(getExternalCacheDir(), qqApkName);
    qqDownInfo.setSavePath(qqApkFile.getAbsolutePath());
    qqDownInfo.setState(DownState.START);

    String alipayDwonloadUrl = "http://gdown.baidu.com/data/wisegame/87b1af6e50012cb5/zhifubao_128.apk";
    alipayDownInfo = new DownInfo(alipayDwonloadUrl);
    String alipayApkName = alipayDwonloadUrl.substring(alipayDwonloadUrl.lastIndexOf("/") + 1, alipayDwonloadUrl.length());
    File alipayApkFile = new File(getExternalCacheDir(), alipayApkName);
    alipayDownInfo.setSavePath(alipayApkFile.getAbsolutePath());
    alipayDownInfo.setState(DownState.START);

}

@SuppressLint("SetTextI18n")
@Override
public void onClick(View v) {
    switch (v.getId()) {
        case R.id.btn_startDown_qq:
            HttpDownManager.getInstance().startDown(qqDownInfo, new HttpProgressOnNextListener<DownInfo>() {

                @Override
                public void onNext(DownInfo downInfo) {
                    tvQQMsg.setText("QQ下载完成" + downInfo.getSavePath());
                }

                @Override
                public void updateProgress(long readLength, long countLength) {
                    qqProgressBar.setMax((int) countLength);
                    qqProgressBar.setProgress((int) readLength);
                    tvQQProgress.setText(readLength * 100 / countLength + "%");
                }

                @Override
                public void onError(Throwable e) {
                    tvQQMsg.setText(e.getMessage());
                }
            });
            break;
        case R.id.btn_pauseDown_qq:
            String tag = (String) btnQQPause.getTag();
            if (tag.equals("true")) {
                btnQQPause.setText("继续下载");
                btnQQPause.setTag("false");
                HttpDownManager.getInstance().pause(qqDownInfo);
            } else {
                btnQQPause.setText("暂停下载");
                btnQQPause.setTag("true");
                HttpDownManager.getInstance().continueDownload(qqDownInfo);
            }

            break;
        case R.id.btn_startDown_alipay:
            HttpDownManager.getInstance().startDown(alipayDownInfo, new HttpProgressOnNextListener<DownInfo>() {

                @Override
                public void onNext(DownInfo downInfo) {
                    tvAlipayMsg.setText("下载完成" + downInfo.getSavePath());
                }

                @Override
                public void updateProgress(long readLength, long countLength) {
                    alipayProgressBar.setMax((int) countLength);
                    alipayProgressBar.setProgress((int) readLength);
                    tvAlipayProgress.setText(readLength * 100 / countLength + "%");
                }

                @Override
                public void onError(Throwable e) {
                    tvAlipayMsg.setText(e.getMessage());
                }
            });
            break;
        case R.id.btn_pauseDown_alipay:
            String tag1 = (String) btnAlipayPause.getTag();
            if (tag1.equals("true")) {
                btnAlipayPause.setText("继续下载");
                btnAlipayPause.setTag("false");
                HttpDownManager.getInstance().pause(alipayDownInfo);
            } else {
                btnAlipayPause.setText("暂停下载");
                btnAlipayPause.setTag("true");
                HttpDownManager.getInstance().continueDownload(alipayDownInfo);
            }
            break;
        case R.id.btn_startDown_weixin:
            HttpDownManager.getInstance().startDown(weixinDownInfo, new HttpProgressOnNextListener<DownInfo>() {

                @Override
                public void onNext(DownInfo downInfo) {
                    tvWeixinMsg.setText("下载完成" + downInfo.getSavePath());
                }

                @Override
                public void updateProgress(long readLength, long countLength) {
                    weixinProgressbar.setMax((int) countLength);
                    weixinProgressbar.setProgress((int) readLength);
                    tvWeixinProgress.setText(readLength * 100 / countLength + "%");
                }

                @Override
                public void onError(Throwable e) {
                    tvWeixinMsg.setText(e.getMessage());
                }
            });
            break;
        case R.id.btn_pauseDown_weixin:
            String tag2 = (String) btnWeixinPause.getTag();
            if (tag2.equals("true")) {
                btnWeixinPause.setText("继续下载");
                btnWeixinPause.setTag("false");
                HttpDownManager.getInstance().pause(weixinDownInfo);
            } else {
                btnWeixinPause.setText("暂停下载");
                btnWeixinPause.setTag("true");
                HttpDownManager.getInstance().continueDownload(weixinDownInfo);
            }
            break;
    }
}


public void updateDialog(View view) {
    UpdateChecker.checkForDialog(this, dialog);
}

public void updateNotification(View view) {
    UpdateChecker.checkForNotification(this, dialog);
}
}

配置问件

strings 中
  <resources>
<string name="app_name">DownloadUpdateDemo</string>

<string name="android_auto_update_dialog_title">发现新版本</string>

<string name="android_auto_update_notify_ticker">发现新版本,点击进行升级</string>
<string name="android_auto_update_notify_content">发现新版本,点击进行升级</string>

<string name="android_auto_update_dialog_btn_download">立即下载</string>
<string name="android_auto_update_dialog_btn_cancel">以后再说</string>

<string name="android_auto_update_toast_no_new_update">已经是最新版本</string>

<string name="android_auto_update_download_progress">正在下载:%1$d%%</string>

<string name="android_auto_update_dialog_checking">正在检查版本</string>
</resources>
style中
    <resources>

<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
    <!-- Customize your theme here. -->
    <item name="colorPrimary">@color/colorPrimary</item>
    <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
    <item name="colorAccent">@color/colorAccent</item>
</style>

</resources>
res下 xml 包中的update_apk_paths.xml
<?xml version="1.0" encoding="utf-8"?>
<paths>

<cache-path name="cache-path" path="." /><!--name自定义   .为根路径-->

<external-cache-path name="external-cache-path" path="." /><!--name自定义  .为SDCard/Android/data/应用包名/cache/-->

<!--name自定义  update_file为SDCard/Android/data/应用包名/cache/update/和gettExternalCacheDirectory对应创建的文件夹保持一致-->
<external-cache-path name="external-cache--custom-path" path="update" />
</paths>

最最后一步:清单问件中

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.winfo.update">

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.EXPAND_STATUS_BAR" />

<application
    android:allowBackup="false"
    android:icon="@mipmap/ic_launcher"
    android:label="@string/app_name"
    android:roundIcon="@mipmap/ic_launcher_round"
    android:supportsRtl="true"
    android:theme="@style/AppTheme">
    <activity android:name=".MainActivity">
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />

            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity>

    <service
        android:name=".version_update.service.DownloadService"
        android:exported="false" />

    <provider
        android:name="android.support.v4.content.FileProvider"
        android:authorities="com.winfo.update.provider"
        android:exported="false"
        android:grantUriPermissions="true">

        <meta-data
            android:name="android.support.FILE_PROVIDER_PATHS"
            android:resource="@xml/update_apk_paths" />

    </provider>

</application>

</manifest>
image

githip地址 https://github.com/xuezhihuixzh/Downloadupdate.git

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,056评论 5 474
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,842评论 2 378
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,938评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,296评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,292评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,413评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,824评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,493评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,686评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,502评论 2 318
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,553评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,281评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,820评论 3 305
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,873评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,109评论 1 258
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,699评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,257评论 2 341