Android四大组件——服务以及实例

宁可枝头抱香死,何曾吹落北风中。这篇文章主要讲述Android四大组件——服务以及实例相关的知识,希望能为你提供帮助。
一 服务的基本用法
1.定义服务
在包中新建一个service,命名为MyService。发现MyService类继承自Service。
【Android四大组件——服务以及实例】要在服务中处理一些逻辑,所以重写Service中的一些方法如下:

1 public class MyService extends Service { 2 3/*在服务创建时被调用*/ 4@Override 5public void onCreate() { 6super.onCreate(); 7} 8 9/*在服务每次启动时候调用*/ 10@Override 11public int onStartCommand(Intent intent, int flags, int startId) { 12return super.onStartCommand(intent, flags, startId); 13} 14 15/*在服务被销毁时候调用*/ 16@Override 17public void onDestroy() { 18super.onDestroy(); 19} 20 21public MyService() { 22} 23 24@Override 25public IBinder onBind(Intent intent) { 26// TODO: Return the communication channel to the service. 27throw new UnsupportedOperationException("Not yet implemented"); 28} 29 }

注:服务是四大组件之一,自然也需要被注册,当然我们新建时候已经被自动在androidManifest.xml中注册了。
 
2.启动和停止服务
布局文件如下:
1 < LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 2android:layout_width="match_parent" 3android:layout_height="match_parent" 4android:orientation="vertical"> 5 6< Button 7android:id="@+id/start_service" 8android:layout_width="match_parent" 9android:layout_height="wrap_content" 10android:text="start service" /> 11 12< Button 13android:id="@+id/stop_service" 14android:layout_width="match_parent" 15android:layout_height="wrap_content" 16android:text="stop service" /> 17 < /LinearLayout>

MyService类同上一部分一样,然后MainActivity类如下:
1 public class MainActivity extends AppCompatActivity implements View.OnClickListener { 2 3private Button startService; 4private Button stopService; 5 6@Override 7protected void onCreate(Bundle savedInstanceState) { 8super.onCreate(savedInstanceState); 9setContentView(R.layout.activity_main); 10 11startService = (Button)findViewById(R.id.start_service); 12stopService = (Button)findViewById(R.id.stop_service); 13startService.setOnClickListener(this); 14stopService.setOnClickListener(this); 15} 16 17@Override 18public void onClick(View view) { 19switch (view.getId()){ 20case R.id.start_service: 21Intent startIntent = new Intent(this,MyService.class); 22startService(startIntent); //启动服务 23break; 24case R.id.stop_service: 25Intent stopIntent = new Intent(this,MyService.class); 26stopService(stopIntent); //停止服务 27break; 28default: 29break; 30} 31} 32 }

可以看到,在点击事件中,我们构建了显性的Intent,目的将其跳转到服务。然后调用startService()和stopService()方法来开始和停止活动。这里如果没有点击停止运行的按键的话,服务会一直执行下去,而如果再MyService中调用stopSelf()方法的话就会停止服务自身。
其中,onCreate()和onStartCommand()方法在 点击startService时都会使用,不同的是,onCreate只在第一次创建的时候使用,再点击的的时候onCreate就不会再执行了。
 
3.活动和服务进行通信
如果我们想实现一个活动来控制一个下载服务,可以在活动中决定何时开始下载和查看下载进度。
这里要用到之前新建服务时候自带的onBind()方法。更新MyService如下:
1 public class MyService extends Service { 2 3private DownloadBinder mBinder = new DownloadBinder(); 4 5class DownloadBinder extends Binder { 6public void startDownload(){ 7Log.d("MyService","startDownload executed"); 8} 9public int getProgress(){ 10Log.d("MyService","getProgress executed"); 11return 0; 12} 13} 14 15······ 16@Override 17public IBinder onBind(Intent intent) { 18// TODO: Return the communication channel to the service. 19return mBinder; 20} 21 }

可见,先新建了一个Binder(绑定器)对象来对下载功能进行管理。而在DownloadBinder类中里写了对服务进行控制的功能,即开始下载的逻辑和查看下载进度的逻辑。
然后在onBind()方法里返回了这个实例,即Binder的对象。
然后在布局中添加两个按键:绑定服务和解除绑定。
在MainActivity中修改如下:
1 public class MainActivity extends AppCompatActivity implements View.OnClickListener { 2 3private Button startService; 4private Button stopService; 5private Button bindService; 6private Button unbindService; 7private MyService.DownloadBinder downloadBinder; 8private ServiceConnection connection = new ServiceConnection() { 9@Override 10public void onServiceConnected(ComponentName componentName, IBinder service) { 11/*在活动成功绑定时候执行*/ 12downloadBinder = (MyService.DownloadBinder) service; 13downloadBinder.startDownload(); 14downloadBinder.getProgress(); 15} 16 17@Override 18public void onServiceDisconnected(ComponentName componentName) { 19/*在活动和服务没有成功绑定时候执行*/ 20} 21}; 22 23protected void onCreate(Bundle savedInstanceState) { 24super.onCreate(savedInstanceState); 25setContentView(R.layout.activity_main); 26 27startService = (Button)findViewById(R.id.start_service); 28stopService = (Button)findViewById(R.id.stop_service); 29bindService = (Button)findViewById(R.id.bind_service); 30unbindService = (Button)findViewById(R.id.unbind_service); 31startService.setOnClickListener(this); 32stopService.setOnClickListener(this); 33bindService.setOnClickListener(this); 34unbindService.setOnClickListener(this); 35} 36 37@Override 38public void onClick(View view) { 39switch (view.getId()){ 40case R.id.start_service: 41Intent startIntent = new Intent(this,MyService.class); 42startService(startIntent); //启动服务 43break; 44case R.id.stop_service: 45Intent stopIntent = new Intent(this,MyService.class); 46stopService(stopIntent); //停止服务 47break; 48case R.id.bind_service: 49Intent bindIntent = new Intent(this,MyService.class); 50bindService(bindIntent,connection,BIND_AUTO_CREATE); //绑定服务 51break; 52case R.id.unbind_service: 53unbindService(connection); //解绑服务 54break; 55default: 56break; 57} 58} 59 }

如上可见,我们先创建一个ServiceConnection的匿名类,然后在里面重写了onServiceConnected()方法和onServiceDisconnected()方法用来在服务绑定成功 和 服务断开时候调用。在onServiceConnected()中,通过向下转型得到DownloadBinder的实例。现在就可以执行所有DownloadBinder中所有的方法了。即实现了指挥服务干什么功能。
然而目前活动和服务还是没有绑定,绑定是在绑定按键的点击事件中。绑定中先构建一个Intent对象,调用bindService方法进行绑定,传入三个参数,分别是刚构建的intent对象、ServiceConnection实例、和标示位(BIND_AUTO_CREATE表示绑定后自动创建)。(自动创建后会执行onCreate方法,但不会执行onStartCommand方法)
而解除绑定只需要调用unbindService()方法即可,传入参数ServiceConnection实例。
 
4.服务的生命周期
当项目调用了context的startService()方法后,相应的服务就会启动,并回调开启服务onStartCommand()方法,如果服务还没被创建,就先执行创建的onCreate()方法。然后服务就会一直处于运行状态,直到调用了stopService()方法或者是stopSelf()方法。因为服务只有一个实例,无论开启多少次,只要调用一次停止方法,服务就会停止了。
同时可以调用bindService()方法来绑定服务,并会回调onBind()方法,如果服务还没被创建,就先执行创建的onCreate()方法。之后调用方可以获取到onBind中返回的所有IBinder实例,就可以自由通信了。当调用解除绑定方法后,服务也会被销毁。
当对一个服务即调用了startService()后,又调用了bindService()方法。必须不满足以上两种条件,服务才会销毁。也就是说要调用stopService()和unBindService()两个方法才会使服务onDestroy()。
 
5.服务使用技巧
(1)使用前台服务
前台服务相比普通服务,会有一个正在运行的图标在系统的状态栏显示,效果类似于通知。而且不会被系统回收。
1 public class MyService extends Service { 2··· 3/*在服务创建时被调用*/ 4@Override 5public void onCreate() { 6super.onCreate(); 7Log.d("MyService", "服务已创建"); 8Intent intent = new Intent(this,MainActivity.class); 9PendingIntent pi = PendingIntent.getActivity(this,0,intent,0); 10Notification notification = new NotificationCompat.Builder(this) 11.setContentTitle("this is content title") 12.setContentText("this is content text") 13.setWhen(System.currentTimeMillis()) 14.setSmallIcon(R.mipmap.ic_launcher) 15.setLargeIcon(BitmapFactory.decodeResource(getResources(),R.mipmap.ic_launcher)) 16.setContentIntent(pi) 17.build(); 18startForeground(1,notification); 19} 20··· 21 }

可以看出来和通知创建的方法很像,基本相同。只是最后调用startForeground()方法将其显示出来。第一个参数是通知id,第二个参数是notification对象。
 
(2)使用IntentService
因为在服务中多执行子线程,同时在子线程中多使用stopSelf()来使其工作结束后结束服务。使用IntentService类可以很好的完成以上的两个要求。
首先创建一个类MyIntentService继承自IntentService。如下:
1 public class MyIntentService extends IntentService { 2 3public MyIntentService() { 4super("MyIntentService"); //调用父类的有参构造函数 5} 6 7@Override 8protected void onHandleIntent(@Nullable Intent intent) { 9//打印当前线程的id 来证明处理逻辑部分在子线程中 10Log.d("MyIntentService","Thread id is "+Thread.currentThread().getId()); 11} 12 13@Override 14public void onDestroy(){ 15super.onDestroy(); 16Log.d("MyIntentService","onDestroy executed"); 17} 18 }

类中首先提供了一个无参的构造函数,并在内部调用父类的有参构造函数。在子类中实现onHandleIntent()这个抽象方法,在这个方法中处理具体的逻辑,这个方法就是在子线程中了。因为运行完是自动停止的,我们重写onDestroy()方法来打印一条状态来证明一下。
布局中添加一个开始IntentService的按钮,在MainActivity中修改按钮的点击事件如下:
1case R.id.start_intent_service: 2//打印主线程的id 3Log.d("MainActivity","Thread id is "+Thread.currentThread().getId()); 4Intent intentService = new Intent(this,MyIntentService.class); 5startService(intentService); 6break;

可以看到。启动intentService和启动普通服务一样,都是新建一个intent对象,然后使用startService()进行跳转。
 
二 实例
一 添加依赖
配置build.gradle:
compile‘com.squareup.okhttp3:okhttp:3.11.0‘

 
二 下载监听接口
1 public interface DownloadListener { 2/*回调接口,用来对下载过程状态进行监听和回调*/ 3void onProgress(int progress); //通知当前下载进度 4 5void onSuccess(); //通知下载成功事件 6 7void onFailed(); //通知下载失败事件 8 9void onPaused(); //通知下载暂停事件 10 11void onCanceled(); //通知下载取消事件 12 }

 
三 下载功能DownloadTask类(使用AsyncTask) 
新建一个DownloadTask类继承自AsyncTask类如下:
1 public class DownloadTask extends AsyncTask< String, Integer, Integer> { 2/*传入三个参数 第一个是传一个字符串给后台任务, 3* 第二个是表示用整数显示进度,第三个是用整数来反馈结果*/ 4 5/*定义了四种下载状态*/ 6public static final int TYPE_SUCCESS = 0; 7public static final int TYPE_FAILED = 1; 8public static final int TYPE_PAUSED = 2; 9public static final int TYPE_CANCELED = 3; 10 11private DownloadListener listener; 12private boolean isCanceled = false; 13private boolean isPaused = false; 14private int lastProgress; 15 16//在DownloadTask的构造函数中传入刚刚定义的DownloadListener参数。 17public DownloadTask(DownloadListener listener) { 18this.listener = listener; 19} 20 21@Override 22/*在后台执行的下载逻辑*/ 23protected Integer doInBackground(String... params) { 24InputStream is = null; 25RandomAccessFile savedFile = null; 26File file = null; 27try { 28long downloadedLength = 0; //记录已下载文件的长度 29String downloadUrl = params[0]; //参数中获得下载的URL地址 30String fileName = downloadUrl.substring(downloadUrl.lastIndexOf("/")); //根据url解析出下载的文件名 31String directory = Environment.getExternalStoragePublicDirectory 32(Environment.DIRECTORY_DOWNLOADS).getPath(); //指定将文件下载到Download目录下 33 34/*判断Download中是否有要下载的文件,如果有读取已经下载的字节数 35* 可以在后面启动断点续传功能*/ 36file = new File(directory + fileName); 37if (file.exists()) { 38downloadedLength = file.length(); 39} 40/*获取待下载文件的长度,如果为0则文件有误 下载失败,如果等于已下载文件长度则说明下载完成*/ 41long contentLength = getContentLength(downloadUrl); //待下载文件的总长度 42if (contentLength == 0) { 43return TYPE_FAILED; 44} else if (contentLength == downloadedLength) { 45return TYPE_SUCCESS; 46} 47 48/*发送一条网络请求*/ 49OkHttpClient client = new OkHttpClient(); 50//断点下载,指定从那个字节开始下载 51Request request = new Request.Builder() 52.addHeader("RANGE", "bytes=" + downloadedLength + "-")//断点下载 53.url(downloadUrl) 54.build(); 55 56Response response = client.newCall(request).execute(); 57if (response != null) { 58is = response.body().byteStream(); 59savedFile = new RandomAccessFile(file, "rw"); 60savedFile.seek(downloadedLength); //跳过已经下载的字节 61byte[] b = new byte[1024]; 62int total = 0; //本次已下载的总长度 63int len; //要下载的长度 64/*当还有下载任务时就一直执行,在下载过程中不停判断是否有触发取消和暂停操作*/ 65while ((len = is.read(b)) != -1) { 66if (isCanceled) { 67return TYPE_CANCELED; //如果下载过程中取消 68} else if (isPaused) { 69return TYPE_PAUSED; //如果下载过程中暂停 70} else { 71total += len; 72savedFile.write(b, 0, len); 73//计算已经下载的百分比 74int progress = (int) ((total + downloadedLength) * 100 / contentLength); 75publishProgress(progress); //传入下载百分比 76} 77} 78} 79} catch (Exception e) { 80e.printStackTrace(); 81} finally { 82/*清空下载过程中产生的数据和文件*/ 83try { 84if (is != null) { 85is.close(); 86} 87if (savedFile != null) { 88savedFile.close(); 89} 90if (isCanceled & & file != null) { 91file.delete(); 92} 93} catch (Exception e) { 94e.printStackTrace(); 95} 96} 97return TYPE_FAILED; //如果在之前没有返回下载完成和其他情况,就是下载失败了。 98} 99 100@Override 101/*在界面上更新下载进度*/ 102protected void onProgressUpdate(Integer... values) { 103int progress = values[0]; 104if (progress > lastProgress) { 105listener.onProgress(progress); 106lastProgress = progress; 107} 108} 109 110@Override 111/*通知最终的下载结果*/ 112protected void onPostExecute(Integer status) { 113switch (status) { 114case TYPE_SUCCESS: 115listener.onSuccess(); 116break; 117case TYPE_FAILED: 118listener.onFailed(); 119break; 120case TYPE_PAUSED: 121listener.onPaused(); 122break; 123case TYPE_CANCELED: 124listener.onCanceled(); 125break; 126default: 127break; 128} 129} 130 131public void pauseDownload() { 132isPaused = true; 133} 134 135public void cancelDownload() { 136isCanceled = true; 137} 138 139/*得到下载任务总长度方法*/ 140private long getContentLength(String downloadUrl) throws IOException { 141OkHttpClient client = new OkHttpClient(); 142Request request = new Request.Builder() 143.url(downloadUrl) 144.build(); 145Response response = client.newCall(request).execute(); 146if (response != null & & response.isSuccessful()) { 147long contentLength = response.body().contentLength(); 148response.body().close(); 149return contentLength; 150} 151return 0; 152} 153 }

 
四 下载服务DownloadService
1 public class DownloadService extends Service { 2 3private DownloadTask downloadTask; 4private String downloadUrl; 5 6/*创造一个DownloadListener匿名类实例,在匿名类中实现具体的五个方法*/ 7private DownloadListener listener = new DownloadListener() { 8@Override 9public void onProgress(int progress) { 10/*使用getNotification构造一个显示下载进度的通知 11* 使用NotificationManager的notify方法去触发这个通知*/ 12getNotificationManager().notify(1, getNotification("Downloading...", progress)); 13} 14 15@Override 16public void onSuccess() { 17downloadTask = null; 18//下载成功时将前台服务通知关闭,并创建一个下载成功的通知 19stopForeground(true); //关闭前台服务 20getNotificationManager().notify(1, getNotification("Download success", -1)); 21Toast.makeText(DownloadService.this, "Download Success", Toast.LENGTH_LONG).show(); 22} 23 24@Override 25public void onFailed() { 26downloadTask = null; 27//下载失败将前台通知关闭,并创建一个下载失败的通知 28stopForeground(true); 29getNotificationManager().notify(1, getNotification("Download failed", -1)); 30Toast.makeText(DownloadService.this, "Download Failed", Toast.LENGTH_LONG).show(); 31} 32 33@Override 34public void onPaused() { 35downloadTask = null; 36Toast.makeText(DownloadService.this, "Download Paused", Toast.LENGTH_LONG).show(); 37} 38 39@Override 40public void onCanceled() { 41downloadTask = null; 42stopForeground(true); 43Toast.makeText(DownloadService.this, "Canceled", Toast.LENGTH_LONG).show(); 44} 45}; 46 47/*为了服务和活动可以通信,创建一个DownloadBinder*/ 48private DownloadBinder mBinder = new DownloadBinder(); 49 50@Override 51public IBinder onBind(Intent intent) { 52// TODO: Return the communication channel to the service. 53return mBinder; 54} 55/*DownloadBinder中提供了开始,暂停 取消三种方法*/ 56class DownloadBinder extends Binder { 57public void startDownload(String url) { 58if (downloadTask == null) { 59downloadUrl = url; 60downloadTask = new DownloadTask(listener); 61downloadTask.execute(downloadUrl); 62/*设置为前台服务*/ 63startForeground(1, getNotification("Downloading...", 0)); 64Toast.makeText(DownloadService.this, 65"Downloading,,,", Toast.LENGTH_LONG).show(); 66} 67} 68 69public void pauseDownload() { 70if (downloadTask != null) { 71downloadTask.pauseDownload(); 72} 73} 74 75public void cancelDownload() { 76if (downloadTask != null) { 77downloadTask.cancelDownload(); 78} 79if (downloadUrl != null) { 80//取消下载要删除文件,关闭通知 81String fileName = downloadUrl.substring(downloadUrl.lastIndexOf("/")); //下载文件名 82String directory = Environment.getExternalStoragePublicDirectory 83(Environment.DIRECTORY_DOWNLOADS).getPath(); //下载地址 84File file = new File(directory + fileName); //下载文件的目录 85if(file.exists()){ 86file.delete(); 87} 88getNotificationManager().cancel(1); //关闭通知 89stopForeground(true); //取消前台通知 90Toast.makeText(DownloadService.this,"Canceled", 91Toast.LENGTH_LONG).show(); 92} 93} 94} 95 96/*返回通知管理器的方法*/ 97private NotificationManager getNotificationManager() { 98return (NotificationManager) getSystemService(NOTIFICATION_SERVICE); 99} 100 101/*构造一个显示下载进度的通知*/ 102private Notification getNotification(String title, int progress) { 103/*点击跳转intent*/ 104Intent intent = new Intent(this, MainActivity.class); 105PendingIntent pi = PendingIntent.getActivity(this, 0, intent, 0); 106NotificationCompat.Builder builder = new NotificationCompat.Builder(this); 107builder.setSmallIcon(R.mipmap.ic_launcher); 108builder.setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher)); 109builder.setContentIntent(pi); 110builder.setContentTitle(title); 111if (progress > = 0) { 112//当progress大于或等于0才显示下载进度 113builder.setContentText(progress + "%"); 114builder.setProgress(100, progress, false); 115} 116return builder.build(); 117} 118 }

服务类主要可以分成三部分,
  1. DownloadListener匿名类实例,在匿名类中实现具体的五个方法。
  2. 为了服务和活动绑定,创建的DownloadBinder和其实力,类中包含三种具体操作方法。
  3. 通知管理器和创建通知的getNotification()部分
 
五 布局文件
布局中包含三个按键,开始、暂停和取消。
1 < LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 2android:layout_width="match_parent" 3android:layout_height="match_parent" 4android:orientation="vertical"> 5 6< Button 7android:id="@+id/start_download" 8android:layout_width="match_parent" 9android:layout_height="wrap_content" 10android:text="start_download" /> 11 12< Button 13android:id="@+id/pause_download" 14android:layout_width="match_parent" 15android:layout_height="wrap_content" 16android:text="pause download" /> 17 18< Button 19android:id="@+id/cancel_download" 20android:layout_width="match_parent" 21android:layout_height="wrap_content" 22android:text="cancel download" /> 23 24 < /LinearLayout>

 
六 MainActivity代码
1 public class MainActivity extends AppCompatActivity implements View.OnClickListener { 2 3private DownloadService.DownloadBinder downloadBinder; 4 5private ServiceConnection connection = new ServiceConnection() { 6@Override 7public void onServiceConnected(ComponentName componentName, IBinder service) { 8//服务被绑定时的操作 获取了DownloadBinder的实例,有了实例就可以在活动中调用服务提供的方法。 9downloadBinder = (DownloadService.DownloadBinder) service; 10} 11 12@Override 13public void onServiceDisconnected(ComponentName componentName) { 14//服务解除绑定时的操作 15} 16}; 17 18protected void onCreate(Bundle savedInstanceState) { 19super.onCreate(savedInstanceState); 20setContentView(R.layout.activity_main); 21Button startDownload = (Button) findViewById(R.id.start_download); 22Button pauseDownload = (Button) findViewById(R.id.pause_download); 23Button cancelDowmload = (Button) findViewById(R.id.cancel_download); 24startDownload.setOnClickListener(this); 25pauseDownload.setOnClickListener(this); 26cancelDowmload.setOnClickListener(this); 27 28/*启动服务并绑定服务 29* 启动服务可以保证DownloadService一直在后台运行*/ 30Intent intent = new Intent(this, DownloadService.class); 31startService(intent); 32bindService(intent, connection, BIND_AUTO_CREATE); 33/*sd卡读写权限申请判断*/ 34if(ContextCompat.checkSelfPermission(MainActivity.this, 35Manifest.permission.WRITE_EXTERNAL_STORAGE)!= PackageManager.PERMISSION_GRANTED){ 36ActivityCompat.requestPermissions(MainActivity.this, 37new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},1); 38} 39} 40 41@Override 42public void onClick(View view) { 43if(downloadBinder==null){ 44return; 45} 46switch (view.getId()) { 47case R.id.start_download: 48String url = "https://timgsa.baidu.com/timg?image& quality=80& size=b9999_10000& sec=" + 49"1535868543461& di=9d49c6cd4d161e0f4d09795fe6b5ac37& imgtype=0& src="https://www.songbingjia.com/android/+ 50"http%3A%2F%2Fphotocdn.sohu.com%" + 51"2F20151013%2Fmp35444706_1444730447601_1_th.jpeg"; 52downloadBinder.startDownload(url); 53break; 54case R.id.pause_download: 55downloadBinder.pauseDownload(); 56break; 57case R.id.cancel_download: 58downloadBinder.cancelDownload(); 59break; 60default: 61break; 62} 63} 64 65//requestPermissions的回调函数,用来判断权限 66@Override 67public void onRequestPermissionsResult(int requesCode,String[] permissions,int[] grantResults){ 68switch (requesCode){ 69case 1: 70if (grantResults.length > 0 & & grantResults[0] != PackageManager.PERMISSION_GRANTED) { 71Toast.makeText(this,"拒绝授权无法使用程序",Toast.LENGTH_LONG) 72.show(); 73finish(); 74} 75break; 76default: 77break; 78} 79} 80 81/*活动销毁的时候要解除服务的绑定*/ 82@Override 83protected void onDestroy(){ 84super.onDestroy(); 85unbindService(connection); 86} 87 }

 
七 权限声明
程序中使用到了网络和SD卡存储读取的权限,所以声明相关权限。
< uses-permission android:name="android.permission.INTERNET"/> < uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

 

    推荐阅读