【Android】四大组件(3)Service

上两节写了Android的两大组件,Activity和BroadcastReceiver,今天要写的是关于Service的问题。

Service

1.服务的用途
2.多线程编程
3.异步消息处理
4.定义服务
5.使用服务
6.服务和活动间的通讯
7.IntentService
8.前台服务
9.通过service实现定时执行

服务的用途

服务,Service,可以说是我们所看不到的一个activity,用来执行一个后台任务,不会和用户产生交互,依赖于主线程,因此为了防止主线程的堵塞,在服务中,我们通常会在其中开启子线程来进行一些耗时任务的操作,然后通过activity和其通信来进行前台ui的伴随更新。

多线程编程

多线程编程在Android开发中用的比较多,由于其ui在主线程中,如果在主线程中执行一个耗时的操作,势必会导致卡顿,Android也对这进行了严格限制,比如不可以在主线程中进行网络请求等等,所以这些就要依赖于多线程编程来进行的,Service是依赖于主线程,所以需要开启子线程。

线程的使用有两种方式,一个是继承Thread实现一个子类,再就是实现Runnable接口,作为参数传递,通常会通过一个匿名类来实现。

继承Thread,在run方法中写入我们所要执行的操作,对于线程的启动,每次我们都需要调用start方法。

public class MyThread extends Thread {

    @Override
    public void run() {
        super.run();
    }

}
     new MyThread().start();

实现Runnable 接口

public class MyThread implements Runnable {
    @Override
    public void run() {   
    }
}
   new Thread(new MyThread()).start();

通过接口能够更好的提升其可扩展性。

异步消息处理

和许多GUI库一样,Android中的ui也是线程不安全的,所以对于其更新,我们要在主线程中进行,而我们的一些耗时操作却是在子线程中进行的,所以需要一个通信机制,来实现子线程和主线程间的信息交互,Android提供的异步消息处理机制是通过,Message,Handler,Looper,MessageQueue来实现这套完整的机制。

 private Handler handler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
        }
    };

 public void  sendMsg(){
        Bundle bundle = new Bundle();
        Message msg = new Message();
        msg.setData(bundle);
        handler.sendMessage(msg);
    }

Message:用来在异步通信的过程中一些数据的携带,可以通过对其what值的设定来作为不同子线程的标示,然后通过setData携带bundle传递数据。

Handler:在异步通讯的过程中,作为一个发送者和接收者存在。

MessageQueue:用来存放Handler发送出来的Message,建立一个队列,每个线程只会有一个队列。

Looper:消息队列管理,每个线程对应一个,通过invoke loop方法,对消息队列进行一个循环遍历,将消息取出来送给Handler处理。

异步处理
Android对其也有一个更好的封装,通过这个封装,我们能够更好的实现异步通讯。AsyncTask,是一个抽象类,该抽象类需要三个参数,分别为执行异步任务的时候需要传入的,用来在后台使用,第二个参数是用来作为前后台进度的更新,第三个参数是在异步任务执行完成后返回的数据类型。

public class DownloadTask extends AsyncTask<Void,Integer,Object>{
//预处理
    @Override
    protected void onPreExecute() {
        super.onPreExecute();
    }
//后台执行
    @Override
    protected Object doInBackground(Void... params) {
        publishProgress(10);
        return null;
    }
//进度更新
    @Override
    protected void onProgressUpdate(Integer... values) {
        super.onProgressUpdate(values);
    }
 //任务执行完成
    @Override
    protected void onPostExecute(Object o) {
        super.onPostExecute(o);
    }
}

启动异步任务

new DownloadTask().execute();

上述方法中,只有doInBackground()是在子线程中执行的,除了这个之外其它的都是在主线程中执行,对于ui的更新,通过publishProgress即可实现子线程和主线程的连接。进行相应的信息交互。个人感觉异步对于执行网络任务比较耗时,信息量比较大的时候,需要通过一个progressdialog进行交互的时候,使用起来会比较方便,但是如果只是简单的一些信息交互,通过Handler也可以来实现掉。

定义一个服务


public class MyService extends Service {

    public MyService() {
    }

    @Override
    public IBinder onBind(Intent intent) {
    }

    @Override
    public void onCreate() {
        super.onCreate();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
    }
}

onCreate():服务首次被创建的时候启动
onStartCommand():服务首次被启动的时候调用
onDestroy():服务被关闭的时候执行用来执行一些回收操作

启动关闭服务

通过Intent来对服务进行启动

 public void startService(){
        Intent intent = new Intent(this,MyService.class);
        startService(intent);
    }

 public void stopService(){
        Intent intent = new Intent(this,MyService.class);
        stopService(intent);
    }

活动和服务间的通讯

启动了服务之后,这样在onStartCommand中的方法得到执行,但是却只是执行了这一次,同时在服务中执行的任务如何向活动中进行传递呢?
实现方式,通过在服务中继承一个Binder类,执行需要我们进行的操作,初始化一个实例,然后通过onBinder返回该实例。在活动中,需要通过ServiceConnection,然后重写其两个回调方法。通过bindService将我们的意图和ServiceConnection进行绑定,绑定的时候就会创建Service,然后执行ServiceConnection的回调方法,同时服务也会执行onBInd方法,返回一个Service的Binder实例给活动,然后活动便可以实现对于服务中执行的一些操作的数据的捕获和处理。

 public class MyService extends Service {
        private DownloadBinder mBinder = new DownloadBinder();
        class DownloadBinder extends Binder {
            public void startDownload() {
                Log.d("MyService", "startDownload executed");
}
            public int getProgress() {
                Log.d("MyService", "getProgress executed");
                return 0;
} }
        @Override
        public IBinder onBind(Intent intent) {
            return mBinder;
}
......
}
private MyService.DownloadBinder downloadBinder;
private ServiceConnection connection = new ServiceConnection() {
    @Override
    public void onServiceDisconnected(ComponentName name) {
    }
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
        downloadBinder = (MyService.DownloadBinder) service;
        downloadBinder.startDownload();
        downloadBinder.getProgress();
} };

绑定服务和活动

Intent bindIntent = new Intent(this, MyService.class); bindService(bindIntent, connection, BIND_AUTO_CREATE); // 绑定服务

解除绑定

unbindService(connection);

服务的生命周期

onCreate()、onStartCommand()、onBind()和 onDestroy()等方法都是在服务的生命周期内可能回调的方法。
一旦在项目的任何位置调用了 Context 的 startService()方法,相应的服务就会启动起来,
并回调 onStartCommand()方法。如果这个服务之前还没有创建过,onCreate()方法会先于 onStartCommand()方法执行。服务启动了之后会一直保持运行状态,直到 stopService()或 stopSelf()方法被调用。注意虽然每调用一次 startService()方法,onStartCommand()就会执行 一次,但实际上每个服务都只会存在一个实例。所以不管你调用了多少次 startService()方法, 只需调用一次 stopService()或 stopSelf()方法,服务就会停止下来了。
另外,还可以调用 Context 的 bindService()来获取一个服务的持久连接,这时就会回调 服务中的 onBind()方法。类似地,如果这个服务之前还没有创建过,onCreate()方法会先于 onBind()方法执行。之后,调用方可以获取到 onBind()方法里返回的 IBinder 对象的实例,这 样就能自由地和服务进行通信了。只要调用方和服务之间的连接没有断开,服务就会一直保 持运行状态。
当调用了 startService()方法后,又去调用 stopService()方法,这时服务中的 onDestroy() 方法就会执行,表示服务已经销毁了。类似地,当调用了 bindService()方法后,又去调用 unbindService()方法,onDestroy()方法也会执行,这两种情况都很好理解。但是需要注意, 我们是完全有可能对一个服务既调用了 startService()方法,又调用了 bindService()方法的, 这种情况下该如何才能让服务销毁掉呢?根据 Android 系统的机制,一个服务只要被启动或 者被绑定了之后,就会一直处于运行状态,必须要让以上两种条件同时不满足,服务才能被 销毁。所以,这种情况下要同时调用 stopService()和 unbindService()方法,onDestroy()方法才 会执行。

IntentService

通过服务,我们可以实现了在后台执行相应的操作,同时还可以通过binder和ServiceConnection实现服务和活动之间进行一个数据的交互,但是对于服务中的相应逻辑,在处理完了之后,如何对其进行关闭,我们通过开启子线程,然后在子线程中通过stopself来实现,还是比较繁琐的,Android提供了一个很优雅的解决方案,那就是IntentService

public class MyIntentService extends IntentService {
//无参构造函数,继承父类有参的构造函数
    public MyIntentService (){
        super("MyIntentService");
    }
//执行相应的逻辑,是一个子线程
    @Override
    protected void onHandleIntent(Intent intent) {

    }
//服务被关闭的时候调用
    @Override
    public void onDestroy() {
        super.onDestroy();
    }
}

启动方式和普通Service的启动方式相同,在方法执行完成后,服务自动关闭。

Intent intentService = new Intent(this, MyIntentService.class); 
startService(intentService);

前台服务

服务的优先级是比较低的,当我们的内存资源缺乏的时候,其将会先被杀掉,通过一个通知栏的形式让其权限增高,然后不会被kill掉,主要原因是我们可以借助它实现更高级的功能。比如,通过一个通知栏挂起,然后通过service实时获取信息,然后在通知栏进行显示,这样即实现了function同时也就提高了优先级。

 @Override
    public void onCreate() {
        super.onCreate();
        //设置通知栏
        Notification notification = new Notification(R.drawable.ic_launcher,"Notification",System.currentTimeMillis());
        Intent notificationIntent = new Intent(this,MainActivity.class);
        PendingIntent pendingIntent = PendingIntent.getActivity(this, 0,
                notificationIntent, 0);
        notification.setLatestEventInfo(this, "This is title", "This is content", pendingIntent);
        //通知栏绑定到service,然后在前端显示
        startForeground(1, notification);
    }

定时执行

有些时候程序中,有些地方需要我们进行一个定时执行的操作,比如闹钟等,实现思路,在service中开一个定时器,到达时间之后,发送一个广播,通过一个接收器接收到广播之后,执行相应的操作。

Android 中的定时任务一般有两种实现方式,一种是使用 Java API 里提供的 Timer 类, 一种是使用 Android 的 Alarm 机制。这两种方式在多数情况下都能实现类似的效果,但 Timer 有一个明显的短板,它并不太适用于那些需要长期在后台运行的定时任务。我们都知道,为 了能让电池更加耐用,每种手机都会有自己的休眠策略,Android 手机就会在长时间不操作 的情况下自动让 CPU 进入到睡眠状态,这就有可能导致 Timer 中的定时任务无法正常运行。 而 Alarm 机制则不存在这种情况,它具有唤醒 CPU 的功能,即可以保证每次需要执行定时 任务的时候 CPU 都能正常工作。需要注意,这里唤醒 CPU 和唤醒屏幕完全不是同一个概念, 千万不要产生混淆。
从 Android 4.4 版本开始,Alarm 任务的触发时间将会变得不准确, 有可能会延迟一段时间后任务才能得到执行。这并不是个 bug,而是系统在耗电性方面进行 的优化。系统会自动检测目前有多少 Alarm 任务存在,然后将触发时间将近的几个任务放在 一起执行,这就可以大幅度地减少 CPU 被唤醒的次数,从而有效延长电池的使用时间。
当然,如果你要求 Alarm 任务的执行时间必须准备无误,Android 仍然提供了解决方案。 使用 AlarmManager 的 setExact()方法来替代 set()方法,就可以保证任务准时执行了。


    public class LongRunningService extends Service {

        @Override
        public IBinder onBind(Intent intent) {
            return null;
        }

        @Override
        public int onStartCommand(Intent intent, int flags, int startId) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    Log.d("LongRunningService", "executed at " + new Date().
    toString());
                }
}).start();

AlarmManager manager = (AlarmManager) getSystemService(ALARM_SERVICE); int anHour = 60 * 60 * 1000; // 这是一小时的毫秒数
Intent i = new Intent(this, AlarmReceiver.class);
PendingIntent pi = PendingIntent.getBroadcast(this, 0, i, 0); manager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, triggerAtTime, pi); 
return super.onStartCommand(intent, flags, startId);
}
}

AlarmManager的参数设置

第一个参数是一个整型参数,用于指定 AlarmManager 的 工作类型,有四种值可选,分别是 ELAPSED_REALTIME、ELAPSED_REALTIME_WAKEUP、 RTC 和 RTC_WAKEUP。其中 ELAPSED_REALTIME 表示让定时任务的触发时间从系统开 机开始算起,但不会唤醒 CPU。ELAPSED_REALTIME_WAKEUP 同样表示让定时任务的触 发时间从系统开机开始算起,但会唤醒 CPU。RTC 表示让定时任务的触发时间从 1970 年 1 月 1 日 0 点开始算起,但不会唤醒 CPU。RTC_WAKEUP 同样表示让定时任务的触发时间从 1970 年 1 月 1 日 0 点开始算起,但会唤醒 CPU。使用 SystemClock.elapsedRealtime()方法可 以获取到系统开机至今所经历时间的毫秒数,使用 System.currentTimeMillis()方法可以获取 到 1970 年 1 月 1 日 0 点至今所经历时间的毫秒数。

总结:从学service这一章节中,发现了很多小的问题,一些api都不是很熟悉,AlarmManager和PeningItent,这两个要在后面专门研究下了。

你可能感兴趣的:(Android之路,android,多线程,Service)