Service基础使用

Service基础使用

之前的文章一直介绍Activity的使用,很多知识和用法单一的配合Activity使用,这次将总结Android四大组件之二——Service.
本文将要介绍以下内容:

  1. Service是什么
  2. 两种Service启动
  3. Service 前台服务与Notification
  4. 后台定时服务
  5. IntentService

Service是什么

Service 是一个可以在后台执行长时间运行操作而不使用用户界面的应用组件。服务可由其他应用组件启动,而且即使用户切换到其他应用,服务仍将在后台继续运行。 此外,组件可以绑定到服务,以与之进行交互,甚至是执行进程间通信 (IPC)。 例如,服务可以处理网络事务、播放音乐,执行文件 I/O 或与内容提供程序交互,而所有这一切均可在后台进行。

上面说了这么多可以做的耗时操作,但不要真的认为Service默认会运行在子线程中,他也不运行在一个独立的进程中,他同样执行在UI线程中,因此,不要在Service中执行耗时操作,除非你在Service中创建了子线程来完成耗时操作。
Service不因程序切换到后台或者用户切换到另一个APP而停止,但如果应用程序被杀死,他当然就也消失了。

两种Service的启动

服务启动分为两种形式,一种是通过StartService()启动Service,另一种是通过BindService(),至于先启动在绑定这个我们最后再说。本文主要介绍的就是这两种,这两种有什么区别呢?我们先看一张他们的生命周期图片,然后在慢慢细说。

Service启动流程图.jpg

StartService()

当应用组件(如 Activity)通过调用 startService() 启动服务时,服务即处于“启动”状态。一旦启动,服务即可在后台无限期运行,即使启动服务的组件已被销毁也不受影响。 已启动的服务通常是执行单一操作,而且不会将结果返回给调用方。例如,它可能通过网络下载或上传文件。 操作完成后,服务会自行停止运行。

通过上面的生命周期图也可以看出来首次启动会创建一个Service的实例,然后依次调用onCreate()和onStartCommand(),此时Service进入运行状态,即使你多次调用StartService()启动Service,也不会创建新的Service对象,而是直接复用前面创建的Service对象,调用它的startCommand()方法。下面介绍一下startService各个回调方法,我们该做什么

onCreate():首次创建服务,系统将调用此方法来执行一次性设置程序,如果服务已经运行,则不会调用此方法
onStartCommand():启动服务的时候,系统将会调用此方法。如果实现了此方法,则在服务工作完成后,需要主动调用stopSelf()或者stopService()来停止服务。
onDestroy(),当服务不再使用且将被销毁时,系统将会调用此方法,服务应该实现此方法来清理所有资源。这是服务接受的最后一个调用。

下面我将通过一个实例展示这个service的实现,以及方法调用的顺序。注意,通过AS新建一个Service则不需要配置mainfest文件,否则需要加入service声明,大概如下面这样。且注意始终通过显示Intent启动或者绑定Service,不要为他设置intent-filter

 <application ... >
      <service android:name=".ExampleService" />

  </application>

Service代码如下:

public class TestService extends Service {
    public TestService() {
    }

    @Override
    public void onCreate() {
        super.onCreate();
        //处理一次性设置
        Log.i("TestService","onCreate被调用");
    }
    
    //必须要有这个方法
    @Override
    public IBinder onBind(Intent intent) {
        // TODO: Return the communication channel to the service.
        throw new UnsupportedOperationException("Not yet implemented");
    }

    @Override
    public void onDestroy() {
        Log.i("TestService","onDestroy被调用");
        super.onDestroy();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        //处理Service逻辑
        Log.i("TestService","onStartCommand被调用");
        return super.onStartCommand(intent, flags, startId);
    }
}

Activity中布局文件放置了一个启动按钮,一个停止按钮,在onCreate()方法,实现startService,代码如下:

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        stop = (Button)findViewById(R.id.stop);
        start = (Button) findViewById(R.id.start);
        final Intent intent = new Intent(this, TestService.class);
        start.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                startService(intent);
            }
        });
        stop.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                stopService(intent);
            }
        });

    }

调用的结果如下:

点击启动service按钮后
I/TestService: onCreate被调用
I/TestService: onStartCommand被调用
点击停止service按钮后
I/TestService: onDestroy被调用

bindService()

当应用组件通过调用 bindService() 绑定到服务时,服务即处于“绑定”状态。绑定服务提供了一个客户端-服务器接口,允许组件与服务进行交互、发送请求、获取结果,甚至是利用进程间通信 (IPC) 跨进程执行这些操作。 仅当与另一个应用组件绑定时,绑定服务才会运行。 多个组件可以同时绑定到该服务,但全部取消绑定后,该服务即会被销毁。

startService()一样,首次使用bindService时绑定一个Service时,系统会实例化一个Service实例,并调用onCreate()和onBind()方法,此后如果再次使用bindService绑定Service,系统不会创建新的Service实例,也不会再调用onBInd()方法,只是会直接把IBinder对象传递给其他后来增加的客户端。 如果我们解除与服务的绑定,只需调用unbindService(),此时onUnbind和onDestory方法将会被调用!这是一个客户端的情况,假如是多个客户端绑定同一个Service的话,情况如下 当一个客户完成和service之间的互动后,它调用 unbindService() 方法来解除绑定。当所有的客户端都和service解除绑定后,系统会销毁service。(除非service也被startService()方法开启)。但一旦调用者销毁,Service也立即终止。下面主要说一下onBInd这个回调方法。
第一种启动方式并没有直接提供Service与Activity的交互方式。第二种启动方式即用bindService提供了Service与Activity交互方式,可以很方便的利用Ibinder实现Service与Activity的通信。关键在onBind方法
onBind()方法返回值是一个IBinder 对象,这个方法必须实现,通过startService返回为null,表示不调用,bindService则返回一个扩展Binder类的对象。
步骤如下:

  1. 定义一个继承Binder类的类,在类里面编写想要Activity调用的方法.
  2. 生成一个该类的对象,放到onBInd()方法中返回。
    代码例子如下:
public class TestService2 extends Service {
    public TestService2() {
    }

    public Mybinder mybinder = new Mybinder();

    public int getInfo() {
        return 123;
    }

    public class Mybinder extends Binder {
        public TestService2 getService() {
            return TestService2.this;
        }
    }

    @Override
    public IBinder onBind(Intent intent) {
        // TODO: Return the communication channel to the service.
        Log.i("TS","onbind被调用");
        return mybinder;
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.i("TS","onDestory被调用");
    }

    @Override
    public void onRebind(Intent intent) {
        super.onRebind(intent);
        Log.i("TS","onRebind被调用");
    }
}

这个例子Mybinder对象中getService方法,得到这个Service对象,从而调用Service中所有方法。
在Activity中的代码,要实现ServiceConnection类中的onServiceDisconnected和onServiceConnected方法,并且通过bindService来绑定服务,unbindService来解绑服务。
主要代码如下:

 TestService2.Mybinder binder;
    private ServiceConnection conn = new ServiceConnection() {

        //Activity与Service断开连接时回调该方法
        @Override
        public void onServiceDisconnected(ComponentName name) {
            System.out.println("------Service DisConnected-------");
        }

        //Activity与Service连接成功时回调该方法
        @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
            //将Service强制转换类型为Binder,这种用法在回调中很常见。
            System.out.println("------Service Connected-------");
            binder = (TestService2.Mybinder) service;
        }
    };
    
   ...
   
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        ...
         start.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                bindService(intent, conn, Service.BIND_AUTO_CREATE);
            }
        });

        stop.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                unbindService(conn);
            }
        });
        //通过binder对象就可以对我们绑定的Service进行各种操作
        status.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(MainActivity.this,binder.getService().getInfo() + "",Toast.LENGTH_SHORT).show();
            }
        });
    }

至于调用顺序,还是和流程图一样,就不一一详说了。这里已经说明了2种启动Service的方式和区别。下面将介绍一个很流行的Service应用,前台服务。

Service 前台服务与Notification

我们在用很多应用的时候,发现他们启动的时候,会在通知栏生成一个和该App的通知,来继续执行Service,比如墨迹天气,很多音乐App.这种叫前台服务,其实这种Service有一个很好的一点,就是不会因为Service自身的优先级低,而被系统KILL,而前台服务就不会。
前台服务的写法很容易,只需要在onCreate()中,建立一个通知,然后用startForeground()设置为前台服务即可。
下面直接放出代码,结合代码注释看看就好了,关于通知更多的内容可以看看Notification详解
这里只列出Service的onCreate()部分代码

@Override
    public void onCreate() {
        super.onCreate();
        //设定一个PendingIntent,来表示点击通知栏时跳转到哪里
        PendingIntent contentIntent = PendingIntent.getActivity(this, 0, new Intent(this, MainActivity.class), 0);
        Notification.Builder builder = new Notification.Builder(this);
        //建立一个notificationManager来管理通知的出现
        notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
        //构造通知的样式,包括图片,标题,内容,时间。
        builder.setSmallIcon(R.mipmap.ic_launcher).
                setWhen(System.currentTimeMillis()).
                setContentTitle("我是标题").
                setContentText("我是内容").
                setTicker("在启动时弹出一个消息").//这个Android5.0以上可能会失效
                setWhen(System.currentTimeMillis()).
                setContentIntent(contentIntent);
                //最后通过build建立好通知
        Notification notification = builder.build();
        //通过manager来显示通知,这个1为notification的id
        notificationManager.notify(1,notification);
        //启动为前台服务,这个1为notification的id
        startForeground(1,notification);
    }

后台定时服务

后台定时服务其实并不是特殊的Service,只是Service的常见的一种应用,放到后台做定时更新,轮询等。这次的Service要配合Alarm以及简单的广播机制来实现。
步骤主要如下:

  1. 获得Service AlarmManager manager = (AlarmManager) getSystemService(ALARM_SERVICE);
  2. 通过set方法设置定时任务
int time = 1000;
        long triggerAtTime = SystemClock.elapsedRealtime() + time;
        Intent i = new Intent(this,AlarmReceiver.class);
        PendingIntent pi = PendingIntent.getBroadcast(this, 0, i, 0);
        manager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, triggerAtTime, pi);
  1. 定义一个Service,在onStartCommand中开辟一个事务线程,用于处理一些定时逻辑
@Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        new Thread(new Runnable() {
            @Override
            public void run() {
               //执行想做的操作,比如输出时间
            }
        }).start();
          //步骤二里面的代码
        return super.onStartCommand(intent, flags, startId);
    }
  1. 定义一个Broadcast,用于启动Service。
public class AlarmReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        Intent i = new Intent(context,LongRunningService.class);
        context.startService(i);
    }
}

这样我们就能执行后台定时服务

IntentService

这是 Service 的子类,它使用工作线程逐一处理所有启动请求。如果您不要求服务同时处理多个请求,这是最好的选择。 您只需实现 onHandleIntent() 方法即可,该方法会接收每个启动请求的 Intent,使您能够执行后台工作。

由于大多数启动服务都不必须同时处理多个请求,因此使用IntentService类实现服务也许是最好的选择。
IntentService执行以下操作:

  • 创建默认的工作线程,用于在应用的主线程外执行传递给onStartCommand()的所有Intent
  • 创建工作队列,用于讲一个Intent逐一传递给onHandleIntent()实现,不要担心多线程问题
  • 在处理完所有启动后停止服务,永远不用调用stopSelf()
  • 提供onBind()的默认实现
  • 提供onstartCommand()的默认实现,可将Intent依次发送给工作队列和onHandleIntent()
    下面给一个IntentService的例子
public class MyIntentService extends IntentService {

    private final String TAG = MyIntentService.class.getName();

    public MyIntentService() {
        
          //此构造函数一定要实现  super(MyIntentService.class.getName());
    }

    @Override
    protected void onHandleIntent(Intent intent) {
        String action = intent.getExtras().getString("param");
        if("s1".equals(action)) Log.i(TAG, "启动s1");
        if("s2".equals(action)) Log.i(TAG, "启动s2");

        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void onCreate() {
        Log.i(TAG,"onCreate");
        super.onCreate();
    }

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


    @Override
    public void setIntentRedelivery(boolean enabled) {
        super.setIntentRedelivery(enabled);
        Log.i(TAG,"setIntentRedelivery");
    }

    @Override
    public void onDestroy() {
        Log.i(TAG,"onDestroy");
        super.onDestroy();
    }

}

在Activity中的onCreate()方法中加入如下代码

 Intent it1 = new Intent(this,MyIntentService.class);
        Bundle b1 = new Bundle();
        b1.putString("param", "s1");
        it1.putExtras(b1);
        Intent it2 = new Intent(this,MyIntentService.class);
        Bundle b2 = new Bundle();
        b2.putString("param", "s2");
        it2.putExtras(b2);
        startService(it1);
        startService(it2);

运行结果如下

I/com.shiqifeng.servicetest.MyIntentService: onCreate
I/com.shiqifeng.servicetest.MyIntentService: onStartCommand
I/com.shiqifeng.servicetest.MyIntentService: 启动s1
I/com.shiqifeng.servicetest.MyIntentService: onStartCommand
I/com.shiqifeng.servicetest.MyIntentService: 启动s2
I/com.shiqifeng.servicetest.MyIntentService: onDestroy

至此Service的基本使用全部结束

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,948评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,371评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,490评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,521评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,627评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,842评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,997评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,741评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,203评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,534评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,673评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,339评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,955评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,770评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,000评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,394评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,562评论 2 349

推荐阅读更多精彩内容