又是一年跳槽旺季,似乎好多人又开始蠢蠢欲动看机会,这时候又得面对各种面试。
于是你在简历上写:1.精通安卓四大组件;...
面试官:”那就简单说说service,有什么作用?与thread的关系?...“你:“...”(卧槽,我真的了解service了吗?)
你口难开,第一感觉,耗时操作呗,具体点呢?说明还是不了解嘛,知道startService/stopService就是了解或者熟悉了?如果真正熟悉,一定不会说不上来,至于怎么表达应该没有什么标准答案。来回就是围绕着那些知识点表述而已。
嗯~废话少说,service会分几篇来讲,这是第一篇从最简单的使用开始说起。
- service 简单上手使用(startService/stopService)
-
service 在manifest配置文件中参数说明
- service 与Activity(其它组件)通信方式
- service 两种启动方式关系说明
- service onStartCommand返回值详解
- service 与thread的关系
service 简单上手使用(startService/stopService)
service通常叫做后台服务,说明它是在后台(通常情况下)为其他组件提供服务的,没有用户交互页面,启动之后长时间存在,直到被主动停止或者系统内存不足kill掉。继承Service实现ServiceDemo
public class LocalService extends Service { public static final String TAG = "LocalService"; @Override public void onCreate() { super.onCreate(); LogUtil.outE(TAG, "onCreate"); } @Override public int onStartCommand(Intent intent, int flags, int startId) { LogUtil.outE(TAG, "onStartCommand"); return super.onStartCommand(intent, flags, startId); } @Override public void onDestroy() { super.onDestroy(); LogUtil.outE(TAG, "onDestroy"); } @Override public IBinder onBind(Intent intent) { return null; } }代码很简单,没有任何业务逻辑,重写生命周期,打印log方便调试。service是基本组件,跟activity一样需要在 `manifest` 中注册之后才能正常使用
<service android:name=".service.LocalService" />这里插入说明,如果没有特殊需求,一般只需要在 `manifest` 注册service就可以了。针对项目中不同的需求,service组件的配置参数如下:
<service android:enabled=["true" | "false"] android:exported=["true" | "false"] android:icon="drawable resource" android:isolatedProcess=["true" | "false"] android:label="string resource" android:name="string" android:permission="string" android:process="string" > . . . </service>
跟activity相比也很多相似的声明,例如:label,name,icon,主要讲解一下特殊的声明:
- android:exported :是否能被其他应用隐式调用,跟activity一样,其他应用可以通过隐式调用唤起activity/service,如果设置为 false 则无法被其他应用调用。设置为true时,根据intent-filter匹配。默认值:如果有intent-filter,默认值为true,否则为false
- android:process :是否在单独进程中运行。当设置为android:process=":remote",代表此service在单独的进程中运行,注意" **:** " 很重要,意思是在当前进程名称前附加应用包名,"remote" 和 " :remote " 不是同一个意思。"remote"表示进程名称为 `remote` ; " :remote " 表示进程名称为 `app包名:remote`
特殊备注:
1.使用process会有一个坑,设置之后会导致Application的onCreate调用多次,可以通过当前进程名称判断是否做主进程初始化的逻辑。
2."remote"就是名称前面没有 ** : ** 的情况,不允许设置单独一个单词,会安装失败,通过测试,发现使用类似`com.remote` 或者 `.remote` 都可以,就是不允许一个单词 `remote`
- android:permission :权限声明
- android:enabled :是否可以被系统实例化,默认为 true。父标签 也有 enable 属性,必须两个都为默认值 true 的情况下服务才会被激活,否则不会激活
starService()/stopService启动服务,很简单,构建Intent,调用 `startService`
Intent intent = new Intent(MainActivity.this, LocalService.class); startService(intent);
启动服务之后,service就一直运行在主线程中,如果没有逻辑操作,什么变化都没有,也没有返回值。
通过log可以看到,start之后service就一直运行着,即使activity已经销毁了,service一样运行在后台。
> 第一次调用 startService 调用 onCreate -> onStartCommand> 没有stop情况下,多次调用 startService 不再调用 onCreate 但是会多次调用 onStartCommand。
> 例如连续调用三次 startService
> onCreate -> onStartCommand
> onStartCommand
> onStartCommand
如果想要让service停止运行,需要调用stopService,或者在service处理完逻辑操作调用stopSelf函数才能让service停止运行
Intent intent = new Intent(MainActivity.this, LocalService.class); stopService(intent);在activity的 `onDestroy` 中调用 stopService,查看日志,发现service停止运行,并回调了 `onDestroy`
如果候面试官问了一个问题:service与activity之间怎么进行数据交互?有几种方式?
你:这个。。。service在项目中很少用,所以不是很清楚
(所以说还是你不了解,很少使用?这就是你不会的借口?)
这时候面试官提示你:bindService知道吗?
你心里窃喜:知道,知道,绑定service嘛,第二种启动srevice的方式。
面试官:那你讲讲bindService怎么使用?为什么要用bindService?使用bindService的同时startService的情况下如何停止service??
你:...请问电梯是出门左拐吗?我先回去了~~~
OK,接下来就详细讲讲service与其它组件之间的通信方式,以及bindSercice相关的知识,希望学完之后不会再出现上面所说的打脸场景
service 与Activity(其它组件)通信方式
- 通过Binder对象
其实这个方式思想还是很简单的,因为service这种带生命周期的组件,不适合通过 new 创建对象,所以通过Binder得到service的实例,然后就可以用这个实例访问service的方法和成员了
private LocalService localService; ServiceConnection connection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName componentName, IBinder iBinder) { LogUtil.outE(TAG, "onServiceConnected " + componentName); localService = ((LocalService.MyBinder) iBinder).getService(); } @Override public void onServiceDisconnected(ComponentName componentName) { LogUtil.outE(TAG, "onServiceDisconnected " + componentName); } };通过 ServiceConnection 得到 IBinder ,调用 getService() (自己写的方法)得到 service 实例。
Intent intent = new Intent(MainActivity.this, LocalService.class); bindService(intent, connection, BIND_AUTO_CREATE);bindService 也很简单,再看一下service中代码,修改 onBind
@Override public IBinder onBind(Intent intent) { return new MyBinder(); }MyBinder 继承 Binder ,添加一个获取service实例的方法
public class MyBinder extends Binder { public LocalService getService() { return LocalService.this; } }> bindService 除了调用方式跟 startService 有所区别。其他逻辑还是一样的,生命周期执行也是首次绑定时调用 onCreate-> onStartCommand
> 之后调用都只调用 onStartCommand
现在模拟一个后台下载文件的demo详细看看通过Binder通信的方式,先看看service实现
public class DownloadService extends Service { //最大值 public static final int MAX_PROGRESS = 100; //是否正在下载 private boolean isRunning = false; //进度 private int progress = 0; //获取进度 public int getProgress() { return progress; } /** * 模拟下载任务 */ public void startDownload() { if (!isRunning) { new Thread(new Runnable() { @Override public void run() { progress = 0; isRunning = true; while (isRunning) { if (progress < MAX_PROGRESS) { progress += 5; try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } else { isRunning = false; } } } }).start(); } } @Override public IBinder onBind(Intent intent) { return new MyBinder(); } public class MyBinder extends Binder { public DownloadService getService() { return DownloadService.this; } } }这代码再简单不过了,应该都能看懂,启动线程,每秒更新进度, `startDwonload` 和 `getProgress` 提供给外部调用,下载方法中做一个简单的判断,当前是否正在下载中,避免用户多次调用导致逻辑错误。
再看一下activity代码
public class MainActivity extends AppCompatActivity { private DownloadService mService; private ProgressBar mProgressBar; private int progress = 0; private boolean isRunning = false; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mProgressBar = (ProgressBar) findViewById(R.id.progress_bar); //绑定 Intent intent = new Intent(MainActivity.this, DownloadService.class); bindService(intent, connection, BIND_AUTO_CREATE); //下载 findViewById(R.id.btn_download).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { mService.startDownload(); listenProgress(); } }); } /** * 监听进度,每秒钟获取调用DownloadService的getProgress()方法来获取进度,更新UI */ public void listenProgress() { if (!isRunning) { new Thread(new Runnable() { @Override public void run() { progress = 0; isRunning = true; while (isRunning) { if (progress < DownloadService.MAX_PROGRESS) { progress = mService.getProgress();//调用service方法回去进度 mProgressBar.setProgress(progress); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } else { isRunning = false; } } } }).start(); } } ServiceConnection connection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName componentName, IBinder iBinder) { mService = ((DownloadService.MyBinder) iBinder).getService(); } @Override public void onServiceDisconnected(ComponentName componentName) { } }; @Override protected void onDestroy() { super.onDestroy(); unbindService(connection); } }activity的代码也不难,这里先不管代码风格和编程方式是否规范,为了让逻辑直观和简介,一切从简,只为更好理解。这里需要注意的一点是,更新UI是要在主线程中操作,ProgressBar 这个控件估计是内部做了处理,这里先不管了
OK,到这里,我们使用了bindService模拟了下载任务,实现了service和activity之间的数据交互。但是!!!但是不觉得这个业务使用这种主动获取数据的方式很麻烦吗?需要activity主动询问service数据。还有什么方式?
其实前辈们一直强调说基础要打扎实,只有基础扎实了才能将知识融会贯通,使用更优雅的方式解决问题。一般多线程之间的数据交互方式有哪些呢?回调?广播?...
- 回调函数
public interface OnProgressListener { void onProgress(int progress); }service业务逻辑中,进度变化时设置进度
public class DownloadService extends Service { //最大值 public static final int MAX_PROGRESS = 100; //是否正在下载 private boolean isRunning = false; //进度 private int progress = 0; //获取进度 public int getProgress() { return progress; } /** * 模拟下载任务 */ public void startDownload() { if (!isRunning) { new Thread(new Runnable() { @Override public void run() { progress = 0; isRunning = true; while (isRunning) { if (progress < MAX_PROGRESS) { progress += 5; //进度设置 if (mListener != null) { mListener.onProgress(progress); } try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } else { isRunning = false; } } } }).start(); } } @Override public IBinder onBind(Intent intent) { return new MyBinder(); } public class MyBinder extends Binder { public DownloadService getService() { return DownloadService.this; } } /** * 定义回调接口 */ public interface OnProgressListener { void onProgress(int progress); } //回调接口 private OnProgressListener mListener; /** * 设置监听 * * @param listener */ public void setOnProgressListener(OnProgressListener listener) { this.mListener = listener; } }
然后在activity中修改为监听数据,不用再主动获取
public class MainActivity extends AppCompatActivity { private DownloadService mService; private ProgressBar mProgressBar; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mProgressBar = (ProgressBar) findViewById(R.id.progress_bar); //绑定 Intent intent = new Intent(MainActivity.this, DownloadService.class); bindService(intent, connection, BIND_AUTO_CREATE); //下载 findViewById(R.id.btn_download).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { mService.startDownload(); } }); } ServiceConnection connection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName componentName, IBinder iBinder) { mService = ((DownloadService.MyBinder) iBinder).getService(); /** * 设置监听 */ mService.setOnProgressListener(new DownloadService.OnProgressListener() { @Override public void onProgress(int progress) { mProgressBar.setProgress(progress); } }); } @Override public void onServiceDisconnected(ComponentName componentName) { } }; @Override protected void onDestroy() { super.onDestroy(); unbindService(connection); } }舒服,不用定时去查询获取数据,只要设置一个监听,当service有数据更新时就会回调,activity只需要收到数据更新UI就可以了。等一下,什么是回调函数?这可能需要你去查查资料哦,如果到现在为止你还不知道什么是回调函数,,那你的路就还很长哦,赶紧去补!
可能很多小伙伴听到回调函数的时候就想到另外一种方式了:广播。也很相似的思路,service的数据发生改变的时候主动发送广播,在activity中设置广播监听,收到数据更新UI
- 广播
service代码
public class DownloadService extends Service { //最大值 public static final int MAX_PROGRESS = 100; //是否正在下载 private boolean isRunning = false; //进度 private int progress = 0; //广播intent private Intent intent = new Intent("com.ruffian.download.action"); /** * 模拟下载任务 */ public void startDownload() { if (!isRunning) { new Thread(new Runnable() { @Override public void run() { progress = 0; isRunning = true; while (isRunning) { if (progress < MAX_PROGRESS) { progress += 5; //发送广播 intent.putExtra("progress", progress); sendBroadcast(intent); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } else { isRunning = false; } } } }).start(); } } @Override public IBinder onBind(Intent intent) { return null; } @Override public int onStartCommand(Intent intent, int flags, int startId) { startDownload(); return super.onStartCommand(intent, flags, startId); } }activity中添加广播监听,别忘了注册和注销广播,说明一下:使用广播的情况不一定要 bindService 的方式,startService 也是可以的
public class MainActivity extends AppCompatActivity { private ProgressBar mProgressBar; private MyReceiver myReceiver; private Intent mIntent; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mProgressBar = (ProgressBar) findViewById(R.id.progress_bar); //start mIntent = new Intent(MainActivity.this, DownloadService.class); //注册广播 IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction("com.ruffian.download.action"); myReceiver = new MyReceiver(); registerReceiver(myReceiver, intentFilter); //下载 findViewById(R.id.btn_download).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { startService(mIntent); } }); } @Override protected void onDestroy() { super.onDestroy(); //解除绑定 stopService(mIntent); //注销广播 unregisterReceiver(myReceiver); } /** * 广播接收器 */ public class MyReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { //拿到进度,更新UI int progress = intent.getIntExtra("progress", 0); mProgressBar.setProgress(progress); } } }> service与activity(或者其它组件)的通信方式有以上3种,根据业务需要去选择最合适的一种,比如下载这种业务使用回调会是不错的选择;少量数据的获取也可使用bindservice得到service实例主动获取;对于多个activity都需要获取数据的业务使用广播则更方便一点。
service 两种启动方式关系说明
学会了 startService 和 bindService 。是不是又想急着去打游戏了呢?如果面试官再问你:如果使用 startService 之后再调用 bindService ,要如何停止服务呢?bind 之后再 start 的情况呢?卧槽,,,要不要问这么细?我已经做的很好了!还问! 巧了,真就有面试官会这么干,看你到底掌握了多少,深度多深~~
莫慌,给一句话: **只要调用了 start 就必须用 stop 才能停止服务。如果单一 bind 那么 unBind 就可以停止服务了**
service onStartCommand返回值详解
到现在为止,基本上把service的基础知识过了一遍,回过头来看看service的类,onCreate ,onDestory 没什么可讲的,但是这个 `onStartCommand` 好像还没仔细研究一下,是不是之前还见过 onStart 呢?卧槽,现在是不是又想问 onStart 跟 onStartCommand 的关系?
没错,我就是想问,你砍我呀。
这个比较简单,onStart 是 <= API5之前的回调,在 >API5 之后这个方法不直接回调了,而是放在 onStartCommand 中, 看一下源码
public int onStartCommand(Intent intent, int flags, int startId) { onStart(intent, startId); return mStartCompatibility ? START_STICKY_COMPATIBILITY : START_STICKY; }所以实现service时候直接重新 onStartCommand 就可以了。这里的intent 是启动组件传递过来的Intent。实际上 onStartCommand 的返回值 int 类型才是最最值得注意的,它有4种可选值
START_STICKY
START_NOT_STICKY
START_REDELIVER_INTENT
START_STICKY_COMPATIBILITY
具体说明一下:
- START_STICKY :当service因内存不足被系统kill掉后,一段时间后当内存再次空闲时,会重启service ,回调 onStartCommand 方法,START_STICKY状态下,startId 改变,重启时 intent==null
- START_NOT_STICKY : 当service因内存不足被系统kill掉后,一段时间后当内存再次空闲时,系统不再自动重启service
- START_REDELIVER_INTENT :当service因内存不足被系统kill掉后,一段时间后当内存再次空闲时,会重启service ,回调 onStartCommand 方法,START_REDELIVER_INTENT 状态下,startId 不变,重启时 intent == 后一次调用startService中的intent
- START_STICKY_COMPATIBILITY :当service因内存不足被系统kill掉后,一段时间后当内存再次空闲时,会重启service,但是只调用 oncreate 方法,没有调用 onStartCommand
由于每次启动服务(调用startService)时,onStartCommand方法都会被调用,因此我们可以通过该方法使用Intent给Service传递所需要的参数,然后在onStartCommand方法中处理的事件,最后根据需求选择不同的Flag返回值,以达到对程序更友好的控制。
service 与thread的关系
前面的例子中我们都看到了,在service中需要创建thread线程做业务逻辑,耗时操作。所以之前一直说用service来做耗时操作的说法可能是错误的喔~其实service运行在主线程(UI线程)
那么问题来了,说好的在service中做耗时操作呢?在主线程中运行,那么超过10s不是ANR了?
我觉得:这里需要把service当做一个对象来理解,他运行在主线程中,start之后就一直运行,但是所谓的耗时操作是需要创建Thread来实现,一个thread的逻辑处理完了,thread结束了,但是service还是运行着的,直到调用stop或者被系统kill掉。
所以开口就说用service做耗时操作,估计就是在网上看了一下面试答案就来了。应该是在service的工作线程中才能做耗时操作,如果你质疑我,自己去尝试,直接在 `onCreate()` 中休眠20s,必定ANR
@Override public void onCreate() { super.onCreate(); LogUtil.outE(TAG, "onCreate"); try { Thread.sleep(20000); } catch (InterruptedException e) { e.printStackTrace(); } }所以现在要搞明白了,service确实运行在主线程,所谓的耗时操作实在service的工作线程中进行的。
那么service 与thread 之间有什么关系呢? 这个可以给肯定答案: 没有半毛钱关系!
thread: 是程序执行的最小单元,它是分配CPU的基本单位,android UI线程也是thread的一种,thread常用来做异步耗时的操作。
service: android的一个组件,运行在主线程中,由系统托管,并不能用来执行耗时操作,常说的Service后台任务只不过是指没有UI的组件罢了
这么说service要做耗时任务都是要创建thread咯?不是的,体统提供了IntentService,他是service的一个实现类,就是为了解决在service中做耗时操作处理而诞生的,原理还是创建thread,只不过开发者不需要关心这部分逻辑,由于篇幅太长,这部分知识放到下一篇博客中将。
看客们可以持续关注,只要你坚持不懈,我就可以一步一步带你到饿死的地步~~~总是有一些什么小程序,快应用什么的想来抢饭碗,想让安卓开发者饿死,然而呢?不存在的,只要我们够吊~~~