四大组件之Service

     

    学之广在于不倦,不倦在于固志。 ——晋·葛洪­

  (学问的渊博在于学习时不知道厌倦,而学习不知厌倦在于有坚定的目标)

  全面到位的一篇Service------强烈推荐     2018/11/1 更新

   001.基本介绍:

         00a、概念:

                   Service是Android中实现程序后台运行的解决方案,四大组件之一

         00b、适用场景:

                  执行一些不需要和用户交互而且要求长期运行的任务,比如:听音乐&看电子书;检测SD卡的变化;记录你的地理位置变化;

         00c、特点:

                  服务的进程依赖于创建服务时所在的应用程序的进程,服务的运行不依赖于任何界面,

         注意:

                  服务默认在主线程运行,我们需要在服务内部手动创建子线程,并在这里执行具体的任务

 

    002.服务的启动与销毁:

          00a、步骤:

                   --->  创建一个类继承Service,Service是一个抽象类,必须重写onBind()方法

                                --->  清单文件注册对应的Service

 

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

                   --->  开启Service,具体区别看代码部分:

 

                         ---> startService();

                         ---> bindService();

                   ---> 销毁Service,具体见代码部分:

                         ---> stopService() / stopSelf()

                         ---> unBindService()

    003.代码实战:

          00a、startService开启服务

                 在MainActivity中添加两个按钮,一个用于开启服务,一个用于销毁服务,添加几个生命周期的打印信息。注意startService()和stopService()方法都是定义在Context类当中的,所以可以在MainActivity中直接调用这两个方法

 

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

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        Log.i(TAG, "onBind: ");
        return null;
    }

    //创建服务时调用
    @Override
    public void onCreate() {
        super.onCreate();
        Log.i(TAG, "onCreate: ");
    }

    //在服务执行操作
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.i(TAG, "onStartCommand: ");
        return super.onStartCommand(intent, flags, startId);
    }

    //销毁服务时调用
    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.i(TAG, "onDestroy: ");
    }
}
<service android:name=".service.MyService"/>

 

 

public class MainActivity extends AppCompatActivity {

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

        //点击开启服务
        findViewById(R.id.bt_start).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //构建出了一个Intent对象,并调用startService()方法来启动MyService
                Intent startIntent = new Intent(MainActivity.this, MyService.class);
                startService(startIntent);
            }
        });

        //点击关闭服务
        findViewById(R.id.bt_start).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //构建出了一个Intent对象,并调用stopService()方法来停止MyService
                Intent stopIntent = new Intent(MainActivity.this, MyService.class);
                startService(stopIntent);
            }
        });
    }
}

 

 

 

          点击了Start Service,打印如下,执行了onCreate和onStartCommand

 15555-15555/com.test.okamiy.mythread I/MyService: onCreate:

 15555-15555/com.test.okamiy.mythread I/MyService: onStartCommand:

      我接着连续点击了3下Start Service,打印如下,只执行了onStartCommand

 15555-15555/com.test.okamiy.mythread I/MyService: onStartCommand:

 15555-15555/com.test.okamiy.mythread I/MyService: onStartCommand:

 15555-15555/com.test.okamiy.mythread I/MyService: onStartCommand:

       注意:

              虽然每次调用一次startService()方法,onstartCommand()方法就会以执行一次,但实际上每个服务都只会存在一个实例。所以不管你调用了多少次startService()方法,只需调用一次stopService()或stopself()方法,服务就会停止

       经多次测试得出,onCreate()方法只会在Service第一次被创建的时候调用,而onStartCommand()方法在每次启动服务的时候都会调用

          此时我们可以在手机运行的应用程序查看,我们的服务已经运行起来了

      点击了Stop Service,打印如下,执行了onDestroy,服务已销毁
 15555-15555/com.test.okamiy.mythread I/MyService: onDestroy:

      

      总结:

              ---> startService开启的服务,停止服务有两种方式:

                             1> 在Service的内部任何地方调用stopSelf()即可停止服务

                             2> 在外部调用stopService()即可停止服务

              ---> startService开启服务,就可以在onCreate()或onStartCommand()方法里去执行一些具体的逻辑了,此时Service和Activity的关系并不大,只是Activity通知了Service一下:“你可以启动了。”然后Service就去忙自己的事情了。但Activity并不知道服务到底去做了什么,以及如何完成。

             ---> onStartCommand方法执行时,返回的是一个int类型。这个整型可以有三个返回值:

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

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

                              3> START_REDELIVER_INTENT:重传Intent。使用这个返回值时,系统会自动重启该服务,并将Intent的值传入。

          00b、bindService绑定服务

                 基于上面的代码。在MainActivity中再添加2个按钮,bind_service,unbind_service,添加点击事件

 

public class MyService extends Service {
    private static final String TAG = "MyService";
    private DownloadBinder mBinder = new DownloadBinder();

    //和Activity通信
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        Log.i(TAG, "onBind: ");
        return mBinder;
    }

    //创建服务时调用
    @Override
    public void onCreate() {
        super.onCreate();
        Log.i(TAG, "onCreate: ");
    }

    //在服务执行操作
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.i(TAG, "onStartCommand: ");
        return super.onStartCommand(intent, flags, startId);
    }

    //销毁服务时调用
    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.i(TAG, "onDestroy: ");
    }

    //Binder类,用于模拟下载,保存进度
    public class DownloadBinder extends Binder {

        public void startDownload() {
            Log.i(TAG, "startDownload: " + "开始下载");
        }

        public int getProgress() {
            Log.i(TAG, "getProgress: " + "获取了下载进度");
            return 0;
        }
    }
}
<service android:name=".service.MyService"/>
public class MainActivity extends AppCompatActivity {
  
 
private boolean mBind = false; //一开始,并没有和Service绑定.这个参数是用来显示绑定状态 

private MyService.DownloadBinder downloadBinder;

//匿名内部类:服务连接对象,有了这个实例我们就和Service联系紧密
//如果当前Activity与服务连接成功后,服务会回调onServiceConnected()方法
 
private ServiceConnection mConnection = new ServiceConnection() {

//和服务绑定成功后,服务会回调该方法
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
downloadBinder = (MyService.DownloadBinder) service;
downloadBinder.startDownload();
downloadBinder.getProgress();

 
mBind = true;

}

//当服务异常终止时会调用。注意,解除绑定服务时不会调用
@Override
public void onServiceDisconnected(ComponentName name) {

            mBind = false; //服务异常终止时,状态为未绑定

}
};

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

//点击绑定服务:将MainActivity和MyService进行绑定
findViewById(R.id.bind_start).setOnClickListener(new View.OnClickListener() {

@Override
public void onClick(View v) {
Intent intent = new Intent(MainActivity.this, MyService.class);
//参数3:标志位,BIND_AUTO_CREATE 表示在Activity和Service建立关联后会自动创建Service
bindService(intent, mConnection, BIND_AUTO_CREATE);
}
});

//点击解绑服务
findViewById(R.id.bind_stop).setOnClickListener(new View.OnClickListener() {

@Override
public void onClick(View v) {

              if (mBind) {
                  unbindService(mConnection);
                  mBind = false;
              }

}
});

//点击开启服务
findViewById(R.id.bt_start).setOnClickListener(new View.OnClickListener() {

@Override
public void onClick(View v) {
//构建出了一个Intent对象,并调用startService()方法来启动MyService
Intent startIntent = new Intent(MainActivity.this, MyService.class);
startService(startIntent);
}
});

  //点击关闭服务
findViewById(R.id.bt_stop).setOnClickListener(new View.OnClickListener() {

@Override
public void onClick(View v) {
//构建出了一个Intent对象,并调用stopService()方法来停止MyService
Intent stopIntent = new Intent(MainActivity.this, MyService.class);
stopService(stopIntent);
}
});
}
}          

 

          点击bind Service,打印如下,执行了onCreateonBind,并执行了startDownloadgetProgress方法,说明我们在Activity里面已经调用了Service的方法

 21456-21456/com.test.okamiy.mythread I/MyService: onCreate:
 21456-21456/com.test.okamiy.mythread I/MyService: onBind:
 21456-21456/com.test.okamiy.mythread I/MyService: startDownload: 开始下载
 21456-21456/com.test.okamiy.mythread I/MyService: getProgress: 获取了下载进度

          点击unbind Service,打印如下,执行了onDestory,解绑了Service

 21456-21456/com.test.okamiy.mythread I/MyService: onDestroy:

          总结:

                  ---> bindService绑定的服务,需要UnbindService解除绑定

                  --->bindService绑定服务,首先需要创建ServiceConnection的匿名类,重写了onServiceConnected()方法和onServiceDisconnected()方法,这两个方法分别会在Activity与Service建立关联和解除关联的时候调用;在onServiceConnected()方法中,我们又通过向下转型得到了MyBinder的实例,有了这个实例,我们就实现了Activity指挥Service干什么Service就去干什么的功能

                   ---> Service服务在整个应用程序范围内都是通用的,即MyService不仅可以和MainActivity建立关联,还可以和任何一个Activity建立关联,而且在绑定完成后它们都可以获取到相同的DownloadBinder实例

               

           注意:

                   ---> 在我们点击了bind Service,同时也点击了startService,我们需要分别点击unbindService和stopService才能销毁Service,没有先后顺序,说明,一个Service必须要在既没有和任何Activity关联又处理停止状态的时候才会被销毁(点击Stop Service按钮只会让Service停止,点击Unbind Service按钮只会让Service和Activity解除关联)

                    ---> 记得在Service的onDestroy()方法里去清理掉那些不再使用的资源,防止在Service被销毁后还会有一些不再使用的对象仍占用着内存

 

         startService和bindService区别

                ---> 生命周期:

                               1> 通过started方式开启的服务会一直运行在后台,需要由Service组件本身(stopSelf)或外部组件(stopService)来停止服务,service才会结束运行

                               2>  bind方式绑定的服务,生命周期就要依赖绑定的组件

                 --->参数传递:

                               1> started服务可以给启动的服务对象传递参数,但无法获取服务中方法的返回值

                               2> bind服务可以给启动的服务对象传递参数,也可以通过绑定的业务对象获取返回结果

                         所以,更多的时候是2种方式配合使用:第一次先使用started方式来启动一个服务,之后可以使用bind的方式绑定服务,从而可以直接调用业务方法获取返回值

 

          00c、创建前台服务:

                如果你希望Service可以一直保持运行状态,而不会由于系统内存不足的原因导致被回收,就可以考虑使用前台Service。前台Service和普通Service最大的区别就在于,它会一直有一个正在运行的图标在系统的状态栏显示,下拉状态栏后可以看到更加详细的信息,非常类似于通知的效果

               比如:墨迹天气,它的Service在后台更新天气数据的同时,还会在系统状态栏一直显示当前天气的信息

       只需要修改上面MyService里面的onCreate方法即可,添加如下代码即可变成前台service点击startService或者bindService就会在通知栏出现我们的通知

 

//创建服务时调用
@Override
public void onCreate() {
    super.onCreate();

    //创建通知
    NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
    builder.setSmallIcon(R.mipmap.ic_launcher);
    builder.setContentTitle("Notification comes");
    builder.setContentText("Okamiy send message");
    Notification notification = builder.build();

    //让MyService变成前台Service,并在系统状态栏显示 参数1:通知的id,参数2:notification对象
    startForeground(1, notification);
    Log.i(TAG, "onCreate: ");
}

 

 

 

          00d、IntentService:

                一个服务中的代码默认运行在主线程中,如果直接在服务里执行一些耗时操作,容易造成ANR异常,所以就需要用到多线程的知识了,因此一个标准的服务如下:

 

public class MyService extends Service {
    private static final String TAG = "MyService";
    //和Activity通信
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        Log.i(TAG, "onBind: ");
    }

    //创建服务时调用
    @Override
    public void onCreate() {
        super.onCreate();
        Log.i(TAG, "onCreate: ");
    }

    //在服务执行操作
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.i(TAG, "onStartCommand: ");
        new Thread(new Runnable() {
            @Override
            public void run() {
                //处理具体的逻辑
... stopSelf(); //服务执行完毕后自动停止 

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

  //销毁服务时调用
@Override
public void onDestroy() {
super.onDestroy();
Log.i(TAG, "onDestroy: ");
}
}
 

 

         虽说上面的这种写法并不复杂,但总会有一些程序猿忘记开启线程,或者忘记调用stopSelf()方法。为了可以简单地创建一个异步的、会自动停止的服务,Android专门提供了一个IntentService类,这个类就很好的解决了上面所提到的两种尴尬。另外,可以启动IntentService多次,而每一个耗时操作会以工作队列的方式在IntentService的onHandleIntent()回调方法中执行,并且每次只会执行一个工作线程,执行完第一个后,再执行第二个,以此类推。

 

public class MyIntentService extends IntentService {

    private static final String TAG = "MyIntentService";

    public MyIntentService() {
        //调用父类的有参构造,这里我们手动给服务起个名字为:MyIntentService
        super("MyIntentService");
    }

    /**
     * 该方法执行在子线程,可以进行耗时操作和处理一些逻辑
     *
     * @param intent
     */
    @Override
    protected void onHandleIntent(@Nullable Intent intent) {
        for (int i = 0; i < 3; i++) {
            try {
                Log.i(TAG, "onHandleIntent: " + "IntentService的线程: " + Thread.currentThread().getId());
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.i(TAG, "onDestroy: ");
    }
}
<service android:name=".service.MyIntentService"/>
public class MainActivity extends AppCompatActivity {
    private static final String TAG = "MainActivity";

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

        //点击启动IntentService
        findViewById(R.id.start_IntentService).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.i(TAG, "onClick: " + "MainActivity的线程:" + Thread.currentThread().getId());
                Intent intent = new Intent(MainActivity.this, MyIntentService.class);
                startService(intent);
            }
        });
   }
}

        

 

         点击IntentService,打印信息如下,说明:MyIntentService是运行在子线程的,并且在执行完耗时操作或者相应的逻辑之后自动执行了Service的销毁,集开启线程和自动停止于一身

/com.test.okamiy.mythread I/MainActivity: onClick: MainActivity的线程:1

/com.test.okamiy.mythread I/MyIntentService: onHandleIntent: IntentService的线程: 3503 /com.test.okamiy.mythread I/MyIntentService: onHandleIntent: IntentService的线程: 3503
/com.test.okamiy.mythread I/MyIntentService: onHandleIntent: IntentService的线程: 3503 /com.test.okamiy.mythread I/MyIntentService: onDestroy:

           

         00e、Service和Thread:

              ---> Service和Thread之间没有任何关系

                     1> Thread我们大家都知道,是用于开启一个子线程,在这里去执行一些耗时操作就不会阻塞主线程的运行

                     2> 而Service我们最初理解的时候,总会觉得它是用来处理一些后台任务的,一些比较耗时的操作也可以放在这里运行。但是,Service默认是运行在主线程的,可以自行打印测试;

                     3> Android中的后台就是指,它的运行是完全不依赖UI的,即使Activity被销毁,或者程序被关闭,只要进程还在,Service就可以继续运行。比如说一些应用程序,始终需要与服务器之间始终保持着心跳连接,就可以使用Service来实现,

                     4> 在Service里面执行耗时操作,是指在Service里面起一个线程Thread执行的耗时操作。

                          在Service里面起线程执行耗时操作的原因:

                                  这是因为Activity很难对Thread进行控制,当Activity被销毁之后,就没有任何其它的办法可以再重新获取到之前创建的子线程的实例。而且在一个Activity中创建的子线程,另一个Activity无法对其进行操作。但是Service就不同了,所有的Activity都可以与Service进行关联,然后可以很方便地操作其中的方法,即使Activity被销毁了,之后只要重新与Service建立关联,就又能够获取到原有的Service中Binder的实例。因此,使用Service来处理后台任务,Activity就可以放心地finish,完全不需要担心无法对后台任务进行控制的情况

               ---> 基于上述一个比较标准的Service就产生了:

 

public class MyService extends Service {
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                // 开始执行后台任务  
                ...
                stopSelf();//任务完成之后销毁服务
            }
        }).start();
        return super.onStartCommand(intent, flags, startId);
    }

    class MyBinder extends Binder {

        public void startDownload() {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    // 执行具体的下载任务  
                    ...
                    stopSelf();//任务完成之后销毁服务
                }
            }).start();
        }
    }
}

 

 

 

    004.个人总结可能有误,欢迎指正,参考篇:

          00a.郭霖大神(基础到进阶): 传送门---郭霖大神 

          00b.这篇是博客园的(全面而细致):传送门---细致入微

 

          Last:欢迎探讨学习          

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值