其實第一行代碼的例子我早就學習過了,現在手上一個項目需要使用到這些東西,准備在操作之前復習一下,就順便發個博客加深一下自己對Android服務和文件下載這一塊的理解,博客可能有點長我盡量寫的詳細一點便於理解。
1.首先新建一個Android項目
2.添加okhttp依賴,這里我們使用okhttp來訪問網絡,其實我是習慣使用http的,但是不可否認okhttp很強大
dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { exclude group: 'com.android.support', module: 'support-annotations' }) compile 'com.android.support:appcompat-v7:24.2.1' testCompile 'junit:junit:4.12' compile 'com.squareup.okhttp3:okhttp:3.4.1'//就添加這一行 }
3.定義一個回調接口DownloadListener ,用於對下載狀態的監聽和對調
public interface DownloadListener { void onProgress(int progress);//通知當前下載進度 void onSuccess();//通知下載成功 void onFiled();//通知下載失敗 void onPaused();//下載暫停 void inCanceled();//下載取消 }
4.開始編寫下載功能。我們使用AsyncTask來實現,AsyncTask是Android提供的輕量級的異步類,我們可以再里面完成一些耗時的工作,比如下載
新建一個DownloadTask繼承AsyncTask,這個代碼有一丟丟長啊,首先我大概說下,下面主要的有三個函數,doInBackground用於執行下載邏輯,onProgressUpdate用於更新下載進度,onPostExecute用於通知最終結果,下面雖然代碼長,我幾乎所有的主要內容全給了備注,相信就算是新手也看的懂得
package com.example.dowmloadfile.Myclass; import android.os.AsyncTask; import android.os.Environment; import com.example.dowmloadfile.imp.DownloadListener; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.RandomAccessFile; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.Response; /** * Created by 海綿寶寶 on 2019/5/13. */ /**三個參數第一個為需要傳入給后台的數據類型,第二個是進度顯示類型,第三個為使用Integer作為反饋結果類型**/ public class DownLoadTask extends AsyncTask<String,Integer,Integer> { 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 downloadListener; private boolean isCanceled=false; private boolean isPaused=false; private int lastProgress; //通過DownLoadTask回調下載狀態 public DownLoadTask(DownloadListener listener){ downloadListener=listener;//傳入一個剛才定義的接口,用於回調 } @Override//后台執行具體的下載邏輯 protected Integer doInBackground(String... params) {//參數為下載的URL地址 InputStream is=null; RandomAccessFile savedFile=null; File file=null; try{ long downloadLength=0; String downloadUrl=params[0]; String fileName=downloadUrl.substring(downloadUrl.lastIndexOf("/"));//解析出下載的文件名 String derectory= Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getPath();//獲取本地的下載文件夾的路徑 file=new File(derectory+fileName); if (file.exists()){ downloadLength=file.length(); } long contentLength=getContentLength(downloadUrl);//讀取下載文件的字節數 if (contentLength==0){//下載文件長度為0說明文件不存在,返回下載失敗 return TYPE_FAILED; }else if (contentLength==downloadLength){//下載文件與本地文件大小一致說明已下載返回下載成功 return TYPE_SUCCESS; } OkHttpClient client=new OkHttpClient(); Request request=new Request.Builder().addHeader("RANGE","bytes="+downloadLength+"-").url(downloadUrl).build();//head用於告訴服務器我們從那個字節開始下載 Response response=client.newCall(request).execute(); if (response!=null){ //以java流的方式不斷獲取信息,不斷寫入本地 is=response.body().byteStream(); savedFile=new RandomAccessFile(file,"rw"); savedFile.seek(downloadLength);//跳過已經下載的字節 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+=len; savedFile.write(b,0,len); int progress=(int)((total+downloadLength)*100/contentLength);//計算當前進度 publishProgress(progress);//在通知中顯示 } } response.body().close(); return TYPE_SUCCESS; }; }catch (Exception e){ e.printStackTrace(); }finally { try { if (is!=null){ is.close(); }else if (savedFile!=null){ savedFile.close(); }else if (isCanceled&&file!=null){ file.delete(); } }catch (Exception e){ e.printStackTrace(); } } return TYPE_FAILED; } @Override//用於比較下載進度然后使用onProgress來更改下載進度的通知 protected void onProgressUpdate(Integer... values) { int progress=values[0];//獲取下載進度 if (progress>lastProgress){//如果進度發生了變化,那么更新進度顯示 downloadListener.onProgress(progress); lastProgress=progress; } } @Override//根據傳入的參數進行回調 protected void onPostExecute(Integer integer) { switch (integer) { case TYPE_SUCCESS: downloadListener.onSuccess(); break; case TYPE_FAILED: downloadListener.onFiled(); break; case TYPE_PAUSED: downloadListener.onPaused(); break; case TYPE_CANCELED: downloadListener.inCanceled(); default: break; }; } public void pauseDownload(){ isPaused=true; } public void cancelDownload(){ isCanceled=true; } //獲取需要下載的文件長度 private long getContentLength(String downloadUrl)throws IOException{ OkHttpClient client=new OkHttpClient(); Request request=new Request.Builder().url(downloadUrl).build(); Response response=client.newCall(request).execute(); if(response!=null&&response.isSuccessful()){ long contentLength=response.body().contentLength(); response.close(); return contentLength; } return 0; } }
5.右擊com.example.dowmloadfile-》New-》Service-》Service新建一個服務然后修改其中的代碼
下面代碼也比較長,簡述一下就是,先創建了一個DownloadListener實現了我們之前建立的接口的具體方法,為了使DownloadService 可以和活動通信我們建立了一個DownloadBinder 將DownloadListener以參數形式傳入,最后getNotification用於顯示進度內容
package com.example.dowmloadfile.Myservice; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; import android.app.Service; import android.content.Intent; import android.graphics.BitmapFactory; import android.os.Binder; import android.os.Environment; import android.os.IBinder; import android.support.v4.app.NotificationCompat; import android.widget.Toast; import com.example.dowmloadfile.MainActivity; import com.example.dowmloadfile.Myclass.DownLoadTask; import com.example.dowmloadfile.R; import com.example.dowmloadfile.imp.DownloadListener; import java.io.File; public class DownloadService extends Service { private DownLoadTask downLoadTask; private String downloadUrl; //創建一個DownloadListener並實現其中的方法 private DownloadListener downloadListener=new DownloadListener() { @Override//以通知的方式顯示進度條 public void onProgress(int progress) { //使用getNotificationManager函數構建一個用於顯示下載進度的通知 //使用notify去觸發這個通知 getNotificationManager().notify(1,getNotification("Download...",progress)); } @Override public void onSuccess() { downLoadTask=null; //關閉前台服務通知 //下面自己寫了一個通知用於通知下載成功 stopForeground(true); getNotificationManager().notify(1,getNotification("download succeed",-1)); Toast.makeText(DownloadService.this,"Download Succeed",Toast.LENGTH_LONG).show(); String directory= Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getPath(); } @Override public void onFiled() { downLoadTask=null; //關閉前台服務通知 stopForeground(true); getNotificationManager().notify(1,getNotification("download filed",-1)); Toast.makeText(DownloadService.this,"Download failed",Toast.LENGTH_LONG).show(); } @Override public void onPaused() { downLoadTask=null; Toast.makeText(DownloadService.this,"Download Paused",Toast.LENGTH_LONG).show(); } @Override public void inCanceled() { downLoadTask=null; //關閉前台服務通知 stopForeground(true); Toast.makeText(DownloadService.this,"Download canceled",Toast.LENGTH_LONG).show(); } }; //用於使服務可以和活動通信 private DownloadBinder mBinder=new DownloadBinder(); @Override public IBinder onBind(Intent intent) { // TODO: Return the communication channel to the service. return mBinder; } //用於使服務可以和活動通信 public class DownloadBinder extends Binder{ //開始下載 public void startDownload(String url){ if(downLoadTask==null){ downloadUrl=url; //創建一個DownLoadTask,將上面的downloadListener傳入 downLoadTask=new DownLoadTask(downloadListener); //使用execute開啟下載 downLoadTask.execute(downloadUrl); //startForeground使服務成為一個前台服務以創建持續運行的通知 startForeground(1,getNotification("download...",0)); Toast.makeText(DownloadService.this,"Download",Toast.LENGTH_LONG).show(); } } //暫停下載 public void pauseDownload(){ if (downLoadTask!=null){ downLoadTask.pauseDownload(); } } //取消下載后需要將下載中的任務取消 public void cancelDownload(){ if(downLoadTask!=null){ downLoadTask.cancelDownload(); }else { if (downloadUrl!=null) { //取消需要將文件刪除並將通知關閉 String fileName=downloadUrl.substring(downloadUrl.lastIndexOf("/")); 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_LONG).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){ builder.setContentText(progress+"%"); builder.setProgress(100,progress,false);//最大進度,當前進度,是否使用模糊進度條 } return builder.build(); } }
6.后台的代碼就是上面那些,剩下的就好理解多了,現在修改xml文件,定義三個按鈕分別用於開始下載,暫停下載,取消下載
<?xml version="1.0" encoding="utf-8"?> <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" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="start download"/> <Button android:id="@+id/pause" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="start pause"/> <Button android:id="@+id/cancel" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="start cancel"/> </LinearLayout>
7.修改Main.java給按鈕添加點擊事件,將前台和我們寫好的后台聯系起來,主函數較為簡單,細心一點都能看懂的
package com.example.dowmloadfile; import android.Manifest; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; import android.content.pm.PackageManager; import android.os.IBinder; import android.support.v4.app.ActivityCompat; import android.support.v4.content.ContextCompat; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.view.View; import android.widget.Button; import com.example.dowmloadfile.Myservice.DownloadService; public class MainActivity extends AppCompatActivity implements View.OnClickListener{ private DownloadService.DownloadBinder downloadBinder; //ServiceConnection主要用於獲取downloadBinder實例 private ServiceConnection connection=new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { //獲取實例以便於在活動中調用其中的方法 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 start=(Button)findViewById(R.id.start); Button pause=(Button)findViewById(R.id.pause); Button cancel=(Button)findViewById(R.id.cancel); start.setOnClickListener(this); pause.setOnClickListener(this); cancel.setOnClickListener(this); Intent intent=new Intent(this, DownloadService.class); startService(intent);//啟動服務保證服務一直運行 bindService(intent,connection,BIND_AUTO_CREATE);//綁定服務保證數據在服務和活動中傳遞 //申請運行時權限 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 view){ if(downloadBinder==null) { return; } switch (view.getId()){ case R.id.start: String url="http://47.107.108.172:8080/Competition/fileUpload/xueyuanFile/1/10/12e1d4ba00aa43a1a69cf572d3d91816_關於舉辦第四屆全國移動互聯創新大賽的通知.pdf"; downloadBinder.startDownload(url); break; case R.id.pause: downloadBinder.pauseDownload(); break; case R.id.cancel: downloadBinder.cancelDownload(); break; default: break; } } @Override protected void onDestroy() { super.onDestroy(); //解除服務的綁定,否則可能會造成消息泄露 unbindService(connection); } }
8.此時Android應該已經幫我們注冊好了權限,不過為了保險起見我們檢查一下
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.dowmloadfile"> <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" 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=".Myservice.DownloadService" android:enabled="true" android:exported="true"></service> </application> </manifest>
9.現在程序就可以運行了