Android 在Sevice使用startService

Service作为android四大组件之一,那么什么情况下我们会使用到Service呢?在这里我把它归纳成两种情况得运用场景:

1、用于长期执行某些操作,并且甚至与UI(主)线程没有交互。比如启动app直接去网络下载文件

2、跨进程间通信,比如appA程序中Service被appB中程序调用

注意:Service默认时运行在它所在的宿主进程的主进程中,也就是说如果我们在Service中做耗时工作,UI(主)线程会卡死,出现ARN程序无响应现象。为了防止这种情况出现,我们一般都是在Service中创建一个新的线程来处理一些耗时工作,这样就不会阻塞主线程。从这里也侧面反映了Service不是另一个独立的进程,Service自己本身不会开辟新的进程,除非手动来设置。默认情况下,Service是运行在本运用程序所属的进程中。


Service启动模式也有两种,分别是:startService和bindService
1、通过startService方式启动
如果运行在后台的Service甚至不需要和UI(主)线程间进行交互,这种情况下,一般是调用startService来启动Service。

2、通过bindService方式启动

两个不同进程间通信 或者 某个应用中Service方法的暴露出去(同个进程间),一般是调用bindService来启动Service。


这里主要讲解的是startService启动模式,bindService启动会在另外一篇中去说的

API为我们提供Service的生命周期


onCreate:如果多次执行了Context的startService方法启动Service,Service方法的onCreate方法只会在第一次创建Service的时候调用一次,以后均不会再次调用。我们可以在onCreate方法中完成一些Service初始化相关的操作

onStartCommand:如果多次执行了Context的startService方法,那么Service的onStartCommand方法也会相应的多次调用。onStartCommand方法很重要,我们在该方法中根据传入的Intent参数进行实际的操作,比如会在此处创建一个线程用于下载数据或播放音乐等

onBind:Service中的onBind方法是个抽象方法,所以Service类本身就是一个抽象类,也就是说onBind方法必须要重写,即使用不到。通过startService使用Service时,我们在重写onBind方法时,只需要将其返回值设为null即可。onBind方法主要是用于给bindService方法调用Service时才使用到。

onDestiny:Service销毁时回调函数

通过上面,我们可以把Service生命周期归纳成下面情况


有了上面的理论知识,下面我们一起看一个例子:

MainActivity

public class MainActivity extends Activity implements View.OnClickListener {
    private Button btn1;
    private Button btn2;
    private boolean isStopService = false;
    private Intent intent = null;
 
    Handler handler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            startService(intent);
            handler.sendEmptyMessageDelayed(0,1000);
        }
    };
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        layoutView();
        setLister();
        initData();
 
    }
 
    private void layoutView() {
        btn1 = (Button) findViewById(R.id.btn1);
        btn2 = (Button) findViewById(R.id.btn2);
    }
 
    private void setLister() {
        btn1.setOnClickListener(this);
        btn2.setOnClickListener(this);
    }
 
    private void initData() {
        intent = new Intent(this, TestStartService.class);
    }
 
    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.btn1:
                isStopService = true;
                //定时器
                handler.sendEmptyMessageDelayed(0,1000);
 
//                startService(intent);
            break;
            case R.id.btn2:
                if (intent != null && isStopService){
                    Log.v("hjz","停止服务");
                    stopService(intent);
                }
                break;
 
        }
    }
}


在开启startService触发事件中做了一个定时器,不断去调用startService,里面还有一个停止服务的触发事件

TestStartService
public class TestStartService extends Service {
 
    @Override
    public void onCreate() {
        super.onCreate();
        Log.v("hjz","onCreate");
    }
 
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }
 
    /**
     *
     * @param intent
     * @param flags  官方解释:有关此开始请求的附加数据。目前是0
     * @param startId 官方解释:一个唯一的整数,代表这个特定的请求开始
     * @return
     */
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.v("hjz","startId="+startId);
        if (startId == 8){
            stopSelf();
        }
        return START_REDELIVER_INTENT;
    }
 
    @Override
    public void onDestroy() {
        super.onDestroy();
    }
 
}

在onStartCommand中,因为starId是唯一的,startId默认从1开始,如果Service已经创建了,多次去调用startService来启动Service,则startId呈递增的形式,每次都加1,当startId等于8时,Service内部调用了stopSelf杀掉自身。

这里操作点击startService按钮之后,再点击停止服务按钮,打出的日志:


通过上面的打印验证上面对onCreate和onStartCommand的描述

如果Service启动后没有去停止掉它,它会一直运行下去,停止startService启动的Service有两种方法:

1、在外部调用stopService

2、在Service内部调用stopSelf方法

上面这两种情况都用上了

 @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.v("hjz","startId="+startId);
        if (startId == 8){
            stopSelf();
        }
        return <span style="color:#ff0000;">START_REDELIVER_INTENT</span>;
    }


值得注意的是在onStartCommand中返回值,常用的返回值有:START_NOT_STICKY、START_SICKY和START_REDELIVER_INTENT,这三个都是静态常理值。

START_NOT_STICKY:表示当Service运行的进程被Android系统强制杀掉之后,不会重新创建该Service,如果想重新实例化该Service,就必须重新调用startService来启动。

使用场景:表示当Service在执行工作中被中断几次无关紧要或者对Android内存紧张的情况下需要被杀掉且不会立即重新创建这种行为也可接受的话,这是可以在onStartCommand返回值中设置该值。如在Service中定时从服务器中获取最新数据

START_STICKY:表示Service运行的进程被Android系统强制杀掉之后,Android系统会将该Service依然设置为started状态(即运行状态),但是不再保存onStartCommand方法传入的intent对象,然后Android系统会尝试再次重新创建该Service,并执行onStartCommand回调方法,这时onStartCommand回调方法的Intent参数为null,也就是onStartCommand方法虽然会执行但是获取不到intent信息。

使用场景:如果你的Service可以在任意时刻运行或结束都没什么问题,而且不需要intent信息,那么就可以在onStartCommand方法中返回START_STICKY,比如一个用来播放背景音乐功能的Service就适合返回该值。
START_REDELIVER_INTENT:表示Service运行的进程被Android系统强制杀掉之后,与返回START_STICKY的情况类似,Android系统会将再次重新创建该Service,并执行onStartCommand回调方法,但是不同的是,Android系统会再次将Service在被杀掉之前最后一次传入onStartCommand方法中的Intent再次保留下来并再次传入到重新创建后的Service的onStartCommand方法中,这样我们就能读取到intent参数。

使用场景:如果我们的Service需要依赖具体的Intent才能运行(需要从Intent中读取相关数据信息等),并且在强制销毁后有必要重新创建运行,那么这样的Service就适合返回START_REDELIVER_INTENT。


在Service中操作耗时工作,一般可以这样写:

 

  private LongRunning thread = new LongRunning();
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.v("hjz","startId="+startId);
        thread.start();
        return START_REDELIVER_INTENT;
    }
 
    /**
     * 耗时操作
     */
    class LongRunning extends Thread{
        @Override
        public void run() {
            //耗时业务处理
        }
    }

接着补充一个知识点,Service进一步的封装类IntentService
由于Service是运行在UI(主)线程中,会带来UI阻塞,所以在操作耗时工作时,都在onStartCommand中开启一个新的线程去执行一些耗时工作。正因为这样,创建一个带有工作线程Service是很常见的(因为工作线程不会阻塞主线程),为了简化程序员工作量,Android额外开发了一个类那就是IntentService
IntentService特点:
1. IntentService自带一个工作线程,当我们的Service中做一些阻塞UI(主)线程工作时,可以使用IntentService。
2. 将我们实际要做的工作放入到IntentService的onHandleIntent方法中处理,并且onHandleIntent运行在IntentService所持有的工作线程中,而非主线程。
3. 当多次启动IntentService,产生多个job,IntentService只能一个一个处理,也就是按照先后顺序进行处理。先将intent1传入onHandleIntent,让其完成job1,然后将intent2传入onHandleIntent,让其完成job2…这样直至所有job完成,所以我们IntentService不能并行地执行多个job,只能一个一个的按照先后顺序完成,当所有job完成的时候IntentService就销毁了,会执行onDestroy回调方法

IntentService源码解析

IntentService是继承Service,根据上面对Service了解,先来看一下onCreate方法 

public void onCreate() {
        super.onCreate();
        //创建HandlerThread线程,利用mName作为线程名称,HandlerThread成为了IntentService的工作线程
        HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
        thread.start();
        //HandlerThread绑定到Looper对象上
        mServiceLooper = thread.getLooper();
        //将Looper传递给ServiceHandler,这样Handler就和HandlerThread通过消息队列绑定在了一起
        mServiceHandler = new ServiceHandler(mServiceLooper);
    }
再来看onStartCommand这个方法

@Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        onStart(intent, startId);
        return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY;
    }


里面调用了onStart这个方法,在瞧一下onStart做了什么操作先

@Override
    public void onStart(Intent intent, int startId) {
        Message msg = mServiceHandler.obtainMessage();
        msg.arg1 = startId;
        msg.obj = intent;
        mServiceHandler.sendMessage(msg);
    }
原来里面做了handler发送处理,在看一下handler是怎么处理的
private volatile ServiceHandler mServiceHandler;
private final class ServiceHandler extends Handler {
        public ServiceHandler(Looper looper) {
            super(looper);
        }
 
        @Override
        public void handleMessage(Message msg) {
            onHandleIntent((Intent)msg.obj);
            stopSelf(msg.arg1);
        }
    }


这里的onHandleIntent是以一个线程形式在处理了,就上面所说的工作线程,所以可以直接在onHandlerIntent中处理一些耗时工作
从上面我们知道,IntentService内部采用HandlerThread来执行任务,当任务执行完毕后IntentService会自动退出


版权声明:本文为CSDN博主「花姓-老花」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/zjws23786/article/details/51800929

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值