我們主要指的是下載一個(gè)文件,不考慮斷點(diǎn)續(xù)傳。
主要的三種方式AsyncTask、Service和使用DownloadManager
一、如果想要在后臺(tái)下載任務(wù)的同時(shí)可以更新進(jìn)度條UI----使用AsyncTask
asynctask效果gif
忽略安裝需要密碼這個(gè)細(xì)節(jié),用oppo手機(jī)的應(yīng)該知道,這個(gè)是只有oppo測(cè)試機(jī)會(huì)這樣,別的手機(jī)就可以直接安裝了。
要點(diǎn):
-
一個(gè)url下載鏈接,一個(gè)ProgressDialog用來(lái)顯示進(jìn)度,一個(gè)按鈕(點(diǎn)擊升級(jí))按鈕監(jiān)聽(tīng)的方法為onUpdateClick()
-
接著放入主要的代碼
//關(guān)于進(jìn)度顯示
private ProgressDialog progressDialog;
//相關(guān)屬性
progressDialog =new ProgressDialog(UpdateDialogActivity.this);
progressDialog.setMessage("正在下載...");
progressDialog.setIndeterminate(true);
progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
progressDialog.setCancelable(true);
//升級(jí)下載按鈕點(diǎn)擊事件
private void onUpdateClick() {
// TODO: 2017/10/11 三種方式實(shí)現(xiàn)apk下載
//第一種 asynctask
//onProgressUpdate和onPreExecute是運(yùn)行在UI線程中的,
// 所以我們應(yīng)該在這兩個(gè)方法中更新progress。
final DownloadTask downloadTask = new DownloadTask(UpdateDialogActivity.this);
//execute 執(zhí)行一個(gè)異步任務(wù),通過(guò)這個(gè)方法觸發(fā)異步任務(wù)的執(zhí)行。這個(gè)方法要在主線程調(diào)用。
downloadTask.execute(url);
progressDialog.setOnCancelListener(new DialogInterface.OnCancelListener() {
@Override
public void onCancel(DialogInterface dialog) {
downloadTask.cancel(true);
}
});
}
-
最主要的是內(nèi)部類 DownloadTask ,具體的方法說(shuō)明參考注釋以及文章AsyncTask機(jī)制原理分析
private class DownloadTask extends AsyncTask<String,Integer,String> {
private Context context;
private PowerManager.WakeLock mWakeLock;
public DownloadTask(Context context) {
this.context = context;
}
//onPreExecute(),在execute(Params... params)方法被調(diào)用后立即執(zhí)行,執(zhí)行在ui線程,
// 一般用來(lái)在執(zhí)行后臺(tái)任務(wù)前會(huì)UI做一些標(biāo)記
@Override
protected void onPreExecute() {
super.onPreExecute();
// take CPU lock to prevent CPU from going off if the user
// presses the power button during download
PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
getClass().getName());
mWakeLock.acquire();
progressDialog.show();
}
// doInBackground這個(gè)方法在onPreExecute()完成后立即執(zhí)行,
// 用于執(zhí)行較為耗時(shí)的操作,
// 此方法接受輸入?yún)?shù)
// 和返回計(jì)算結(jié)果(返回的計(jì)算結(jié)果將作為參數(shù)在任務(wù)完成是傳遞到onPostExecute(Result result)中),
// 在執(zhí)行過(guò)程中可以調(diào)用publishProgress(Progress... values)來(lái)更新進(jìn)度信息
//后臺(tái)任務(wù)的代碼塊
@Override
protected String doInBackground(String... url) {
InputStream input = null;
OutputStream output = null;
HttpURLConnection connection = null;
try {
URL urll=new URL(url[0]);
Log.d("upgrade","url1:"+urll+"http:////url:"+url);
connection = (HttpURLConnection) urll.openConnection();
connection.connect();
// expect HTTP 200 OK, so we don't mistakenly save error report
// instead of the file
if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) {
return "Server returned HTTP " + connection.getResponseCode()
+ " " + connection.getResponseMessage();
}
// this will be useful to display download percentage
// might be -1: server did not report the length
int fileLength = connection.getContentLength();
// download the file
input = connection.getInputStream();
output = new FileOutputStream("/sdcard/new.apk");
byte data[] = new byte[4096];
long total = 0;
int count;
while ((count = input.read(data)) != -1) {
if (isCancelled()) {
input.close();
return null;
}
total += count;
// publishing the progress....
if (fileLength > 0) // only if total length is known
//在調(diào)用這個(gè)方法后,執(zhí)行onProgressUpdate(Progress... values),
//運(yùn)行在主線程,用來(lái)更新pregressbar
publishProgress((int) (total * 100 / fileLength));
output.write(data, 0, count);
}
} catch (Exception e) {
return e.toString();
} finally {
try {
if (output != null)
output.close();
if (input != null)
input.close();
} catch (IOException ignored) {
}
if (connection != null)
connection.disconnect();
}
return null;
}
//onProgressUpdate(Progress... values),
// 執(zhí)行在UI線程,在調(diào)用publishProgress(Progress... values)時(shí),此方法被執(zhí)行。
@Override
protected void onProgressUpdate(Integer... progress) {
super.onProgressUpdate(progress);
// if we get here, length is known, now set indeterminate to false
progressDialog.setIndeterminate(false);
progressDialog.setMax(100);
progressDialog.setProgress(progress[0]);
}
//onPostExecute(Result result),
// 執(zhí)行在UI線程,當(dāng)后臺(tái)操作結(jié)束時(shí),此方法將會(huì)被調(diào)用。
@Override
protected void onPostExecute(String result) {
mWakeLock.release();
progressDialog.dismiss();
if (result != null)
Toast.makeText(context,"Download error: "+result, Toast.LENGTH_LONG).show();
else
{Toast.makeText(context,"File downloaded", Toast.LENGTH_SHORT).show();}
//這里主要是做下載后自動(dòng)安裝的處理
File file=new File("/sdcard/new.apk");
Intent installIntent = new Intent(Intent.ACTION_VIEW);
installIntent.setDataAndType(Uri.fromFile(file), "application/vnd.android.package-archive");
installIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(installIntent);
}
}
二、使用DownloadManager
每個(gè)Android App都會(huì)有版本更新的功能,而下載功能Google官方推薦使用 DownloadManager服務(wù)
使用最簡(jiǎn)單的一種
download.gif
DownloadManager.Request request = new DownloadManager.Request(Uri.parse(url));
request.setDescription("下載中");
request.setTitle("我的下載");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
}
request.allowScanningByMediaScanner();//設(shè)置可以被掃描到
request.setVisibleInDownloadsUi(true);// 設(shè)置下載可見(jiàn)
request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);//下載完成后通知欄任然可見(jiàn)
request.setDestinationInExternalPublicDir(
Environment.DIRECTORY_DOWNLOADS, "my.apk");
manager = (DownloadManager) getActivity().getSystemService(Context.DOWNLOAD_SERVICE);
// manager.enqueue(request);
long Id = manager.enqueue(request);
//listener(Id);
SharedPreferences sPreferences = getActivity().getSharedPreferences(
"downloadapk", 0);
sPreferences.edit().putLong("apk",Id).commit();//保存此次下載ID
Log.d("shengji", "開(kāi)始下載任務(wù):" + Id + " ...");
如果想同樣實(shí)現(xiàn)下載完安裝,要使用廣播.當(dāng)DownloadManager下載完成后會(huì)發(fā)出一個(gè)廣播 android.intent.action.DOWNLOAD_COMPLETE,創(chuàng)建一個(gè)廣播接收者,處理自動(dòng)提示安裝:
public class DownLoadBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
long completeId = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1);
Log.d("shengji","下載完成后的ID:"+completeId);
SharedPreferences sPreferences =context.getSharedPreferences(
"downloadapk", 0);
long Id = sPreferences.getLong("apk", 0);
if (Id==completeId){
DownloadManager manager =
(DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
Intent installIntent=new Intent(Intent.ACTION_VIEW);
Uri downloadFileUri = manager
.getUriForDownloadedFile(completeId);
installIntent.setDataAndType(downloadFileUri,
"application/vnd.android.package-archive");
installIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(installIntent);
}
}
}
在AndroidManifet中進(jìn)行注冊(cè)
<receiver android:name=".receiver.DownLoadBroadcastReceiver">
<intent-filter android:priority="20" >
<action android:name="android.intent.action.DOWNLOAD_COMPLETE" />
</intent-filter>
</receiver>
還要加權(quán)限:
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
雖說(shuō)代碼量很少,但是也確實(shí)會(huì)有問(wèn)題遇到
問(wèn)題1:No Activity found to handle Intent
解決:首先不要單獨(dú)設(shè)置data和type, 要同時(shí)setDataAndType(data, "application/vnd.android.package-archive")。其次最多的可能是下載文件路徑的問(wèn)題,好好檢查文件路徑是否錯(cuò)誤或是否不可讀。最簡(jiǎn)單的方法就是把a(bǔ)pk的路徑固定死
問(wèn)題2:權(quán)限問(wèn)題,targetSdkVersion >=23需要獲取權(quán)限才能自動(dòng)安裝
解決:
方法一:把build.gradle 文件中的targetSdkVersion < 23。這種方式也是最簡(jiǎn)單的。
方法二:動(dòng)態(tài)的獲取權(quán)限:代碼如下
// getPersimmions();方法
@TargetApi(23)
private void getPersimmions() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
ArrayList<String> permissions = new ArrayList<String>();
/*
* 讀寫(xiě)權(quán)限和電話狀態(tài)權(quán)限非必要權(quán)限(建議授予)只會(huì)申請(qǐng)一次,用戶同意或者禁止,只會(huì)彈一次
*/
// 讀寫(xiě)權(quán)限
if (addPermission(permissions, Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
permissionInfo += "Manifest.permission.WRITE_EXTERNAL_STORAGE Deny \n";
}
if (permissions.size() > 0) {
requestPermissions(permissions.toArray(new String[permissions.size()]), SDK_PERMISSION_REQUEST);
}
}
}
@TargetApi(23)
private boolean addPermission(ArrayList<String> permissionsList, String permission) {
if (checkSelfPermission(permission) != PackageManager.PERMISSION_GRANTED) { // 如果應(yīng)用沒(méi)有獲得對(duì)應(yīng)權(quán)限,則添加到列表中,準(zhǔn)備批量申請(qǐng)
if (shouldShowRequestPermissionRationale(permission)){
return true;
}else{
permissionsList.add(permission);
return false;
}
}else{
return true;
}
}
@TargetApi(23)
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
// TODO Auto-generated method stub
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
三、使用service(IntentService和ResultReceiver)
service.gif
IntentService繼承自service,在IntentService中我們開(kāi)啟一個(gè)線程執(zhí)行下載任務(wù)(service和你的app其實(shí)是在一個(gè)線程中,因此不想阻塞主線程的話必須開(kāi)啟新的線程。
//在這里根據(jù)url進(jìn)行下載文件,并通過(guò)receiver把需要更新的progressbar的值放在bundle傳過(guò)去
public class DownloadService extends IntentService {
public static final int UPDATE_PROGRESS = 8344;
public DownloadService() {
super("DownloadService");
}
@Override
protected void onHandleIntent(Intent intent) {
String urlToDownload = intent.getStringExtra("url");
ResultReceiver receiver = (ResultReceiver) intent.getParcelableExtra("receiver");
HttpURLConnection connection ;
try {
URL url = new URL(urlToDownload);
connection = (HttpURLConnection) url.openConnection();
connection.connect();
// this will be useful so that you can show a typical 0-100% progress bar
int fileLength = connection.getContentLength();
Log.d("test","fileLength:"+fileLength);
// download the file
InputStream input = connection.getInputStream();
OutputStream output = new FileOutputStream("/sdcard/new.apk");
byte data[] = new byte[2048];
long total = 0;
int count;
while ((count = input.read(data)) != -1) {
total += count;
// publishing the progress....
Bundle resultData = new Bundle();
resultData.putInt("progress" ,(int) (total * 100 / fileLength));
receiver.send(UPDATE_PROGRESS, resultData);
output.write(data, 0, count);
}
output.flush();
output.close();
input.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
//記得注冊(cè)<service android:name=".DownloadService"/>
activity中這樣調(diào)用DownloadService
progressDialog.show();
Intent intent = new Intent(this, DownloadService.class);
intent.putExtra("url",url);
intent.putExtra("receiver", new DownloadReceiver(new Handler()));
startService(intent);
activity中定義一個(gè)廣播接收器繼承ResultReceiver,ResultReceiver允許我們接收來(lái)自service中發(fā)出的廣播
//使用ResultReceiver接收來(lái)自DownloadService的下載進(jìn)度通知
private class DownloadReceiver extends ResultReceiver {
public DownloadReceiver(Handler handler) {
super(handler);
}
@Override
protected void onReceiveResult(int resultCode, Bundle resultData) {
super.onReceiveResult(resultCode, resultData);
if (resultCode == DownloadService.UPDATE_PROGRESS) {
int progress = resultData.getInt("progress");
//(true)就是根據(jù)你的進(jìn)度可以設(shè)置現(xiàn)在的進(jìn)度值。
//(false)就是滾動(dòng)條的當(dāng)前值自動(dòng)在最小到最大值之間來(lái)回移動(dòng),形成這樣一個(gè)動(dòng)畫(huà)效果
progressDialog.setIndeterminate(false);
progressDialog.setProgress(progress);
if (progress == 100) {
progressDialog.dismiss();
//自動(dòng)安裝下載的apk
File file=new File("/sdcard/new.apk");
Intent installIntent = new Intent(Intent.ACTION_VIEW);
installIntent.setDataAndType(Uri.fromFile(file), "application/vnd.android.package-archive");
installIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(installIntent);
}
}
}
}
如果對(duì)您有用,給個(gè)贊鼓勵(lì)一下唄~