Service

Service是Android中实现程序后台运行的解决方案,它非常适用于去执行那些不需要和用户交互而且还要长期运行的任务。

  • Service生命周期
    Service生命周期可以从两种启动Service的模式开始讲起,分别是context.startService()context.bindService()
    • startService的启动模式下的生命周期:当我们首次使用startService()启动一个服务时,系统会实例化一个Service实例,依次调用其onCreate()和onStartCommand()方法,然后进入运行状态。此后,如果再使用startService()启动服务时,不再创建新的服务对象,每个服务只会存在一个实例,系统会自动找到刚才创建的Service实例,直接调用了onStartCommand()方法(onStartCommand()中又调用了onStart()方法)。如果我们想要停掉一个服务,可使用stopService()方法,此时onDestroy()方法会被调用。需要注意的是,不管前面使用了多次startService(),只需一次stopService(),或自身的stopSelf()方法,即可停掉服务。
    • bindService启动模式下的生命周期:在这种模式下,当调用者首次使用bindService()绑定一个服务时,系统会实例化一个Service实例,并依次调用其onCreate()方法和onBind()方法,不管之后调用 bindService() 几次,onCreate()方法都只会调用一次,同时onBind()方法始终不会被调用。如果我们需要解除与这个服务的绑定,可调用Context.unbindService() 断开连接或者之前调用bindService()的 Context 不存在了(如Activity被finish的时候),系统将会自动停止Service,此时onUnbind()方法和onDestroy()方法会被调用。
      两种方式的流程:
Service生命周期.png

注:

  • 当一个Service被终止(1、调用stopService();2、调用stopSelf();3、不再有绑定的连接(没有被启动))时,onDestroy()方法将会被调用,在这里你应当做一些清除工作,如停止在Service中创建并运行的线程。
  • 在调用 bindService 绑定到Service的时候,你就应当保证在某处调用unbindService 解除绑定(尽管 Activity 被 finish() 的时候绑定会自动解除,并且Service会自动停止)。
  • 同时使用 startService() 与 bindService() 要注意到,Service的终止,需要unbindService()与stopService()同时调用,才能终止Service,不管 startService() 与 bindService() 的调用顺序,如果先调用 unbindService() 此时服务不会自动终止,再调用 stopService() 之后服务才会停止,如果先调用 stopService() 此时服务也不会终止,而再调用 unbindService() 或者 之前调用 bindService() 的 Context 不存在了(如Activity 被 finish() 的时候)之后服务才会自动停止;
  • 当在旋转手机屏幕的时候,当手机屏幕在“横”“竖”变换时,此时如果你的 Activity 如果会自动旋转的话,旋转其实是 Activity 的重新创建,因此旋转之前的使用 bindService 建立的连接便会断开(Context 不存在了),对应服务的生命周期与上述相同。
  • 一般是重写onStartCommand方法,。

startService()示例:

public class MyService extends Service {  
  
    private static final String TAG = "MyService";  
      
    @Override  
    public void onCreate() {  
        super.onCreate();  
        Log.i(TAG, "onCreate called.");  
    }  
      
    @Override  
    public int onStartCommand(Intent intent, int flags, int startId) {  
        Log.i(TAG, "onStartCommand called.");  
        return super.onStartCommand(intent, flags, startId);  
    }  
      
    @Override  
    public void onStart(Intent intent, int startId) {  
        super.onStart(intent, startId);  
        Log.i(TAG, "onStart called.");  
    }  
      
    @Override  
    public IBinder onBind(Intent intent) {  
        Log.i(TAG, "onBind called.");  
        return null;  
    }  
      
    @Override  
    public boolean onUnbind(Intent intent) {  
        Log.i(TAG, "onUnbind called.");  
        return super.onUnbind(intent);  
    }  
      
    @Override  
    public void onDestroy() {  
        super.onDestroy();  
        Log.i(TAG, "onDestroy called.");  
    }  
}  

每个Service必须在AndroidManifest.xml中 通过<service>来注册。

<service android:name=".service.MyService" ></service>  

上述代码表示一个在应用包service目录下的继承Service的Myservice服务。

点击事件:

/** 
 * 启动服务按钮方法 
 * @param view 
 */  
public void start(View view) {  
    Intent intent = new Intent(this, MyService.class);  
    startService(intent);  
}  
  
/** 
 * 停止服务按钮方法 
 * @param view 
 */  
public void stop(View view) {  
    Intent intent = new Intent(this, MyService.class);  
    stopService(intent);  
}  

bindService()示例:
因为前面服务中的onBind方法返回值为null,因此,要想实现绑定操作,必须返回一个实现了IBinder接口类型的实例,该接口描述了与远程对象进行交互的抽象协议,有了它我们才能与服务进行交互。

@Override  
public IBinder onBind(Intent intent) {  
    Log.i(TAG, "onBind called.");  
    return new Binder() {};  
}  

返回了一个Binder的实例,而这个Binder恰恰是实现了IBinder接口,这样就可以实现绑定服务的操作了。

public class MainActivity extends AppCompatActivity{  
      
    private static final String TAG = "MainActivity";  
    private boolean binded;  
      
    @Override  
    public void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.main);  
    }  
      
    private ServiceConnection conn = new ServiceConnection() {  
          
        @Override  
        public void onServiceConnected(ComponentName name, IBinder service) {  
            binded = true;  
            Log.i(TAG, "onServiceConnected called.");  
        }  
          
        @Override  
        public void onServiceDisconnected(ComponentName name) {  
        }  
    };  
      
    /** 
     * 绑定服务 
     * @param view 
     */  
    public void bind(View view) {  
        Intent intent = new Intent(this, MyService.class);  
        bindService(intent, conn, Context.BIND_AUTO_CREATE);  
    }  
      
    /** 
     * 解除绑定 
     * @param view 
     */  
    public void unbind(View view) {  
        unbindService();    
    }  

    //在MainActivity销毁之前如果没有进行解除绑定会导致后台出现异常信息
    @Override  
    protected void onDestroy() {  
        super.onDestroy();  
        unbindService();  
    } 
      
    /** 
     * 解除服务绑定 
     */  
    private void unbindService() {  
        if (binded) {  
            unbindService(conn);  
            binded = false;  
        }  
    } 
}  

在使用bindService()绑定服务时,我们需要一个ServiceConnection代表与服务的连接,使用bindService()启动服务后调用者和服务绑定到了一起,当调用者被销毁,服务也立即结终止。

  • 创建前台服务
    前台服务的优点:Service默认是运行在后台的,因此,它的优先级相对比较低,当系统出现内存不足的情况时,它就有可能被回收掉。如果将Service运行在前台,Service就可以一直保持运行状态,而不会被系统因内存不足回收。它可以在通知栏显示消息,当服务被终止的时候,通知栏的 Notification 也会消失,这样对于用户有一定的通知作用。常见的如更新天气信息,音乐播放服务。设置服务为前台服务,使用的方法是 startForeground()与 stopForeground()
public class WeatherService extends Service{
    private static final int NOTIFY_ID = 123;
    
    @Override
    public void onCreate(){
        super.onCreate();
        showNotification();
    }

    //在通知栏显示天气信息
    private void showNotification(){
        NotificationCompat.Builder mBuilder = 
            new NotificationCompat.Builder(this)
                .setSmallIcon(R.drawable.weather)
                .setContentTitle(getText(R.string.the_day))
                .setContentText(getText(R.string.weather));
        //创建通知被点击时触发的Intent
        Intent resultIntent = new Intent(this, MainActivity.class);
        
        //创建任务栈Builder
        TaskStackBuilder stackBuilder = TaskStackBuilder.create(this);
        stackBuilder.addParentStack(MainActivity.class);
        stackBuilder.addNextIntent(resultIntent);
        PendingIntent resultPendingIntent = 
            stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
        mBuilder.setContentIntent(resultPendingIntent);
      
        NotificationManager mNotification = 
            (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
        //构建通知
        final Notification notification = mBuilder.builder();
        //显示通知
        mNotification.notify(NOTIFY_ID, notification);
        //启动前台服务
        startForeground(NOTIFY_ID, notification);
    }
}

使用 startForeground(),如果NOTIFY_ID为 0 ,那么notification将不会显示。
另:
使用Notification通知栏的时候,用TaskStackBuilder来获取PendingIntent处理点击跳转到别的Activity。使用TaskStackBuilder来实现在通知栏中如果有Notification通知的话,点击它,然后会直接跳转到对应的应用程序的某个界面,这时如果按下Back键,会返回到该应用程序的主界面,而不是系统的主界面。addParentStack()是用来添加一个Activity,与这个Activity的AndroidManifest.xml文件中的parentActivityName的属性相关联。

<activity  
    android:name="....."  
    android:parentActivityName=".MainActivity" >  
</activity>  

  • 进程内与服务通信
    进程内与服务通信实际上就是通过bindService的方式与服务绑定,获取到通信中介Binder实例,然后通过调用这个实例的方法,完成对服务的各种操作。
public class MyService extends Service {  
  
    private static final String TAG = "MyService";  
      
    @Override  
    public IBinder onBind(Intent intent) {  
        Log.i(TAG, "onBind called.");  
        return new MyBinder();  
    }  
      
    /** 
     * 绑定对象 
     * @author user 
     * 
     */  
    public class MyBinder extends Binder {  
          
        /** 
         * 问候 
         * @param name 
         */  
        public void greet(String name) {  
            Log.i(TAG, "hello, " + name);  
        }  
    }  
}  

我们创建了一个MyBinder的内部类,定义了一个greet方法,在onBind方法中就将这个MyBinder的实例返回,只要调用者获取到这个实例,就可以像拿着游戏手柄一样对服务进行操作。

public class MainActivity extends AppCompatActivity{  
      
    /** 
     * 绑定对象实例 
     */  
    private MyService.MyBinder binder;  
    private boolean binded;  
      
    @Override  
    public void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.main);  
    }  
      
    private ServiceConnection conn = new ServiceConnection() {  
          
        @Override  
        public void onServiceConnected(ComponentName name, IBinder service) {  
            binder = (MyService.MyBinder) service;  //获取其实例  
            binder.greet("coder");                  //调用其方法  
        }  
          
        @Override  
        public void onServiceDisconnected(ComponentName name) {  
        }  
    };  
      
    /** 
     * 绑定服务 
     * @param view 
     */  
    public void bind(View view) {  
        Intent intent = new Intent(this, MyService.class);  
        bindService(intent, conn, Context.BIND_AUTO_CREATE);  
    }  
      
     /** 
     * 解除绑定 
     * @param view 
     */  
    public void unbind(View view) {  
        unbindService();    
    }  

    //在MainActivity销毁之前如果没有进行解除绑定会导致后台出现异常信息
    @Override  
    protected void onDestroy() {  
        super.onDestroy();  
        unbindService();  
    } 
      
    /** 
     * 解除服务绑定 
     */  
    private void unbindService() {  
        if (binded) {  
            unbindService(conn);  
            binded = false;  
        }  
    } 
}

在绑定服务成功时将IBinder类型的service参数强转为MyService.MyBinder类型,获取绑定中介实例,然后调用其greet方法。绑定服务后,去操作binder对象,也许它还为null,这就容易引起空指针异常,正确的做法是把这些操作放到绑定成功之后。


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

推荐阅读更多精彩内容