Android 版本更新(對話框和通知欄)下載和進(jìn)度條下載(適合百分之80的項(xiàng)目)

成年人的世界:

1:在成年人的世界里,是沒有“崩潰”這個(gè)選項(xiàng)的。

2:他們需要小心翼翼地發(fā)泄,精打細(xì)算地緩解,并在最短的時(shí)間范圍內(nèi)恢復(fù)到正常。

3:他們不會在真正的大事面前倒下,因?yàn)橹雷约荷砗鬀]有人,或者,身后有人。他們只不過是在平淡的瑣事中感到悲傷與憤怒。

效果圖:

圖一:


超級截屏_20190731_094936.png

圖二:


超級截屏_20190731_095002.png

圖三:


超級截屏_20190731_095036.png

圖四:

image.png

雖然代碼很多, 我會為大家依次粘貼(按包粘貼)

項(xiàng)目目錄:


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類
/**
 * 自定義錯(cuò)誤信息,統(tǒng)一處理返回處理
 */
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);
}

/**
 * 轉(zhuǎn)換錯(cuò)誤數(shù)據(jù)
 *
 * @param code 錯(cuò)誤嗎
 * @return 錯(cuò)誤信息
 */
private static String getApiExceptionMessage(int code) {
    String message;
    switch (code) {
        case NO_DATA:
            message = "無數(shù)據(jù)";
            break;
        default:
            message = "error";
            break;
    }
    return message;
}
}
RetryWhenNetworkException 類
 /**
 * retry條件
 */
public class RetryWhenNetworkException implements Function<Observable<? extends Throwable>, ObservableSource<?>> {

//    retry次數(shù)
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) { //如果超出重試次數(shù)也拋出錯(cuò)誤,否則默認(rèn)是會進(jìn)入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統(tǒng)一接口數(shù)據(jù)
 */
public interface HttpService {

//斷點(diǎn)續(xù)傳下載接口
@Streaming//大文件需要加入這個(gè)判斷,防止下載過程中寫入到內(nèi)存中
@Headers("Content-type:application/octet-stream")
@GET
Observable<ResponseBody> download(@Header("RANGE") String start, @Url String url);
}

第三步 httpdowonload包

DownloadInterceptor類
    /**
   * 成功回調(diào)處理
   * 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 類
  /**
 * 成功回調(diào)處理
 * Created by pc12 on 2019/7/31.
 */

public interface DownloadProgressListener {
/**
 * 下載進(jìn)度
 * @param read 進(jìn)度
 * @param count 總長度
 */
void update(long read, long count, boolean done);
  }
DownloadResponseBody 類
  /**
 * 自定義進(jìn)度的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;
//基礎(chǔ)url
private String baseUrl;
//文件總長度
private long countLength;
//下載長度
private long readLength;
//下載唯一的HttpService
private HttpService service;
//回調(diào)監(jiān)聽
//private HttpProgressOnNextListener listener;
//超時(shí)設(shè)置
private int DEFAULT_TIMEOUT = 6;
//下載狀態(tài)
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類

/**
 * 下載狀態(tài)
 */

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

}
HttpDownManager類
/**
 * http下載處理類
 * Created by pc12 on 2019/7/31.
 */

public class HttpDownManager {
//記錄下載數(shù)據(jù)
private Set<DownInfo> downInfos;
//回調(diào)sub隊(duì)列
private HashMap<String, ProgressDownSubscriber> subMap;
//進(jìn)度監(jiān)聽隊(duì)列
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;
}

/**
 * 繼續(xù)下載
 */
public void continueDownload(DownInfo downInfo) {
    //根據(jù)下載的url獲取到進(jìn)度監(jiān)聽隊(duì)列中的監(jiān)聽器
    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;
    }
    //添加回調(diào)處理類
    ProgressDownSubscriber<DownInfo> subscriber = new ProgressDownSubscriber<>(info, httpProgressOnNextListener);
    //將監(jiān)聽進(jìn)度的器添加到隊(duì)列中,以便于繼續(xù)下載時(shí)獲取
    mProgressListenerHashMap.put(info.getUrl(), httpProgressOnNextListener);
    //記錄回調(diào)sub
    subMap.put(info.getUrl(), subscriber);
    //獲取service,多次請求公用一個(gè)service
    HttpService httpService;
    //設(shè)置文件的狀態(tài)為下載
    info.setState(DownState.START);
    if (downInfos.contains(info)) {
        httpService = info.getService();
    } else {
        DownloadInterceptor interceptor = new DownloadInterceptor(subscriber);
        OkHttpClient.Builder builder = new OkHttpClient.Builder();
        //手動創(chuàng)建一個(gè)OkHttpClient并設(shè)置超時(shí)時(shí)間
        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;
                }
            })//回調(diào)線程
            .observeOn(AndroidSchedulers.mainThread())
            //數(shù)據(jù)回調(diào)
            .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();
}


/**
 * 返回全部正在下載的數(shù)據(jù)
 */
@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", "文件創(chuàng)建失敗");
        }
    }
    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請求開始時(shí),自動顯示一個(gè)ProgressDialog
   * 在Http請求結(jié)束是,關(guān)閉ProgressDialog
   * 調(diào)用者自己對請求數(shù)據(jù)進(jìn)行處理
   */
  public class ProgressDownSubscriber<T> implements Observer<T>, DownloadProgressListener {
//弱引用結(jié)果回調(diào)
private WeakReference<HttpProgressOnNextListener<T>> mHttpProgressOnNextListener;

/*下載數(shù)據(jù)*/
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);
    }
}

/**
 * 對錯(cuò)誤進(jìn)行統(tǒng)一處理
 * 隱藏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) {
        /*接受進(jìn)度消息,造成UI阻塞,如果不需要顯示進(jìn)度可去掉實(shí)現(xiàn)邏輯,減少壓力*/
        Observable.just(read).observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Consumer<Long>() {
                    @Override
                    public void accept(Long aLong) {
                        /*如果暫?;蛘咄V?fàn)顟B(tài)延遲,不需要繼續(xù)發(fā)送回調(diào),影響顯示*/
                        if (downInfo.getState() == DownState.PAUSE || downInfo.getState() == DownState.STOP)
                            return;
                        downInfo.setState(DownState.DOWN);
                        mHttpProgressOnNextListener.get().updateProgress(aLong, downInfo.getCountLength());
                    }
                });
    }
}
}

第四步listener 包

HttpProgressOnNextListener類

/**
 * 下載過程中的回調(diào)處理
 */
public abstract class HttpProgressOnNextListener<T> {

  /**
 * 成功后回調(diào)方法
 *
 * @param t 下載成功之后文件信息
 */
public abstract void onNext(T t);


/**
 * 完成下載
 * 主動調(diào)用,更加靈活
 */
public void onComplete(){

}


/**
 * 下載進(jìn)度
 *
 * @param readLength  當(dāng)前下載進(jìn)度
 * @param countLength 文件總長度
 */
public abstract void updateProgress(long readLength, long countLength);

/**
 * 失敗或者錯(cuò)誤方法
 * 主動調(diào)用,更加靈活
 *
 * @param e 下載失敗異常
 */
public void onError(Throwable e) {

}






  ///**
  //* 開始下載
  //  */
  //public abstract void onStart();

  / /**
 //  * 暫停下載
//  */
// public void onPuase() {
//
// }
//
// /**
//  * 停止下載銷毀
//  */
// public void onStop() {
//
// }
  }

第五步:entity包

VersionInfo 類
/**
 * 版本更新實(shí)體類
 */
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的實(shí)例
 *
 * @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);//超時(shí)時(shí)間
        okHttpClient = builder.build();
    }
    return okHttpClient;
}
}
RequestSubscriber類
/**
 * 請求被訂閱者
 * @param <T>
 */
public abstract class RequestSubscriber<T> implements Observer<T> {

/**
 * 定義一個(gè)請求成功的抽象方法 子類必須實(shí)現(xiàn)并在實(shí)現(xiàn)中進(jìn)行處理服務(wù)器返回的數(shù)據(jù)
 *
 * @param t 服務(wù)器返回的數(shù)據(jù)
 */
protected abstract void onSuccess(T t);

/**
 * 定義一個(gè)請求失敗的抽象方法 子類必須實(shí)現(xiàn)并在實(shí)現(xiàn)中進(jìn)行服務(wù)器返回?cái)?shù)據(jù)的處理
 *
 * @param msg 服務(wù)器返回的錯(cuò)誤信息
 */
protected abstract void onFailure(String msg);

@Override
public void onSubscribe(Disposable d) {

}

@Override
public void onNext(T t) {
    /*
     * 請求成功將數(shù)據(jù)發(fā)出去
     */
    onSuccess(t);
}

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

@Override
public void onComplete() {

}
}

第七步: service 包

DownloadService類
  /**
 * 下載服務(wù)
 */
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);
            // 如果進(jìn)度與之前進(jìn)度相等,則不更新,如果更新太頻繁,否則會造成界面卡頓
            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) {
    //如果沒有設(shè)置 SDCard 寫權(quán)限,或者沒有 SDCard,apk 文件保存在內(nèi)存中,需要授予權(quán)限才能安裝
    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 {

/**
 * 獲取當(dāng)前應(yīng)用的版本號
 * @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;
}

/**
 * 獲取當(dāng)前應(yīng)用的版本名稱
 * @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 {

/**
 * 設(shè)置通知欄是否收起
 * @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, "應(yīng)用更新", NotificationManager.IMPORTANCE_NONE);
        mChannel.setDescription("應(yīng)用有新版本");
        mChannel.enableLights(true); //是否在桌面icon右上角展示小紅點(diǎn)
        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());
}

/**
 * 不斷調(diào)用次方法通知通知欄更新進(jìn)度條
 */
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卡上的。在手機(jī)里可以直接看到
 * 后者的目錄存在app的內(nèi)部存儲上,需要root以后,用Root Explorer 文件管理器才能看到
 */

/**
 * 獲取應(yīng)用的緩存目錄
 * 路徑需要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;
}

/**
 * 獲取應(yīng)用的緩存目錄 路徑在手機(jī)里可以直接看到
 * 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創(chuàng)建文件夾
    File appCacheDir = new File(context.getExternalCacheDir(), "update");
    //如果不存在就創(chuàng)建
    if (!appCacheDir.exists()) {
        if (appCacheDir.mkdirs()) {//創(chuàng)建成功就返回SDCard/Android/data/com.winfo.update/cache/update_file/
            return appCacheDir;
        } else {
            //創(chuàng)建失敗就返回默認(rèn)的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     更新內(nèi)容
 * @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();
}

/**
 * 啟動服務(wù)傳遞下載地址進(jìn)行下載
 *
 * @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("繼續(xù)下載");
                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("繼續(xù)下載");
                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("繼續(xù)下載");
                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">發(fā)現(xiàn)新版本</string>

<string name="android_auto_update_notify_ticker">發(fā)現(xiàn)新版本,點(diǎn)擊進(jìn)行升級</string>
<string name="android_auto_update_notify_content">發(fā)現(xiàn)新版本,點(diǎn)擊進(jìn)行升級</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">已經(jīng)是最新版本</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/應(yīng)用包名/cache/-->

<!--name自定義  update_file為SDCard/Android/data/應(yīng)用包名/cache/update/和gettExternalCacheDirectory對應(yīng)創(chuàng)建的文件夾保持一致-->
<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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容