成年人的世界:
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