Service全面解析

来源:互联网 发布:js获取滚动条高度 编辑:程序博客网 时间:2024/06/05 04:56

Android Service定义

服务是一种长期在后台运行的程序组件,它不提供用户界面
长期运行在后台 没有界面的activity就是服务servie

使用服务的原因:

子线程也没有界面也可以工作,但是他是空线程容易被回收.而服务就不容易被回收,服务即使进程停止也能自动恢复

Service的生命周期

Service不能通过new方法创建,必须是系统创建出来的才能使用。

启动服务的方法有两种:

  1. Started启动,应用组件通过startService(Intent intent)启动服务,一旦开启,那么服务就会无限期在后台运行,哪怕开启它的组件被销毁了。但是它可以通过stopService(Intent intent)停止服务,通过此方法第一次启动服务的时候调用onCreate()和onStartCommand()方法,如果该服务已经存在,那么再次启动则只执行onStartCommand()。
  2. Bound绑定,应用组件通过bindService方法与之绑定启动,一个绑定的服务提供一个客户-服务端接口,以允许组件与服务交互,发送请求,获得结果,甚至执行进程间通信。最后它通过unbindService方法移除绑定关闭。一个绑定的服务只和与其绑定的组件同时运行。多个组件可以同时绑定到一个服务,但当全部接触绑定后,服务就被销毁。。

注意:通过bindService绑定服务的时候注意,如果之前没有服务,那么服务就会被创建出来,创建出来的方法会和应用组件不求同生,但求共死。但是如果绑定之前已经有了服务,那么就拿过来直接绑定,但是它不能与应用组件共死了。

查看项目zx_service_lifecycle,启动服务:

/** * 启动服务服务 */public void startService(View v){    startService(intent);}/** * 关闭服务 */public void stopService(View v){    stopService(intent);}/** * 绑定服务 */public void bindService(View v){    conn = new MyServiceConnect();    bindService(intent,conn,BIND_AUTO_CREATE);}/** * 移除绑定 *public void unbindService(View view){    unbindService(conn);}

启动前台服务

前台服务是被认为是用户已知的正在运行的服务,当系统需要释放内存时不会优先杀掉该进程。前台进程必须发一个notification在状态栏中显示,直到进程被杀死。因为前台服务会一直消耗一部分资源,但不像一般服务那样会在需要的时候被杀掉,所以为了能节约资源,保护电池寿命,一定要在建前台服务的时候发notification,提示用户。当然,系统提供的方法就是必须有notification参数的,所以不要想着怎么把notification隐藏掉。

@Overridepublic int onStartCommand(Intent intent, int flags, int startId) {    Intent notificationIntent = new Intent(this,MainActivity.class);    PendingIntent pendingIntent = PendingIntent.getActivity(this,0,notificationIntent,0);    Notification noti = new Notification.Builder(this)            .setContentTitle("Title")            .setContentText("Message")            .setSmallIcon(R.mipmap.ic_launcher)            .setContentIntent(pendingIntent)            .build();    //startForeground就是启动前台服务,第一个参数123456是通知的唯一参数,也可以设置成其他参数,只要不为零即可    startForeground(123456,noti);    return Service.START_STICKY;}

onStartCommand方法常用的3种返回值及其使用

在android开发中,经常遇到android的内存不足会销毁正在运行的服务Service,当内存充足的情况下,又会重新启动服务的情况。android的服务service的销毁与重建的行为依赖于service中onStartCommand()的返回值。

onStartCommand()方法常用返回值有3种:

  1. START_NOT_STICKY:表示当Service运行的进程被Android系统强制杀掉之后,不会重新创建该Service,当然如果在其被杀掉之后一段时间又调用了startService,那么该Service又将被实例化。那什么情境下返回该值比较恰当呢?如果我们某个Service执行的工作被中断几次无关紧要或者对Android内存紧张的情况下需要被杀掉且不会立即重新创建这种行为也可接受,那么我们便可将 onStartCommand的返回值设置为START_NOT_STICKY。
  2. START_STICKY:如果返回START_STICKY,表示Service运行的进程被Android系统强制杀掉之后,Android系统会将该Service依然设置为started状态(即运行状态),但是不再保存onStartCommand方法传入的intent对象,然后Android系统会尝试再次重新创建该Service,并执行onStartCommand回调方法,但是onStartCommand回调方法的Intent参数为null,也就是onStartCommand方法虽然会执行但是获取不到intent信息。如果你的Service可以在任意时刻运行或结束都没什么问题,而且不需要intent信息,那么就可以在onStartCommand方法中返回START_STICKY,比如一个用来播放背景音乐功能的Service就适合返回该值。
  3. START_REDELIVER_INTENT:如果返回START_REDELIVER_INTENT,表示Service运行的进程被Android系统强制杀掉之后,与返回START_STICKY的情况类似,Android系统会将再次重新创建该Service,并执行onStartCommand回调方法,但是不同的是,Android系统会再次将Service在被杀掉之前最后一次传入onStartCommand方法中的Intent再次保留下来并再次传入到重新创建后的Service的onStartCommand方法中,这样我们就能读取到intent参数。只要返回START_REDELIVER_INTENT,那么onStartCommand重的intent一定不是null。如果我们的Service需要依赖具体的Intent才能运行(需要从Intent中读取相关数据信息等),并且在强制销毁后有必要重新创建运行,那么这样的Service就适合返回START_REDELIVER_INTENT。

调用服务中的方法

前面讲到两种启动服务的方法,第二种方法的主要目的就是要调用服务中的方法。

例如:我们过年回家需要买火车票,但是向谁付钱买火车票呢?火车票是从铁路部门过来的,所以我们需要付钱给铁路部门,让它给我票,但是我们不能直接向铁路部门去买票,我们需要通过售票窗口。服务Service就像铁路部门,他有提供火车票的方法,但是我们不能调用,只能通过售票窗口,让售票窗口来调用铁路的提供火车票的方法。

RailwaySectorService是铁路部门,TicketWindow是售票窗口

public class RailwaySectorService extends Service {    @Nullable    @Override    public IBinder onBind(Intent intent) {        return new TicketWindow();    }    /**     * 提供票的方法     */    public void ticket(){        Toast.makeText(this, "给,您的票,祝你旅途愉快。。。。。。。。。。", Toast.LENGTH_SHORT).show();    }    /**     * TicketWindow就是售票窗口,它可以提供卖票和卖饮料的方法。     * 但是实际中售票窗口只能售票,不能卖饮料。卖饮料是售票窗口自己的方法,不是铁路部门给他的方法。     * 这里铁路部门通过IService接口和将该类定义成private给它做了限制,它只能卖票     *     */    private class TicketWindow extends Binder implements IService{        /**         * 卖票         * @param money         */        public void sellTickets(double money){            if(money<350.0){                Toast.makeText(RailwaySectorService.this, "还差"+(350-money)+"元", Toast.LENGTH_SHORT).show();            }else{                ticket();            }        }        /**         * 卖饮料         * @param money         */        public void sellDrinks(double money){            if(money<3.0){                Toast.makeText(RailwaySectorService.this, "这些钱不够买饮料的", Toast.LENGTH_SHORT).show();            }else{                Toast.makeText(RailwaySectorService.this, "这是你的饮料", Toast.LENGTH_SHORT).show();            }        }    }}

MainActivity:

public class MainActivity extends AppCompatActivity {    Intent intent = null;    public MyServiceConnection conn = null;    public IService tw;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        intent = new Intent(this,RailwaySectorService.class);        conn = new MyServiceConnection();        bindService(intent,conn,BIND_AUTO_CREATE);    }    public void buyTicket(View view){        tw.sellTickets(360.0);//        tw.sellDrinks(4.0);    }    class MyServiceConnection implements ServiceConnection {        /**         * 当RailwaySectorService中的onBind被调用,并且返回TicketWindow的对象时候,才调用         * @param name         * @param service  TicketWindow,售票窗口         */        @Override        public void onServiceConnected(ComponentName name, IBinder service) {            //因为RailwaySectorService中的TicketWindow是私有的,所以只能转换成它的接口IService            //接口IService中只有卖票的方法            tw = (IService) service;        }        @Override        public void onServiceDisconnected(ComponentName name) {        }    }}

IService接口:

public interface IService {    public void sellTickets(double money);}

混合使用两种启动Service方法:

案例:音乐播放器

网上的音乐播放器都是在退出应用的时候都能播放音乐,这是因为播放音乐的方法在服务Service中,退出应用还能播放,这需要使用startService()方法启动Service,然而要调用服务Service中的方法,那么这就需要使用bindService()方法启动Service。

/**     * 启动服务,这种启动方法使得Service会一直在后台运行,     * 但是此种方法不能调用服务Service中的方法     */    intent = new Intent(this, MusicService.class);    startService(intent);    /**     * 绑定服务,目的是调用Service中播放音乐的方法     * 因为,前面已经启动服务了,所以在绑定服务之后,重新移除绑定的话,服务还会在后台运行     */    conn = new MusicServiceConnection();    bindService(intent,conn,BIND_AUTO_CREATE);

绑定远程服务

调用远程服务就是调用其它应用的服务。

例如:

创建一个远程服务:

public class RemoteService extends Service {    public RemoteService() {    }    @Nullable    @Override    public IBinder onBind(Intent intent) {        return new RemoteBinder();    }    public void method(){        System.out.println("调用远程服务。。。。。。。。。。。。。");    }    private class RemoteBinder extends IService.Stub {        public void callMethod(){            method();        }    }}

清单文件AndroidManifest:

    <service        android:name=".RemoteService"        android:exported="true">        <intent-filter>            <action android:name="cn.demo.zx_remote_service.YCFW"/>        </intent-filter>    </service>

其中exported属性值为true的时候,表示可以被其他应用访问,false表示不能被其他应用访问。
IService原先是一个接口,现状也要改成.aidl文件,系统会自动生成对应的java文件,android studio开发工具就在build->generated->source->aidl->debug中查看。

创建一个调用远程服务的项目:
这里最主要的就是将访问项目中aidl文件与包拷贝过来,使得此项目中的aidl文件的包名和文件与被调用项目中完全一致。android studio中rebuild一下,生成对应的java文件。


这里还要注意,android5.0之后调用远程服务不能使用隐式调用,否则会报:

怎样解决这个问题呢?

public class MyIntent {    public static Intent createExplicitFromImplicitIntent(Context context,Intent implicitIntent){        /**         * 获取包管理器         * 然后通过包管理器获取所有的能够匹配implicitIntent意图的服务入口信息         */        PackageManager packageManager = context.getPackageManager();        List<ResolveInfo> resolveInfos = packageManager.queryIntentServices(implicitIntent, 0);        if(resolveInfos==null || resolveInfos.size()!=1){            return null;        }        /**         * 获取其中一个匹配的服务入口信息         * 然后通过这个入口信息获取包名和服务的名称         * 然后通过包名和服务名称创建一个组件         * 这个组件包含服务所在项目的包名和服务的名称。         */        ResolveInfo resolveInfo = resolveInfos.get(0);        String packageName = resolveInfo.serviceInfo.packageName;        String name = resolveInfo.serviceInfo.name;        ComponentName componentName = new ComponentName(packageName,name);        /**         * 根据原先意图在创建一个Intent,然后给这个意图加上要打开的组件信息         * 通过这个Intent就能打开指定包名和服务名称的服务了         */        Intent intent = new Intent(implicitIntent);        intent.setComponent(componentName);        return intent;    }}

然后再调用服务:

intent = new Intent();intent.setAction("cn.demo.zx_remote_service.YCFW");Intent intent1 = new Intent(MyIntent.createExplicitFromImplicitIntent(this,intent));conn = new RemoteServiceConn();bindService(intent1,conn,BIND_AUTO_CREATE);

源码下载:

https://github.com/zhangxun1900/my_service_learn

0 0