Android四大组件之Service

Service概述

  • Service是一种可以在后台执行长时间运行操作而没有用户界面的应用组件。

  • 它可由其它应用组件(如Activity)启动,一旦被启动将在后台一直运行,即使启动服务的组件已销毁也不受影响。非常适合用于执行那些不需要和用户交互而且还需要长期运行的任务。

  • 组件可以绑定到服务,从而进行交互。

  • 适用场景:播放音乐;处理网络事务;在后台记录位置信息等。

Service分类

  • 按运行地点可分为本地服务(LocalService)和远程服务(RemoteService)

    1. 本地服务:依附在主进程上而不是独立进程,所以不能进行耗时操作,可以在service里面创建一个Thread来执行耗时任务。主进程被kill后,服务便终止。

    2. 远程服务:即独立服务,对应进程名为所在包名加上指定的android:process字符串。因为是独立进程,所以Activity所在进程被kill时,该服务不受其它进程影响。可进行跨进程通信(IPC)。

  • 按运行类型可分为前台服务(Foreground Service)和后台服务

    1. 前台服务:会在通知栏显示Ongoing的Notification。当服务被终止时,通知栏的Notification也会消失,这样对于用户有一定的通知作用。常见如音乐播放服务。

    2. 后台服务:默认的服务即为后台服务,不会显示在Notification。当服务被终止时,用户是看不到效果的。某些不需要运行或终止提示的服务,如天气更新,日期同步等。

  • 按使用方式可分为:

    1. 使用startService:主要用于启动一个服务执行后台任务,不进行通信。对应的停止服务为stopService。

    2. 使用bindService:该方式启动的服务可进行通信,调用者挂掉后,服务也会跟着挂掉。对应的停止服务为unbindService。

    3. 使用startService和bindService:停止服务stopService和unbindService也必须使用。

Service的生命周期

Service生命周期
  • onCreate(): 首次创建服务时调用。如果服务已运行,则不会调用。只会在第一次创建Service时调用,适合完成一些初始化工作。

  • onStartCommand(): 当另一个组件通过调用startService()请求启动服务时,系统将调用该方法。

  • onDestroy(): 当服务不再使用且将被销毁时,系统调用该方法。

  • onBind(): 当另一个组件通过调用bindService()与服务绑定时,系统将调用该方法。该方法是抽象方法,所以onBind()方法必须重写。

  • onUnbind(): 当另一个组件通过调用unbindService()与服务解绑时,系统将调用该方法。

  • onRebind(): 当startService和bindService都启用,之后调用unbindService解绑服务时,另一个组件想要与该服务绑定,并且之前onUnbind()方法返回true,该方法将被调用。

生命周期调用:

  • 启动-->停止Service:startService() > onCreate() > onStartCommand() > onStartCommand() (多次启动)--> stopService() > onDestroy()

  • 绑定-->解绑Service:bindService() > onCreate() > onBind() --> unbindService() > onUnbind() > onDestroy()

  • 启动绑定-->解绑停止Service:startService() > onCreate() > onStartCommand() > bindService() > onBind() --> unbindService() > onUnbind() > stopService() > onDestroy()

  • 解绑绑定Service:unbindService() > onUnbind(ture) > bindService() > onRebind()

代码实现

MyService.java代码如下:

public class MyService extends Service {
    public static final String TAG = "Joy";
    //通过binder实现调用者client与Service之间的通信
    private MyBinder binder = new MyBinder();

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

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

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

    @Override
    public boolean onUnbind(Intent intent) {
        Log.d(TAG,"onUnbind");
        return super.onUnbind(intent);
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        Log.d(TAG,"onBind");
        return binder;
    }

    public class MyBinder extends Binder {
        public MyService getService() {
            return MyService.this;
        }
    }
    //该方法提供给绑定者调用
    public int getNumber() {
        return new Random().nextInt();
    }
}
  • 使用startService启动service
public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Intent intent = new Intent(this, MyService.class);
        startService(intent);
        startService(intent);
        stopService(intent);
        startService(intent);
    }
}

log如下:

02-27 16:22:02.518 26058-26058/com.android.myapplication D/Joy: oncreate
02-27 16:22:02.518 26058-26058/com.android.myapplication D/Joy: onStartCommand startId = 1
02-27 16:22:02.518 26058-26058/com.android.myapplication D/Joy: onStartCommand startId = 2
02-27 16:22:02.518 26058-26058/com.android.myapplication D/Joy: onDestroy
02-27 16:22:02.518 26058-26058/com.android.myapplication D/Joy: oncreate
02-27 16:22:02.518 26058-26058/com.android.myapplication D/Joy: onStartCommand startId = 1

总结:可以看出多次调用startService不会重复执行onCreate回调,但每次都会执行onStartCommand回调,且只需要调用一次stopService方法便可以停止服务,无论之前被调用了几次启动服务方法。

  • 使用bindService启动service
public class MainActivity extends AppCompatActivity {
    public static final String TAG = "Joy";
    private Button bind;
    private Button unbind;
    private MyService myService = null;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        bind = findViewById(R.id.bind);
        unbind = findViewById(R.id.unbind);
        bind.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                bindService(new Intent(MainActivity.this, MyService.class), conn, Context.BIND_AUTO_CREATE);
            }
        });

        unbind.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                unbindService(conn);
            }
        });
    }

    private ServiceConnection conn = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            MyService.MyBinder myBinder = (MyService.MyBinder) service;
            myService = myBinder.getService();
            int num = myService.getNumber();
            Log.d(TAG,"onServiceConnected");
            Log.d(TAG,"num = "+num);
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            Log.d(TAG,"onServiceDisconnected");
        }
    };
}

启动界面如下:



点击BIND SERVICE按钮log如下:

02-27 16:43:47.166 16555-16555/com.android.myapplication D/Joy: oncreate
02-27 16:43:47.166 16555-16555/com.android.myapplication D/Joy: onBind
02-27 16:43:47.176 16555-16555/com.android.myapplication D/Joy: onServiceConnected
02-27 16:43:47.176 16555-16555/com.android.myapplication D/Joy: num = 445451174

点击UNBIND SERVICE按钮或者按返回键log如下:

02-27 16:44:37.036 16555-16555/com.android.myapplication D/Joy: onUnbind
02-27 16:44:37.036 16555-16555/com.android.myapplication D/Joy: onDestroy

总结:

  1. 绑定者可以调用服务里面的方法。
  2. 即使多次点击BIND SERVER按钮,也不会多次触发onBind()方法。
  3. Service中需要创建一个实现IBinder的内部类。在onBind()方法中需返回一个IBinder实例,不然onServiceConnected()方法不会调用。
  4. onServiceDisconnected() 在service正常关闭的情况下是不会被调用的,该方法只在Service被异常中止或者被杀死的时候调用。
  • Remote Service启动服务
    因为远程服务是在另一个进程,它不受其它进程影响,可以为其它应用程序提供调用的接口--实际上就是进程间通信(IPC)。Android提供了AIDL工具来帮助进程间接口的建立。

前台服务

前台服务是那些被认为用户所认可的且在系统内存不足时不允许系统杀死的服务。前台服务必须给状态栏提供一个通知,它被放到正在运行(Ongoing)标题之下,表示只有在这个服务被终止或从前台主动移除通知后才能被解除。

  • 使用原因:
    在一般情况下,Service都是在后台运行,它们的系统优先级相对较低。当系统内存不足时,在后台运行的Service就有可能被回收。所以可以选择将需要保持运行的Service设置为前台服务。

  • 代码实现 :

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.d(TAG,"onStartCommand startId = "+startId);
        Notification.Builder builder = new Notification.Builder(this.getApplicationContext());
        Intent intent1 = new Intent(this,MainActivity.class);

        builder.setContentIntent(PendingIntent.getActivity(this,0,intent1,0))
                .setLargeIcon(BitmapFactory.decodeResource(this.getResources(),R.mipmap.ic_launcher))
                .setContentTitle("title")
                .setSmallIcon(R.mipmap.ic_launcher)
                .setContentText("content")
                .setWhen(System.currentTimeMillis());

        Notification notification = builder.build();
        notification.defaults = Notification.DEFAULT_SOUND;

        startForeground(110,notification);
        return super.onStartCommand(intent, flags, startId);
    }

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

重写onStartCommand()方法,使用startForeground(int, Notification)方法来启动service。需要在onDestroy()方法中通过stopForeground(true)方法来取消前台运行状态。

IntentService

Service不能直接处理耗时的任务,一般可以使用Service+Thread来实现。而IntentService可以处理异步任务请求。

  • IntentService是Service的子类,内部有一个工作线程来处理耗时操作。
  • 当任务执行完成后,IntentService会自动停止,不需要手动结束。
  • 如果多次启动IntentService,每个耗时操作会以工作队列的方式在IntentService的onHandleIntent() 方法中执行,依次执行,使用串行的方式,执行完自动结束。无需处理多线程问题。

相关代码:

MyIntentService.java代码:

public class MyIntentService extends IntentService {

    public MyIntentService() {
        super("MyIntentService");
    }

    @Override
    protected void onHandleIntent(@Nullable Intent intent) {
        long endTime = System.currentTimeMillis() + 20 *1000;
        Log.d("MyIntentService","onHandleIntent");
        while (System.currentTimeMillis() < endTime) {
            synchronized (this) {
                try {
                    wait(endTime - System.currentTimeMillis());
                } catch (Exception e) {

                }
            }
        }
        Log.d("MyIntentService","finished");
    }
}

MainActivity.java利用startService启动:

        final Intent intent = new Intent(MainActivity.this, MyIntentService.class);
        startService(intent);

log如下:

02-27 15:36:18.061 23525-24995/com.android.myapplication D/MyIntentService: onHandleIntent
0227-04 15:36:38.061 23525-24995/com.android.myapplication D/MyIntentService: finished

因MyIntentService继承了IntentService,所以不需要实现onBind(), onStartCommand()方法,只需实现onHandleIntent()即可。

不建议通过bindService来启动IntentService,因为IntentService源码中onBind()默认返回null。

Service与Thread的区别

  • Thread:Thread 是程序执行的最小单元,它是分配CPU的基本单位。Thread可以用来执行一些异步的操作。

  • Service:Service是android的一种机制,当它是Local Service,对应的Service是运行在主进程的 main 线程上的。如果是Remote Service,对应的Service是运行在独立进程的 main 线程上。

Thread的运行是独立于Activity的,当Activity被finish后,如果Thread没有被主动停止或run()方法没有执行完毕,Thread会一直执行。因此当Activity被finish后,Thread引用则不被持有,且没有办法在别的Activity对同一个Thread进行控制。
假如有需求的话,可以创建并启动一个Service,在Service里面创建、运行并控制该Thread,即可实现对Thread的控制。

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