前言:在这里完成了一个完整的从服务器上下载数据的实例!
一、我使用的时OkHttp进行网络通信,所以首先在gradle的dependencies中添加如下依赖:
compile 'com.squareup.okhttp3:okhttp:3.4.1'
二、定义一个进行回调的接口,因为在下载过程中我们会遇到下载成功、失败、暂停、取消等问题,所以先写好这个接口,到时候可以直接进行调用:
/** * 定义一个回调接口,用于对下载过程中的各种状态进行监听和回调 */ public interface DownloadListener { void onProgress(int progress);//通知当前的下载进度 void onSuccess();//通知下载成功 void onFailed();//。。。。失败 void onPaused();//。。。。。暂停 void onCanceled();//。。。。取消 }
三、接口写好后,我们就可以编写下载功能的代码了,代码中有详细的注释!新建一个DownloadTask继承自AsyncTask,
/** * 下载任务,第一个泛型参数String是指传一个字符串参数给后台,也就是Url; * 第二个泛型参数Integer,是指使用整型数据作为进度显示单位,也就是显示下载进度条 * 第三个泛型参数是Integer,表示使用整型数据反馈结果 */ public class DownloadTask extends AsyncTask<String,Integer,Integer> { //4个整型常量用于表示下载的状态 public static final int TYPE_SUCCESS=0; public static final int TYPE_FAILED=1; public static final int TYPE_PAUSED=2; public static final int TYPE_CANCELED=3; private DownloadListener listener; private boolean isCanceled=false; private boolean isPaused=false; private int lastProgress; //在构造函数中传入一个刚刚定义的DownLoadListener参数,待会用这个参数进行下载状态的回调 public DownloadTask(DownloadListener listener){ this.listener=listener; } //doInBackground方法用于在后台执行具体的下载逻辑 @Override protected Integer doInBackground(String... params) { InputStream is=null; RandomAccessFile savedFile=null; File file=null; try { long downloadedLength=0;//记录已下载的文件的长度 //获取到下载的URL地址 String downloadUrl=params[0];//下载地址 //根据URL解析出下载的文件名 String fileName=downloadUrl.substring(downloadUrl.lastIndexOf("/")); //将文件下载到Environment.DIRECTORY_DOWNLOADS目录下,也就是SD卡的Download目录 String directory= Environment.getExternalStoragePublicDirectory(Environment .DIRECTORY_DOWNLOADS).getPath(); file=new File(directory+fileName); //判断目录中是否已经存在要下载的文件 if (file.exists()){ //如果已经存在,则读取已下载的字节数,这样就可以在后面使用断点续传的功能 downloadedLength=file.length(); } //使用get。。方法来获取待下载文件的总长度 long contentLength=getContentLength(downloadUrl); //若文件长度=0,则文件有问题,直接返回下载失败 if (contentLength==0){ return TYPE_FAILED; //若文件长度=已经下载的文件的长度 }else if (contentLength==downloadedLength){ //已下载的字节和文件总字节相同,则下载成功 return TYPE_SUCCESS; } //建立网络链接 OkHttpClient client=new OkHttpClient(); //要发起一个http请求,首先创建一个Request对象 Request request=new Request.Builder() //断点下载,这里添加了一个header,指定从那个字节开始下载,因为已经下载过的就不要下载了 .addHeader("RANGE","bytes="+downloadedLength+"-") .url(downloadUrl) .build(); //调用Okhttp的newCall方法返回根据request请求,服务器返回的数据 Response response=client.newCall(request).execute(); /** * 采用java流的方式,不断从网络上读取数据,不断写入到本地 * 一直到文件全部下载完为止,在整个过程中,我们还要判断用户有没有 * 触发暂停、取消等操作,有的话通过TYPE的响应方式返回, * 没有的话则实时计算当前的下载进度 */ if (response!=null){ //用inputstream的实例得到服务器返回的数据的详细内容 is= response.body().byteStream(); //把文件进行保存到本地 savedFile=new RandomAccessFile(file,"rw"); savedFile.seek(downloadedLength);//跳过已下载的字节 byte[] b=new byte[1024]; int total=0; int len; while ((len=is.read(b))!=-1){ //判断用户是否触发取消事件 if (isCanceled){ return TYPE_CANCELED; }else if (isPaused){ //判断用户是否触发暂停事件 return TYPE_PAUSED; }else { total=total+len; savedFile.write(b,0,len); //计算下载的百分比 int progress= (int) ((total+downloadedLength )*100/contentLength); //调用 publishProgress进行通知 publishProgress(progress); } } //关流 response.body().close(); //返回下载成功 return TYPE_SUCCESS; } } catch (Exception e) { e.printStackTrace(); }finally { try { if (is!=null){ is.close(); } if (savedFile!=null){ savedFile.close(); } if (isCanceled&&file!=null){ file.delete(); } } catch (Exception e) { e.printStackTrace(); } } return TYPE_FAILED; } //这个方法用于在界面上更新当前的下载进度 @Override protected void onProgressUpdate(Integer... values) { int progress=values[0]; if (progress>lastProgress){ //如果下一次的下载进度大于上一次的,则通过progress通知一下下载进度 listener.onProgress(progress); //然后把当前的进度当作上一次的进度 lastProgress=progress; } } //这个方法用于通知最终的下载结果,根据参数中传入的下载状态进行回调 @Override protected void onPostExecute(Integer integer) { switch (integer){ case TYPE_SUCCESS: listener.onSuccess(); break; case TYPE_FAILED: listener.onFailed(); break; case TYPE_PAUSED: listener.onPaused(); break; case TYPE_CANCELED: listener.onCanceled(); break; default: break; } } public void pauseDownload(){ isPaused=true; } public void cancelDownload(){ isCanceled=true; } private long getContentLength(String downloadUrl) throws IOException{ //建立网络链接 OkHttpClient client=new OkHttpClient(); //要发起一个http请求,首先创建一个Request对象 Request request=new Request.Builder() .url(downloadUrl) .build(); //调用Okhttp的newCall方法返回根据request请求,服务器返回的数据 Response response=client.newCall(request).execute(); if (response!=null&&response.isSuccessful()){ //用contentLength接收服务器返回的详细数据 long contentLength=response.body().contentLength(); response.body().close(); return contentLength; } return 0; } }
四、这样我们就完成了具体的下载功能,为了保证下载能一直在后台进行,我们还需要创建一个下载服务!代码如下:
/** * 为了保证下载任务可以一直在后台运行,我们创建一个下载的服务 */ public class DownloadService extends Service { private DownloadTask downloadTask; private String downloadUrl; //创建 DownloadListener的匿名类实例,并在类中实现了5个方法 private DownloadListener listener=new DownloadListener() { @Override public void onProgress(int progress) { //调用getNotification方法构建了一个用于显示下载进度的通知 //然后调用NotificationManager()的notify方法去触发这个通知 //这样就可以在状态栏看到下载进度了 getNotificationManager().notify(1,getNotification("Downloading...",progress)); } @Override public void onSuccess() { downloadTask=null; //下载成功时,将前台服务通知关闭,并创建一个下载成功的通知 //关闭前台服务通知 stopForeground(true); getNotificationManager().notify(1,getNotification("Download Success",-1)); Toast.makeText(DownloadService.this,"Download Success",Toast.LENGTH_SHORT).show(); } @Override public void onFailed() { downloadTask=null; //下载失败时,将前台服务通知关闭,并创建一个下载失败的通知 //关闭前台服务通知 stopForeground(true); getNotificationManager().notify(1,getNotification("Download Failed",-1)); Toast.makeText(DownloadService.this,"Download Failed",Toast.LENGTH_SHORT).show(); } @Override public void onPaused() { downloadTask=null; Toast.makeText(DownloadService.this,"Paused",Toast.LENGTH_SHORT).show(); } @Override public void onCanceled() { downloadTask=null; stopForeground(true); Toast.makeText(DownloadService.this,"Canceled",Toast.LENGTH_SHORT).show(); } }; /** * 下面的内容是为了让服务(service)与活动(Activity)进行通信 */ private DownloadBinder mBinder=new DownloadBinder(); @Override public IBinder onBind(Intent intent) { return mBinder; } //创建DownloadBinder class DownloadBinder extends Binder{ //开始下载 public void startDownload(String url){ if (downloadTask==null){ downloadUrl=url; //创建一个downloadTask实例,把DownloadListener作为参数传入 downloadTask=new DownloadTask(listener); //调用execute方法开始下载,方法中传入下载的url downloadTask.execute(downloadUrl); //前台显示下载 startForeground(1,getNotification("Downloading...",0)); Toast.makeText(DownloadService.this,"Downloading...",Toast.LENGTH_SHORT).show(); } } //暂停下载 public void pauseDownload(){ //暂停下载,直接调用DownloadTask的pauseDownload方法 if (downloadTask!=null){ downloadTask.pauseDownload(); } } //取消下载 public void cancelDownload(){ if (downloadTask!=null){ downloadTask.cancelDownload(); } if (downloadUrl!=null){ //取消下载时需要将文件删除,并将通知关闭 //根据URL解析出下载的文件名 String fileName=downloadUrl.substring(downloadUrl.lastIndexOf("/")); //将文件下载到Environment.DIRECTORY_DOWNLOADS目录下,也就是SD卡的Download目录 String directory= Environment.getExternalStoragePublicDirectory( Environment.DIRECTORY_DOWNLOADS).getPath(); File file=new File(directory+fileName); //取消下载时需要将文件删除,并将通知关闭 if (file.exists()){ file.delete(); } getNotificationManager().cancel(1); stopForeground(true); Toast.makeText(DownloadService.this,"Canceled",Toast.LENGTH_SHORT).show(); } } } private NotificationManager getNotificationManager(){ return (NotificationManager) getSystemService(NOTIFICATION_SERVICE); } private Notification getNotification(String title,int progress){ Intent intent=new Intent(this,MainActivity.class); PendingIntent pi=PendingIntent.getActivity(this,0,intent,0); NotificationCompat.Builder builder=new NotificationCompat.Builder(this); builder.setSmallIcon(R.mipmap.ic_launcher); builder.setLargeIcon(BitmapFactory.decodeResource(getResources(),R.mipmap.ic_launcher)); builder.setContentIntent(pi); builder.setContentTitle(title); if (progress>=0){ //当progress大于等于0时才需要显示下载进度 builder.setContentText(progress+"%"); //第一个参数传入通知的最大进度,第二个是通知的当前进度,第三个是是否使用模糊进度条 builder.setProgress(100,progress,false); } return builder.build(); } }
五、现在下载的服务也已经写好了,后端的工作基本完成了,接下来写前端界面如下:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <Button android:id="@+id/start_download" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="开始下载"/> <Button android:id="@+id/pause_download" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="暂停下载"/> <Button android:id="@+id/cancel_download" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="取消下载"/> </LinearLayout>
六、界面写了四个按钮,非常清晰,下面开始写MainActivity的代码如下:
public class MainActivity extends AppCompatActivity implements View.OnClickListener { private DownloadService.DownloadBinder downloadBinder; //创建了一个ServiceConnection匿名类 private ServiceConnection connection=new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { //获取到DownloadBinder的实例,用这个实例在活动中调用服务提供的各种方法 downloadBinder= (DownloadService.DownloadBinder) service; } @Override public void onServiceDisconnected(ComponentName name) { } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Button startDownload= (Button) findViewById(R.id.start_download); Button pauseDownload= (Button) findViewById(R.id.pause_download); Button canelDownload= (Button) findViewById(R.id.cancel_download); startDownload.setOnClickListener(this); pauseDownload.setOnClickListener(this); canelDownload.setOnClickListener(this); //开启下载后台服务,启动服务可以让服务在后台一直运行 Intent intent=new Intent(this,DownloadService.class); startService(intent); bindService(intent,connection,BIND_AUTO_CREATE);//绑定服务,可以让服务与活动进行通信 //WRITE_EXTERNAL_STORAGE的权限申请,文件是要下载到SD卡的Download目录下的,没有权限,无法工作 if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE)!= PackageManager.PERMISSION_GRANTED){ ActivityCompat.requestPermissions(MainActivity.this,new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},1); } } @Override public void onClick(View v) { if (downloadBinder==null){ return; } switch (v.getId()){ case R.id.start_download: String url="http://123.206.81.238:8080/xiaomatext/test.apk"; downloadBinder.startDownload(url); break; case R.id.pause_download: downloadBinder.pauseDownload(); break; case R.id.cancel_download: downloadBinder.cancelDownload(); break; default: break; } } //请求权限返回的结果 @Override public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { switch (requestCode){ case 1: if (grantResults.length>0&&grantResults[0]!=PackageManager.PERMISSION_GRANTED){ Toast.makeText(this,"权限被拒绝,无法使用程序",Toast.LENGTH_SHORT).show(); finish(); } break; default: break; } } //一定要记得写下代码对服务进行接触绑定,不然可能会造成内存泄漏 @Override protected void onDestroy() { super.onDestroy(); unbindService(connection); } }
七、最后我们要在Manifest文件中加上权限如下:
<uses-permission android:name="android.permisssion.INTERNET"/> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>好了,到此,我们就完成了一个从服务器上下载一个安装包的实例了!运行一下就OK了,当然也可以下载别的东西,自己再动手尝试一下就行了。