我们主要指的是下载一个文件,不考虑断点续传。
主要的三种方式AsyncTask、Service和使用DownloadManager
一、如果想要在后台下载任务的同时可以更新进度条UI----使用AsyncTask
忽略安装需要密码这个细节,用oppo手机的应该知道,这个是只有oppo测试机会这样,别的手机就可以直接安装了。
要点:
-
一个url下载链接,一个ProgressDialog用来显示进度,一个按钮(点击升级)按钮监听的方法为onUpdateClick()
-
接着放入主要的代码
//关于进度显示
private ProgressDialog progressDialog;
//相关属性
progressDialog =new ProgressDialog(UpdateDialogActivity.this);
progressDialog.setMessage("正在下载...");
progressDialog.setIndeterminate(true);
progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
progressDialog.setCancelable(true);
//升级下载按钮点击事件
private void onUpdateClick() {
// TODO: 2017/10/11 三种方式实现apk下载
//第一种 asynctask
//onProgressUpdate和onPreExecute是运行在UI线程中的,
// 所以我们应该在这两个方法中更新progress。
final DownloadTask downloadTask = new DownloadTask(UpdateDialogActivity.this);
//execute 执行一个异步任务,通过这个方法触发异步任务的执行。这个方法要在主线程调用。
downloadTask.execute(url);
progressDialog.setOnCancelListener(new DialogInterface.OnCancelListener() {
@Override
public void onCancel(DialogInterface dialog) {
downloadTask.cancel(true);
}
});
}
-
最主要的是内部类 DownloadTask ,具体的方法说明参考注释以及文章AsyncTask机制原理分析
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)方法被调用后立即执行,执行在ui线程,
// 一般用来在执行后台任务前会UI做一些标记
@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这个方法在onPreExecute()完成后立即执行,
// 用于执行较为耗时的操作,
// 此方法接受输入参数
// 和返回计算结果(返回的计算结果将作为参数在任务完成是传递到onPostExecute(Result result)中),
// 在执行过程中可以调用publishProgress(Progress... values)来更新进度信息
//后台任务的代码块
@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+"////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
//在调用这个方法后,执行onProgressUpdate(Progress... values),
//运行在主线程,用来更新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),
// 执行在UI线程,在调用publishProgress(Progress... values)时,此方法被执行。
@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),
// 执行在UI线程,当后台操作结束时,此方法将会被调用。
@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();}
//这里主要是做下载后自动安装的处理
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
每个Android App都会有版本更新的功能,而下载功能Google官方推荐使用 DownloadManager服务
使用最简单的一种
DownloadManager.Request request = new DownloadManager.Request(Uri.parse(url));
request.setDescription("下载中");
request.setTitle("我的下载");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
}
request.allowScanningByMediaScanner();//设置可以被扫描到
request.setVisibleInDownloadsUi(true);// 设置下载可见
request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);//下载完成后通知栏任然可见
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", "开始下载任务:" + Id + " ...");
如果想同样实现下载完安装,要使用广播.当DownloadManager下载完成后会发出一个广播 android.intent.action.DOWNLOAD_COMPLETE,创建一个广播接收者,处理自动提示安装:
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中进行注册
<receiver android:name=".receiver.DownLoadBroadcastReceiver">
<intent-filter android:priority="20" >
<action android:name="android.intent.action.DOWNLOAD_COMPLETE" />
</intent-filter>
</receiver>
还要加权限:
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
虽说代码量很少,但是也确实会有问题遇到
问题1:No Activity found to handle Intent
解决:首先不要单独设置data和type, 要同时setDataAndType(data, "application/vnd.android.package-archive")。其次最多的可能是下载文件路径的问题,好好检查文件路径是否错误或是否不可读。最简单的方法就是把apk的路径固定死
问题2:权限问题,targetSdkVersion >=23需要获取权限才能自动安装
解决:
方法一:把build.gradle 文件中的targetSdkVersion < 23。这种方式也是最简单的。
方法二:动态的获取权限:代码如下
// getPersimmions();方法
@TargetApi(23)
private void getPersimmions() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
ArrayList<String> permissions = new ArrayList<String>();
/*
* 读写权限和电话状态权限非必要权限(建议授予)只会申请一次,用户同意或者禁止,只会弹一次
*/
// 读写权限
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) { // 如果应用没有获得对应权限,则添加到列表中,准备批量申请
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)
IntentService继承自service,在IntentService中我们开启一个线程执行下载任务(service和你的app其实是在一个线程中,因此不想阻塞主线程的话必须开启新的线程。
//在这里根据url进行下载文件,并通过receiver把需要更新的progressbar的值放在bundle传过去
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();
}
}
}
//记得注册<service android:name=".DownloadService"/>
activity中这样调用DownloadService
progressDialog.show();
Intent intent = new Intent(this, DownloadService.class);
intent.putExtra("url",url);
intent.putExtra("receiver", new DownloadReceiver(new Handler()));
startService(intent);
activity中定义一个广播接收器继承ResultReceiver,ResultReceiver允许我们接收来自service中发出的广播
//使用ResultReceiver接收来自DownloadService的下载进度通知
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)就是根据你的进度可以设置现在的进度值。
//(false)就是滚动条的当前值自动在最小到最大值之间来回移动,形成这样一个动画效果
progressDialog.setIndeterminate(false);
progressDialog.setProgress(progress);
if (progress == 100) {
progressDialog.dismiss();
//自动安装下载的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);
}
}
}
}
如果对您有用,给个赞鼓励一下呗~