记:Service中的小细节

一.关于Service的启动模式对比

1.startService方式

首先先来个总结性图片:


startService方式.png

接下来我们开始根据日志进行分析:
(1)新建一个类(TestStartService)继承Service,并在Manifest文件中进行注册

public class TestStartService extends Service {
    private static final String TAG = "TestStartService";

    @Override
    public void onCreate() {
        Log.e(TAG,"<onCreate>Thread Id="+Thread.currentThread().getId());
        super.onCreate();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.e(TAG,"<onStartCommand>Thread Id="+Thread.currentThread().getId()+",startId="+startId);
        return super.onStartCommand(intent, flags, startId);
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        Log.e(TAG,"<onBind>Thread Id="+Thread.currentThread().getId());
        return null;
    }

    @Override
    public void onDestroy() {
        Log.e(TAG,"<onDestroy> Thread Id="+Thread.currentThread().getId());
        super.onDestroy();
    }
}

(2)在Activity中,
a.进行3次启动操作,调用startService
b.执行1次停止操作,调用stopService()
c.再次启动,调用Service

public class MainActivity extends AppCompatActivity {

    private static final String TAG = "MainActivity";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        Log.e(TAG, "主线程id=" + Thread.currentThread().getId());

        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //3次启动
        Intent intent1 = new Intent(this, TestStartService.class);
        startService(intent1);
        Intent intent2 = new Intent(this, TestStartService.class);
        startService(intent2);
        Intent intent3 = new Intent(this, TestStartService.class);
        startService(intent3);

        //停止
        Intent intent4 = new Intent(this, TestStartService.class);
        stopService(intent4);


        //再启动
        Intent intent5 = new Intent(this, TestStartService.class);
        startService(intent5);
    }
}

执行结果Log信息如下:

E/TestStartService: <onCreate>Thread Id=2
E/TestStartService: <onStartCommand>Thread Id=2,startId=1
E/TestStartService: <onStartCommand>Thread Id=2,startId=2
E/TestStartService: <onStartCommand>Thread Id=2,startId=3
E/TestStartService: <onDestroy> Thread Id=2
E/TestStartService: <onCreate>Thread Id=2
E/TestStartService: <onStartCommand>Thread Id=2,startId=1

进行分析:我们进行了3次启动service方法,却只执行了1次onCreate方法,执行了3次onStartCommand;当进行stopService,会执行Service中的onDestory();再次启动service,执行了
onCreate和onStartCommand方法;打印出的threadId一直是2。

得出结论:①onCreate()只有第一次startService会执行,而后执行多少次startService都会执行多少次onStartCommand()②只有执行stopService,service才会被停止(仅对于此案例),证明service生命周期不是随组件而变化③一直运行在主线程中,故不要在service做过多耗时任务


2.bindService方式

再来一个总结性的图片


bindService方式.png

(1)和上面的方式一样,新建个类(TestBindService)继承Service

public class TestBindService extends Service {
    private static final String TAG = "TestBindService";

   //用于client中建立和service的连接,可以获取到service中方法
    public class MyBinder extends Binder{
        public TestBindService getService(){
            return TestBindService.this;
        }
    }

    private MyBinder myBinder=new MyBinder();

    private final Random generator = new Random();

    @Override
    public void onCreate() {
        super.onCreate();
        Log.e(TAG, "<onCreate> name=" + Thread.currentThread().getName());
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.e(TAG, "<onStartCommand> name=" + Thread.currentThread().getName());
        return START_NOT_STICKY;
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        Log.e(TAG, "<onBind> name=" + Thread.currentThread().getName());
        return myBinder;
    }

    @Override
    public boolean onUnbind(Intent intent) {
        Log.e(TAG, "<onUnbind> from=" + intent.getStringExtra("from"));
        return false;
    }

    @Override
    public void onDestroy() {
        Log.e(TAG, "<onDestroy> name=" + Thread.currentThread().getName());
        super.onDestroy();
    }

    //getRandomNumber是Service暴露出去供client调用的公共方法
    public int getRandomNumber() {
        return generator.nextInt();
    }
}

(2)新建一个Activity(ActivityA)
(3)布局中只是放入了线性排列的4个button
(4)由于此案例我们想要调用bindService方式启动,会发现需要需要3个参数:(android.content.Intent service, android.content.ServiceConnection conn, int flags) ,所以我们new一个ServiceConnection ,其中有两个方法:onServiceConnected()和onServiceDisconnected(),分别是service绑定成功和取消绑定执行的,附上这个类代码

public class ActivityA extends Activity implements View.OnClickListener {
    private static final String TAG = "ActivityA";

    private TestBindService bindService;
    private boolean isBind = false;

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_a);

        findViewById(R.id.btnBindService).setOnClickListener(this);
        findViewById(R.id.btnUnbindService).setOnClickListener(this);
        findViewById(R.id.btnStartActivityB).setOnClickListener(this);
        findViewById(R.id.btnFinish).setOnClickListener(this);
    }


    private ServiceConnection serviceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
            Log.e(TAG, "onServiceConnected");
            isBind = true;
            TestBindService.MyBinder myBinder = (TestBindService.MyBinder) iBinder;
            bindService = myBinder.getService();

            //证明可以再activity中调到service中方法:
            int num = bindService.getRandomNumber();
            Log.e(TAG, num + "");
        }

        @Override
        public void onServiceDisconnected(ComponentName componentName) {
            Log.e(TAG, "onServiceDisconnected");
            isBind = false;
        }
    };

    @Override
    public void onClick(View v) {
        Intent intent;
        switch (v.getId()) {
            case R.id.btnBindService:
                Log.e(TAG,"ActivityA 点击 bindService");
                intent = new Intent(ActivityA.this, TestBindService.class);
                bindService(intent, serviceConnection, BIND_AUTO_CREATE);
                //flag值:控制需要绑定的Service的行为和运行模式
                break;
            case R.id.btnUnbindService:
                if(isBind){
                    Log.e(TAG,"ActivityA 点击 unbindService");
                    unbindService(serviceConnection);
                }
                break;
            case R.id.btnStartActivityB:
                Log.e(TAG,"ActivityA 点击启动 ActivityB");

                intent=new Intent(ActivityA.this,ActivityB.class);
                startActivity(intent);
                break;
            case R.id.btnFinish:
                Log.e(TAG,"ActivityA 点击执行 finish");

                finish();
                break;
        }
    }

    @Override
    protected void onDestroy() {
        Log.e(TAG,"ActivityA onDestroy");
        super.onDestroy();
    }

}

(5)ActivityB布局和代码基本同ActivityA,只缺少一个跳转

public class ActivityB extends Activity implements Button.OnClickListener {
    private static final String TAG = "ActivityB";

    private TestBindService bindService;
    private boolean isBind = false;

    private ServiceConnection serviceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
            Log.e(TAG, "onServiceConnected");
            isBind = true;
            TestBindService.MyBinder myBinder = (TestBindService.MyBinder) iBinder;
            bindService = myBinder.getService();

            //证明可以再activity中调到service中方法:
            int num = bindService.getRandomNumber();
            Log.e(TAG, num + "");
        }

        @Override
        public void onServiceDisconnected(ComponentName componentName) {
            Log.e(TAG, "onServiceDisconnected");
            isBind = false;
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_b);

        findViewById(R.id.btnBindService).setOnClickListener(this);
        findViewById(R.id.btnUnbindService).setOnClickListener(this);
        findViewById(R.id.btnFinish).setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        Intent intent;
        switch (v.getId()) {
            case R.id.btnBindService:
                Log.e(TAG, "ActivityB 点击 bindService");
                intent = new Intent(ActivityB.this, TestBindService.class);
                bindService(intent, serviceConnection, BIND_AUTO_CREATE);
                //flag值:控制需要绑定的Service的行为和运行模式
                break;
            case R.id.btnUnbindService:
                if (isBind) {
                    Log.e(TAG, "ActivityB 点击 unbindService");
                    unbindService(serviceConnection);
                }
                break;
            case R.id.btnFinish:
                Log.e(TAG, "ActivityB 点击执行 finish");

                finish();
                break;
        }
    }

    @Override
    protected void onDestroy() {
        Log.e(TAG, "ActivityB onDestroy");

        super.onDestroy();
    }
}

下面开始按步骤总体分析:

a.ActivityA首次调用bindService:会回调Service的onCreate()、onBind(),ActivityA的ServiceConnection中的onServiceConnected()。因为这是service首次运行
b.ActivityA再次调用bindService:什么都不会执行。因为此时已经运行了
c.ActivityA执行unbindService:会执行onUnbind()和onDestroy()。Service检测是否还有其他client与其连接,如果没有Service执行onUnbind()和onDestroy()
d.ActivityA调用bindService后调用finish:会执行onUnbind()和onDestroy()。如果client销毁,那么client会自动与Service解除绑定。
e.销毁APP。重新进入ActivityA首次调用A的bindService,然后跳转到ActivityB,然后执行B的bindService:ActivityA的bindService执行onCreate()、onBind(),B的bindService只会执行onServiceConnected。原因在于此时Service已经运行起来了
f.ActivityB执行unbind():什么都不会执行。因为当前一共有client和service建立了连接,都finish或都解绑了才会回调
g.ActivityA执行unbind():会执行onUnbind()和onDestroy()。Service检测是否还有其他client与其连接,如果没有Service执行onUnbind()和onDestroy()

得出结论:①通过bindService方式建立的是client-service之间的一对多的关系,即一个service对应多个Acivitiy。只有对应的client都销毁或者调用了unbind(),service才会回调onUnbind()和onDestroy();②service生命周期受宿主client影响;③service依然运行在主线程中④不会回调onStartCommand()方法

二.关于onStartCommend的返回值

start_sticky、start_no_sticky、START_REDELIVER_INTENT、START_STICKY_COMPATIBILITY。
START_STICKY: 如果service进程被kill掉,保留service的状态为开始状态,但不保留递送的intent对象。随后系统会尝试重新创建service,由 于服务状态为开始状态,所以创建服务后一定会调用onStartCommand(Intent,int,int)方法。如果在此期间没有任何启动命令被传 递到service,那么参数Intent将为null;

START_NOT_STICKY:“非粘性的”。使用这个返回值时,如果在执行完onStartCommand后,服务被异常kill掉,系统不会自动重启该服务;

START_REDELIVER_INTENT:重传Intent。使用这个返回值时,如果在执行完onStartCommand后,服务被异常kill掉,系统会自动重启该服务,并将Intent的值传入;

START_STICKY_COMPATIBILITY:START_STICKY的兼容版本,但不保证服务被kill后一定能重启。

三.几个小练习问题

1.Service 的 start 和 bind 状态有什么区别?

eg:start方式,生命周期不受组件的影响,有独立的生命周期。多次startService(),只有第一次会执行onCreate(),多次调用onStartCommand()。
bind方式,生命周期会受组件的影响。一个service可以被多个组件绑定。oncreate和onbind只有第一次创建会被调用。全部组件都执行了onUnbindService,才会回调onUnbind方法。

2.同一个 Service,先 startService,然后再 bindService,如何把它停止掉?

eg:执行1次stopService和多次unbindService(每个绑定的Activity需执行一次)。这两个过程没有顺序

3.Service 的生命周期方法 onCreate、onStart、onBind 等运行在哪个线程?

eg:主线程。所以不能做过多的耗时任务,否则会ANR,可以使用广播或IntentService
简单描述IntentService:①它继承于Service,用于处理异步请求的类。因为它内部有一个HandlerThread处理耗时线程,而且会在执行完成后,自动调用结束方法,不需要stopService();②内部通过消息的方式发送给HandlerThread的,然后又Handler中的Looper来分发处理消息。是采用队列的形式,消息全部处理完毕后会自动调用销毁操作③优先级高不容易被杀死

三.Activity与Service之间如何传值?

//2018-12-20

1.利用ServiceConnection(bindService方式)

service(注意不要忘了在manifest中注册):

public class MyService extends Service {

    private static final String TAG = "MyService";

    private MyBinder myBinder = new MyBinder();

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

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

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

    class MyBinder extends Binder {
        public void setData(String value) {
            Log.e(TAG, value);
        }
    }
}

Activity中:

public class MainActivity extends AppCompatActivity {
    private MyService.MyBinder myBinder;
    private ServiceConnection myServiceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            myBinder = (MyService.MyBinder) service;
            myBinder.setData("方式1发送");
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Intent intent = new Intent(this, MyService.class);
        bindService(intent, myServiceConnection, BIND_AUTO_CREATE);

    }

}

2.利用Intent传值(startService方式)

service(注意不要忘了在manifest中注册):

public class MyService2 extends Service {
    private static final String TAG = "MyService2";

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

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        String value = intent.getStringExtra("key");
        Log.e(TAG, value);
        return super.onStartCommand(intent, flags, startId);
    }

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

Activity中:

public class IntentWayActivity extends Activity {
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_intent_way);

        findViewById(R.id.btStart).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(IntentWayActivity.this, MyService2.class);
                intent.putExtra("key", "方式2");
                startService(intent);
            }
        });
    }
}

3.callback回调方式,感觉略微繁琐

更多IntentService可以参考https://www.jianshu.com/p/8a3c44a9173a

四.新增IntentService

//2019.01.15
IntentService 继承Service,故作用同Service
IntentService 的onCreate()中包含了HandlerThread
特点:
1.按顺序执行发送的service
2.所有的service执行完成后会自动销毁,不需要调用stop
3.只能用startService()方式,不能用bindService(),因为只有startService()会回调onStartCommand(),通过onStartCommand()传递的Intent,并将此传递给onHandleIntent(),在这里面根据不同传值处理逻辑
4.继承Service,写构造方法时,需要super(name),这个name是我们自定义的,为什么这里需要个name呢?因为IntentService包含了HandlerThread,HandlerThread的初始化需要传入一个name,用于标识线程,所以这个name是给HandlerThread
使用步骤:
1.新建继承IntentService,重写各个方法
2.注册到Manifest文件中
3.在需要发送地方,使用startService()方式发送
完整代码:

public class MyIntentService extends IntentService{
    private static final String TAG = "MyIntentService";
    public MyIntentService() {
        //由于IntentService内部含有HandlerThread,
        //HandlerThread初始化要传个名字用来标识线程
        super("danny");
    }

    @Override
    protected void onHandleIntent(@Nullable Intent intent) {
        int type=intent.getIntExtra("type",0);
        switch (type){
            case 1:
                Log.e(TAG,"this is type1");
                break;
            case 2:
                Log.e(TAG,"this is type2");
                break;
        }
    }

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

        Log.e(TAG,"onCreate");
    }

    @Override
    public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
        Log.e(TAG,"onStartCommand");

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

    @Override
    public void onDestroy() {
        Log.e(TAG,"onDestroy");

        super.onDestroy();
    }
}
public class IntentServiceActivity extends Activity {
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_intent_service);

        Intent intent = new Intent(this, MyIntentService.class);
        intent.putExtra("type", 1);
        startService(intent);

        Intent intent2 = new Intent(this, MyIntentService.class);
        intent2.putExtra("type", 2);
        startService(intent2);
    }
}

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

推荐阅读更多精彩内容