温故而知新——service

  • 只有 Activity、服务和内容提供程序可以绑定到服务 — 您无法从广播接收器绑定到服务。

1:Service和Thread的区别

  • service也是在主线程中运行的,因此在执行密集型或者阻止型操作的时候,还是需要在service中是通过线程来操作,比如播放音乐或者访问网络。

  • 在google文档中分析了service和子线程之间的应用区别,service主要是当用户未与应用交互也可在后台运行,而子线程呢,则主要是当用户与应用交互。比如同样是播放音乐,如果需求是当用户不与应用交互的情况下仍然要播放,那就是service,如果仅仅是在打开activity的时候播放,那么用子线程就可以。

2:Service的回调方法

(1):onStartCommand()
  • 当使用startService()来启动service,那么就回调用该方法。一旦调用该方法,service就会无限期执行。想要结束就需要调用stopSelf()/stopService(intent)。如果只想绑定服务,无需实现该方法。这里的stopService(intent)是在context中的方法context.stopService(intent),在其他组件中调用。如果使用的是重载方法stopSelf(startId)这里的startid需要与最后一个startid相等,否则不会关闭。
  • 三个返回方法:start_redeliver_intent(重建,上一个intent开始调用onstartcommand,挂起的itnent依次传递);start_sticky(重建,不会传递上一个intent,如果有挂起则依次传递,如果没有,则用空intent调用onstartcommand);start_not_sticky(除非有挂起intent,否则不重建);
(2):onBind()
  • 如果不希望被绑定,返回null即可

  • 如果希望绑定,那么就必须返回一个IBinder接口。

(3):onCreate()
  • 首次创建才会调用,调用在onStartCommand()和onBind()之前。非首次创建不调用
(4):onDestory()
  • service接收的最后一个回调。

3:xml中的属性

  • android:exported=false 表示当前这个service只能用于当前应用。其他应用无论显示隐式都不能使用。如果不设置的话,默认值会因为是否含有过滤器而改变。因为添加了过滤器意味着想要外部调用,那么这个值如果不设置,默认为true,如果没有过滤器,默认值就是false

  • 如果想要设置intent-filter,那么在代码中同时还要设置setPackage()。

  • enable,默认为true,是否允许系统创建实例

4:IntentService

  • 创建默认的工作线程,用于在应用的主线程外执行传递给 onStartCommand() 的所有 Intent。

  • 创建工作队列,用于将 Intent 逐一传递给 onHandleIntent() 实现,这样您就永远不必担心多线程问题。

  • 在处理完所有启动请求后停止服务,因此您永远不必调用 stopSelf()。

  • 提供 onBind() 的默认实现(返回 null)。

  • 提供 onStartCommand() 的默认实现,可将 Intent 依次发送到工作队列和 onHandleIntent() 实现。

  • 综上所述,您只需实现 onHandleIntent() 来完成客户端提供的工作即可。(不过,您还需要为服务提供小型构造函数。)

  public HelloIntentService() {
      super("HelloIntentService"); //必须调用,传入一个工作线程的名字
  }

5:通过HandlerThread模拟IntentService

intentservice的特点就是有一个子线程按照顺序依次执行请求,全部请求执行完成,就会自动关闭服务。模拟的关键就在于两点:
其一:子线程按照顺序依次执行,这个需求交给了handlerthread。通过获取这个handlerthread的looper,创建一个handler,依次传递任务。
其二:因为每次请求都会调用onstartcommand,而这个方法每次调用的参数startid是不一样的,因此在每次任务结束之后就会调用以下stopself(startID)来模拟,当所有任务完成就关闭服务。

(1):在onCreate方法中创建HandlerThread,和Handler。因为onCreate只会在创建时被调用一次.
  • 创建HandlerThread
HandlerThread handlerThread=new HandlerThread(MyIntentService.class.getSimpleName(), Process.THREAD_PRIORITY_BACKGROUND);
handlerThread.start();
  • 创建Handler
myHandler=new MyHandler(handlerThread.getLooper());
……
private final class MyHandler extends Handler{
        public MyHandler(Looper looper){
            super(looper);
        }
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            try {
                name=msg.getData().getString("name");
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
                Log.e(MyIntentService.class.getSimpleName(),"InterruptedException");
                Thread.currentThread().interrupt();
            }
            Log.e(MyIntentService.class.getSimpleName(),name+":"+msg.arg1);
            //the startId matches the last start request and the service will be stopped
            stopSelf(msg.arg1);
        }
    }

(2):重写onStartCommand
  • Messager.obtain()和myHandler.obtainMessage()两者的区别在于后者绑定了message的target为这个handler,前者没有。
 @Override
    public int onStartCommand(Intent intent,  int flags, int startId) {
//        Message obtain = Message.obtain();
        name=intent.getStringExtra("name");
        Log.e(MyIntentService.class.getSimpleName(),name+"---->onStartCommand");
        Message message = myHandler.obtainMessage();
        message.arg1=startId;
        if(intent !=null){
            message.setData(intent.getExtras());
        }
        myHandler.sendMessage(message);
        return START_STICKY;
    }

6:通过PendingIntent.getBroadCast()让普通Service与调用者通信。

  • 创建一个启动receiver的intent。receiver还是需要自己自定义。
  • 将这个启动receiver的intent放进PendingIntent.getBroadCast()的参数中。
  • 将这个pendingIntent作为一个变量,通过启动service的intent传递到service中bundle.putParcelable("receiver",pIntent);
  • 在service中需要发送消息的时候调用。需要发送的消息还是通过创建一个新的intent来传递。
PendingIntent client = bundle.getParcelable("receiver");  
Intent intent=new Intent();
intent.putExtra("param",name);
client.send(intent);

7:服务的三种运行方式

  • 启动服务,无限期运行,需要调用stopSelf或者stopService.重复开启,只会重复调用onStartCommand
  • 绑定服务,所有绑定者都退出(unbindService()),service销毁.重复绑定,不会重复调用onBind
  • 启动且绑定服务。先启动再绑定。就算所有绑定者都退出,还是需要调用stopSelf或者stopService才能销毁。如果还有一个绑定者未退出,那么就算调用了停止service方法,也没有用。
  • 也就是说只要调用了onStartCommand方法,服务就回无限期运行,就必须手动调用stopSelf/stopService停止
  • startService-->onCreate-->onStartCommand->onDestroy
  • bindService-->onCreate-->onBind-->onUnbind()-->onDestroy
  • stop停止service,并不会引起service的相关回调,而unbindService则会引起回调onUnbind

8:绑定服务

跨进程通信必须要绑定服务才能实现

(1):三种接口定义

  • 扩展Binder类。如果服务只是您的自有应用的后台工作线程,不需要跨进程,则优先采用这种方法。

LocalService.java

public class LocalService extends Service {
    private final IBinder mBinder = new LocalBinder();

    public class LocalBinder extends Binder {
        LocalService getService() {
            return LocalService.this;
        }
    }

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

    public int getRandomNumber() {
      return  new Random().nextInt(100);
    }
}

BindingActivity.java

public class BindingActivity extends Activity {
    LocalService mService;
    boolean mBound = false;

    ...

    @Override
    protected void onStart() {
        super.onStart();
        Intent intent = new Intent(this, LocalService.class);
        bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
    }

    @Override
    protected void onStop() {
        super.onStop();
        if (mBound) {
            unbindService(mConnection);
            mBound = false;
        }
    }


    public void onButtonClick(View v) {
        if (mBound) {
            int num = mService.getRandomNumber();
            Toast.makeText(this, "number: " + num, Toast.LENGTH_SHORT).show();
        }
    }

    private ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName className,
                IBinder service) {
            LocalBinder binder = (LocalBinder) service;
            mService = binder.getService();
            mBound = true;
        }

        @Override
        public void onServiceDisconnected(ComponentName arg0) {
            mBound = false;
        }
    };
}

onServiceDisconnected()
Android 系统会在与服务的连接意外中断时(例如当服务崩溃或被终止时)调用该方法。当客户端取消绑定时,系统“不会”调用该方法。如果您的服务已启动并接受绑定,则当系统调用您的 onUnbind() 方法时,如果您想在客户端下一次绑定到服务时接收 onRebind() 调用,则可选择返回 true。onRebind() 返回空值,但客户端仍在其 onServiceConnected() 回调中接收 IBinder。

服务在多进程不同应用间使用的时候。Messenger会在单一线程中创建包含所有请求的队列,也就是服务会一个一个按顺序完成请求。不必考虑多线程安全。关于Messenger的使用可以参看Messenger的例子。该例子介绍了,当服务绑定成功之后,acitvity发送一条消息到服务,服务回一条消息到activity。接下来用户每点击一次activity中的按钮发送消息到service,service都会自动回一条消息到activity。

Messenger也是以AIDL为底层结构,虽然说AIDL和Messenger都是服务于不同进程应用的,但是二者的区别在于,Messenger是单线程队列完成请求,有顺序,每次只完成一个。AIDL则是多线程,服务同时处理多个请求,需要考虑线程安全。

更新

在android8.0中对后台service做了限制,系统不允许后台应用创建后台服务。一旦通过 startForegroundService () 启动前台服务(这个api的版本要求是26),必须在 service 中有 startForeground () 配套,不然会出现 ANR 或者 crash.

另一方面给出了JobIntentService来替换后台服务。在8.0及以上使用了JobService。以下仍然使用了startService()

class MyJobIntentService : JobIntentService(){

   //直接调用这个静态方法就可以了
   companion object{
       fun enquewWork(context: Context,work:Intent ){
           enqueueWork(context,MyJobIntentService::class.java,1234,work);
       }
   }
   override fun onHandleWork(intent: Intent) {
       //这里面实际上是在Asynctask的线程中执行的。
       Log.e("MyJobIntentService",Thread.currentThread().name)
   }

}

当我们使用如上代码时需要注意在AndroidManifest.xml中申请相应的权限

        <service android:name=".service.MyJobIntentService" android:permission="android.permission.BIND_JOB_SERVICE"/>

以及

    <uses-permission android:name="android.permission.WAKE_LOCK" />

在调用时

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

推荐阅读更多精彩内容