package com.example.zhudashi.servicebestpractice; import android.Manifest; import android.app.Activity; 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.os.storage.StorageManager; import android.support.annotation.NonNull; 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 android.widget.Toast; import java.lang.reflect.Method; public class MainActivity extends AppCompatActivity implements View.OnClickListener{ private DownloadService.DownloadBinder downloadBinder; private ServiceConnection connection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName componentName, IBinder service) { //活動和服務綁定的時候執行 downloadBinder = (DownloadService.DownloadBinder) service; } @Override public void onServiceDisconnected(ComponentName componentName) { //活動和服務斷開連接時執行 } }; @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 cancelDownload = (Button) findViewById(R.id.cancel_download); startDownload.setOnClickListener(this); pauseDownload.setOnClickListener(this); cancelDownload.setOnClickListener(this); // Method mMethodGetPaths = null; // String[] paths = null; // //通過調用類的實例mStorageManager的getClass()獲取StorageManager類對應的Class對象 // //getMethod("getVolumePaths")返回StorageManager類對應的Class對象的getVolumePaths方法,這里不帶參數 // Context context = MainActivity.this; // StorageManager mStorageManager = (StorageManager)context // .getSystemService(context.STORAGE_SERVICE);//storage // try { // mMethodGetPaths = mStorageManager.getClass().getMethod("getVolumePaths"); // paths = (String[]) mMethodGetPaths.invoke(mStorageManager); // } catch (Exception e) { // // TODO Auto-generated catch block // e.printStackTrace(); // } // System.out.println("zhxing33 path = "+paths[0]); // System.out.println("zhxing33 path = "+paths[1]); Intent intent = new Intent(this,DownloadService.class); startService(intent);//連接服務 bindService(intent,connection,BIND_AUTO_CREATE); //綁定服務,通過connection進行主線程和子線程之間的交互 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_download: String url = "https://raw.githubusercontent.com/guolindev/eclipse/master/eclipse-inst-win64.exe"; 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, @NonNull String[] permissions, @NonNull 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: } } @Override protected void onDestroy() { super.onDestroy(); unbindService(connection); } }
1.onCreate()方法中值得注意的是,服務的啟動和綁定,
Intent intent = new Intent(this,DownloadService.class); startService(intent);//啟動服務
啟動服務只需要一個intent來展現MainActivity和服務之間的關系即可
bindService(intent,connection,BIND_AUTO_CREATE);
綁定服務,使用參數connection對象中的service對象來達到在活動中操作服務中方法的目的。
這里有一個疑問,為什么需要啟動服務,直接綁定服務不就行了嗎?書上說是為了讓DownloadService一直在后台運行,我認為是不成立的,因為后面又把該活動綁定了,所以DownloadService並不會一直在后台運行,他會隨着活動的結束而結束,
所以書上說啟動和綁定都需要就讓人很費解了。
注:
1.通過startservice開啟的服務.一旦服務開啟, 這個服務和開啟他的調用者之間就沒有任何的關系了.
調用者不可以訪問 service里面的方法. 調用者如果被系統回收了或者調用了ondestroy方法, service還會繼續存在
2.通過bindService開啟的服務,服務開啟之后,調用者和服務之間 還存在着聯系 ,
一旦調用者掛掉了.service也會跟着掛掉 .
2.
private ServiceConnection connection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName componentName, IBinder service) { //活動和服務綁定的時候執行 downloadBinder = (DownloadService.DownloadBinder) service; } @Override public void onServiceDisconnected(ComponentName componentName) { //活動和服務斷開連接時執行 } };
運用匿名類,繼承ServiceConnection類,重寫的onServiceConnected方法在活動和服務綁定的時候執行,其中把service賦值為downloadBinder是為了能在活動中通過downloadBinder調用服務中的方法(onClick()中就有調用)。
package com.example.zhudashi.servicebestpractice; 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.util.Log; import android.widget.Toast; import java.io.File; public class DownloadService extends Service { private DownloadTask downloadTask; private String downloadUrl; //匿名類,繼承downloadlistener接口,UI操作 private DownloadListener listener = new DownloadListener() { @Override public void onProgress(int progress) { 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,"pause",Toast.LENGTH_SHORT).show(); } @Override public void onCanceled() { downloadTask = null; stopForeground(true); Toast.makeText(DownloadService.this,"Cancled",Toast.LENGTH_SHORT).show(); } }; private DownloadBinder mBinder = new DownloadBinder();//用來連接主線程和子線程的聯結器,繼承至Binder。 @Override public IBinder onBind(Intent intent) { // TODO: Return the communication channel to the service. return mBinder; } class DownloadBinder extends Binder { public void startDownload(String url){//啟動下載 if(downloadTask == null){ downloadUrl = url; downloadTask = new DownloadTask(listener);//開啟異步類(下載和ui更新在里面處理) downloadTask.execute(downloadUrl);//用於啟動該異步類 startForeground(1,getNotification("Downloading...",0));//前台服務開啟,1是通知id,類似notify()方法的第一個參數 //第二個參數是構建出來的Notification對象。 //調用該方法會讓該service變成一個前台服務。 Toast.makeText(DownloadService.this,"Downloading...",Toast.LENGTH_SHORT).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(); // String directory = System.getenv("EXTERNAL_STORAGE"); File file = new File(directory+fileName); System.out.println("zhixing2 path = "+directory+fileName); if(file.exists()){ file.delete(); System.out.println("zhixng4"); } getNotificationManager().cancel(1);//取消通知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){ //用來構建一個通知,title是該通知的id。 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();//生成一個通知 } }
1.
@Override
public IBinder onBind(Intent intent) { // TODO: Return the communication channel to the service. return mBinder; }
繼承了service就需要重寫onBind()方法,onBind()方法返回的對象mBinder就是用來連接活動和服務的IBinder類型的對象,返回到了MainActivity活動中,在活動和服務綁定時執行onServiceConnected()方法賦值給了downloadBinder以保證
可以在MainActivity活動中操作該服務。、
2.這里又用到一個匿名類,實現DownloadListener接口,里面有五個方法,分別實現以下功能:
void onProgress(int progress);//下載通知
void onSuccess();//下載成功通知和提示
void onFailed();//下載失敗通知和提示
void onPaused();//下載暫停提示
void onCanceled();//下載取消的提示,關閉前台通知。
3.
public void startDownload(String url){//啟動下載
if(downloadTask == null){
downloadUrl = url;
downloadTask = new DownloadTask(listener);//開啟異步類(下載和ui更新在里面處理)
downloadTask.execute(downloadUrl);//用於啟動該異步類
startForeground(1,getNotification("Downloading...",0));//前台服務開啟,1是通知id,類似notify()方法的第一個參數
//第二個參數是構建出來的Notification對象。
//調用該方法會讓該service變成一個前台服務。
Toast.makeText(DownloadService.this,"Downloading...",Toast.LENGTH_SHORT).show();
}
}
downloadTask.execute(downloadUrl);
這條語句可以理解為開啟了子線程,里面有一些耗時的操作以及一些UI操作,該類具體怎么運作,可以看下嘛的代碼
package com.example.zhudashi.servicebestpractice; import android.os.AsyncTask; import android.os.Environment; import android.os.storage.StorageManager; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.RandomAccessFile; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.Response; /** * Created by zhudashi on 2018/2/14. */ 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 listener; private boolean isCanceled = false; private boolean isPaused = false; private int lastProgress; public DownloadTask(DownloadListener listener){ this.listener = listener; } @Override protected Integer doInBackground(String... params) { //子線程中處理,用來處理耗時的代碼 InputStream is = null; RandomAccessFile savedFile = null; File file = null; try{ long downloadedLength = 0;//記錄下載文件長度 String downloadUrl = params[0]; String filename = downloadUrl.substring(downloadUrl.lastIndexOf("/")); //獲取文件名 // String[] paths = null; //通過調用類的實例mStorageManager的getClass()獲取StorageManager類對應的Class對象 //getMethod("getVolumePaths")返回StorageManager類對應的Class對象的getVolumePaths方法,這里不帶參數 String directory = Environment.getExternalStoragePublicDirectory( Environment.DIRECTORY_DOWNLOADS).getPath(); // String directory = System.getenv("EXTERNAL_STORAGE"); file = new File(directory+filename); System.out.println("zhixing file = "+file.getPath()); if(file.exists()){ downloadedLength = file.length(); } long contentLength = getContentLength(downloadUrl); System.out.println("zhixing3 length="+downloadedLength); //獲取的是要下載文件的總長度 if(contentLength == 0){ return TYPE_FAILED; }else if(contentLength == downloadedLength){ //已下載字節和文件總字節相等,說明已經下載完成 return TYPE_SUCCESS; } OkHttpClient client = new OkHttpClient(); Request request = new Request.Builder() .addHeader("RANGE","bytes="+downloadedLength+"-") .url(downloadUrl) .build(); Response response = client.newCall(request).execute(); if(response != null){ is = response.body().byteStream(); //此時response.body().contentLength()獲取的是還需要下載的字節長度。 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+=len; savedFile.write(b,0,len); int progress = (int) ((total+downloadedLength)*100/contentLength); //下載的百分百,整數的 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) { //后台任務調用publishProgress()方法后調用該方法,主線程中運行,進行UI操作 int progress = values[0]; if(progress > lastProgress){//這次的下載進度和上次的下載進去進行比較 listener.onProgress(progress); lastProgress = progress; } } @Override protected void onPostExecute(Integer status) { //當后台任務執行完畢通過return語句進行返回的時侯,這個方法會被調用,返回數據會傳到該 //方法中,可以利用返回數據進行一些UI操作。 //根據參數傳入的下載狀態進行回調。 switch(status){ case TYPE_SUCCESS: listener.onSuccess(); break; case TYPE_FAILED: listener.onFailed(); break; case TYPE_PAUSED: listener.onPaused(); break; case TYPE_CANCELED: listener.onCanceled(); 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; } }
1.該類繼承AsyncTask異步類,是為了方便我們在子線程中對UI進行操作,其具體用法書上347頁有詳細的解答。
簡單來說我們把耗時的操作(下載操作)放在doInBackground()(可以簡單的認為在執行完downloadTask.execute(downloadUrl);后執行該函數中的語句)中實現,該函數中的代碼全部在子線程中運行。
onProgressUpdate()方法(在執行publishprogress()方法后執行該方法)中實現一些UI操作,因為其代碼在主線程中運行。
onPostExecute()用來接收doInBackground()中的返回數據,通過不同的返回數據執行不同的UI操作。doInBackground()返回數據時,會在返回數據前先執行finally|{}中的語句。
2.
String filename = downloadUrl.substring(downloadUrl.lastIndexOf("/"));
//讀取最后一個/后面的字符串,即下載文件的名字
String directory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getPath();
//得到內置sd卡的路徑(這里強調內置sd卡)/storage/emulated/0/Download/。
file = new File(directory+filename);
http://blog.csdn.net/qq_34471736/article/details/54964385
http://blog.csdn.net/tobacco5648/article/details/70314621
//獲取內置和外置sd卡路徑的方法
我獲取的外置sd卡路徑是: /storage/0403-0201 不同的sd卡可能不一樣,不知道為什么就是存不進去,測試了很多遍。
