View体系之四大组件—— Service详解
在学习Service之前,我们先提出以下几个问题:
1、什么是Service?Service的作用是什么?
2、Service有哪几种状态,其生命周期是什么?
3、Service如何使用?(同一进程/跨进程)
4、Service如何保活(进程保活)?
一、什么是Service?为什么Android会使用Service?
Service顾明思义就是服务,是Andorid提供的实现后台运行的解决方案,主要用于执行后台任务。
比如:通常我们在微信或者qq聊天时,电话进来,然后在打电话的过程中qq或者微信也会一直挂载,这就是Android的后台功能。
服务并不是运行在独立的进程中,而是会依赖创建服务时的应用程序进程,在这个应用程序进程被杀死后,依赖该进程的服务也会被杀死。
又比如:我们在一个应用中一边下载视频,一边继续浏览其他内容。
需要注意的是,服务(除IntentService外)并不会默认开启线程,服务的执行是在主线程中执行,因此如果在服务中执行耗时任务,也会导致主线程被阻塞,导致ANR,所以如果需要执行耗时任务,可以通过使用IntentService或者是开启线程来执行。
二、Service的状态和生命周期
1、Service的状态
根据Service是否与组件进行交互,可分为两种状态:启动状态和绑定状态。
使用范围:
通常用于那些不需要交互、无限期后台运行的任务。比如从服务器下载数据
如何使用:
调用startService()启动,服务即可在后台无限期的运行,即使启动Service的组件已经销毁,也不影响Service的运行。
需要停止Service时,可以通过组件调用stopService()方法或者在Service的子类中调用 stopself()方法即可。
使用范围:
通常使用与那些需要与后台Service进行交互的情形
如何使用:
在绑定状态时,组件调用bindService()启动该Service,同时实现Service与组件的绑定。而绑定的服务中提供了一个IBinder()接口,用于组件和服务进行交互,发送请求,获取结果。
而当所有的组件都与它解绑后,该Service才会被销毁。
2、 Service的生命周期分析,
具体如下图 1:
图 1
通过图片可以发现,启动状态和绑定状态的声明周期是不同的。
需要注意,在绑定状态时,同一个组件对该Service多次绑定,只需要调用一次unbindService()即可解绑。即多次绑定一次解绑
三、Service的具体使用
在AndroidManifest.xml中声明,如下:
android:enabled=[
"true"
| "
false
"]
//是否被系统实例化,默认为true
android:exported=[
"true"
| "
false
"]
//是否可以被隐式调用
android:icon=
"drawable resource"
android:isolatedProcess=[
"true"
| "
false
"]
android:label=
"string resource"
android:name=
"string" //Service类名
android:permission=
"string"
//权限
android:process=
"string"
> //是否在单独的进程中运行
. . .
这里其他值采用默认,声明如下:
2、继承Service类。具体如下:
package com.sky_wf.de
moutils.activity;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.support.annotation.Nullable;
/**
* @Date : 2018/2/24
* @Author : WF
* @Description :
*/
public class LocalService extends Service
{
@Nullable
@Override
public IBinder onBind(Intent intent)//所有继承Service都必须实现onBind()方法
{
return null;
}
@Override
public void onCreate()//Service创建时调用,只调用一次
{
super.onCreate();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId)
{
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy()//Service销毁时调用
{
super.onDestroy();
}
}
四大组件包括Application其实根本上来说,都继承了Context。因此我们经常在组件中这样写:
public class LoginActivity extends Activity
{
private Context context;
@override
public void onCreate()
{
context = this;
}
}
其实在这里,this代表的就是当前LoginActivty的一个实例,而Context则是Activity的父类,context = this。这本身就是一种向上转型。
这里也就可以理解,为什么很多方法或者组件实例化时参数需要Context,而我们传入一个this就可以。又或者我们自己实例化一个Context传入。
Service本身就是一个抽象类,继承ContextWrapper,而ContextWrapper则是继承与Context,
在Service这个抽象类中有唯一的一个抽象方法onBind()。
onBind()方法是Service中唯一的一个抽象方法,因此继承Service类时必须实现这个类。通常用于进行通信,尤其是后面我们会讲到的跨进程通信。
在首次创建Service时调用,当重复执行startService()或者bindService()时,onCreate()不再执行。
当组件调用startService()启动服务时,则系统会调用此方法,如果是第一次调用startService()则会先调用onCreate()再调用onStartCommand(),重复启动服务,只会调用 onStartCommand()。
通常我们在onStartCommand中执行一些后台处理。
这里我们需要特别注意的是其返回的int值,目前有三种START_STICKY,START_NOT_STICKY,START_REDELIVER_INTENT。
START_STICKY
当Service因内存不足而被系统kill掉后,一段时间后内存再次空闲时,系统将会尝试重新创建此Service,一旦创建成功后将回调onStartCommand方法,但其中的Intent将是null,除非有挂起的 Intent,如pendingintent。
适用于不执行命令、但无限期运行并等待作业的媒体播放器或类似服务。
START_NOT_STICKY
当Service因内存不足而被系统kill后,即使系统内存再次空闲时,系统也不会尝试重新创建此Service。除非程序中再次调用startService启动此Service,这是最安全的选项,可以避免在不必要时 以及应用能够轻松重启所有未完成的作业时运行服务。
START_REDELIVER_INTENT
当Service因内存不足而被系统kill后,则会重建服务,之后调用onStartCommand()。与START_STICKY不同的是,其中的传递的Intent将是非空,是最后一次调用startService中的intent。
适用于主动执行应该立即恢复的作业(例如下载文件)的服务。
3、布局文件和Activity
(1)布局文件
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
android:id="@+id/btn_start"
android:layout_width="100dp"
android:layout_height="100dp"
android:text="启动服务"
/>
android:id="@+id/btn_stop"
android:layout_width="100dp"
android:layout_height="100dp"
android:text="停止服务"
/>
(2)Activity
package com.sky_wf.demoutils.activity;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
import com.sky_wf.demoutils.R;
/**
* @Date : 2018/2/7
* @Author : WF
* @Description :
*/
public class TestActivity extends AppCompatActivity implements View.OnClickListener
{
private Button btn_startService, btn_stopService;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.layout_test);
btn_startService = (Button) findViewById(R.id.btn_start);
btn_stopService = (Button) findViewById(R.id.btn_stop);
btn_startService.setOnClickListener(this);
btn_stopService.setOnClickListener(this);
}
@Override
public void onClick(View v)
{
Intent intent = new Intent(this, LocalService.class);
switch (v.getId())
{
case R.id.btn_start:
startService(intent);
break;
case R.id.btn_stop:
stopService(intent);
break;
}
}
}
(3)在Manifest中声明对应Activity和Service
(4)运行查看log
分析log:
点击绑定服务Button,首次执行startService()对调用Service的onCreate()和onStartCommand()方法
之后多次点击Button,此时只执行onStartCommand()方法
在点击stopService()方法后,则执行Service的onDestory()方法,该Service停止并销毁。
四、绑定服务与组件通信的问题
前面的实例介绍了启动Service的简单实例,这我们就要详细将一下Service的绑定状态通信问题。
相比启动Service,绑定 Service通常会涉及到Service与组件的一个交互问题,也就是说,组件传递数据到Service,然后Service也可能会回应数据到组件。话是这么说,但是并不是这么简单。
比如说,这里需要考虑,service与组件是在同一进程中还是跨进程,如果是同一进程中,我们可以通过Intent直接显式启动,同时可以获取到Service的实例对象,然后访问对应的public方法就好。但是如果是跨进程的,那么我们无法通过回调Service的实例来调用public方法。此时就需要我们使用IPC通信。
1、扩展Binder类
对于服务和客户端都是在同一进程下运行的情形(思考为什么?之后会回答),通常会扩展Binder类。
使用步骤:
(1)首先继承Ibinder接口,定义一个方法getService()返回Service的对象
(2)在Serivice中的onBind()方法中返回扩展的Ibinder类的对象
(3)客户端拿到这个binder,然后调用getService获得Service对象,并访问serviec对象的方法
这里需要明白Binder和IBinder的关系,如下:
p ublic interface IBinder {
/**
* The first transaction code available for user commands.
*/
int FIRST_CALL_TRANSACTION = 0x00000001 ;
/**
* The last transaction code available for user commands.
*/
int LAST_CALL_TRANSACTION = 0x00ffffff ;
.....
}
public class Binder implements IBinder {
/*
* Set this flag to true to detect anonymous, local or member classes
* that extend this Binder class and that are not static. These kind
* of classes can potentially create leaks.
*/
.......
}
也就是说IBinder是一个接口,而Binder则是实现了IBinder接口的实现类,而public IBinder onBinde()方法返回的的是IBinder接口,因此这里实际上是对扩展的Binder类进行了一次向上转型。
(1)定义Service类
package com.sky_wf.demoutils.activity;
import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.support.annotation.Nullable;
import android.util.Log;
/**
* @Date : 2018/2/26
* @Author : WF
* @Description :扩展Binder类实现绑定Service
*/
public class LocalBinderService extends Service
{
private int count = 0;
private LocalBinder binder = new LocalBinder();
private boolean stop = false;
private Thread mthread;
public class LocalBinder extends Binder
{
LocalBinderService getService()
{
return LocalBinderService.this;//获取当前LocalBinderService类的对象
}
}
/**
*
*返回Binder对象,便于客户端获取
*/
@Nullable
@Override
public IBinder onBind(Intent intent)
{
Log.d("wf", "LocalService onBind");
return binder;
}
@Override
public void onCreate()
{
super.onCreate();
Log.d("wf", "LocalService onCreate");
mthread = new Thread(new Runnable() {
@Override
public void run() {
while(!stop)
{
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count++;
}
}
});
mthread.start();
}
@Override
public void onDestroy()
{
super.onDestroy();
stop = true;
Log.d("wf", "LocalService onDestroy");
}
@Override
public boolean onUnbind(Intent intent)
{
Log.d("wf", "LocalService onUnbind");
return super.onUnbind(intent);
}
public int getCount()
{
return count;
}
}
(2)主Activity和布局文件(布局文件只有三个Button这里省略)
package com.sky_wf.demoutils.activity;
import android.app.Service;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import com.sky_wf.demoutils.R;
/**
* @Date : 2018/2/26
* @Author : WF
* @Description :
*/
public class TestBinderActivity extends AppCompatActivity implements View.OnClickListener
{
private Button btn_startService, btn_stopService, btn_getServiceDatas;
private LocalBinderService mservice;
private ServiceConnection conn;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.layout_test);
btn_startService = (Button) findViewById(R.id.btn_start);
btn_stopService = (Button) findViewById(R.id.btn_stop);
btn_getServiceDatas = (Button)findViewById(R.id.btn_getServiceData);
btn_startService.setOnClickListener(this);
btn_stopService.setOnClickListener(this);
btn_getServiceDatas.setOnClickListener(this);
conn = new ServiceConnection() {
/**
* 在与Service服务绑定后回调(也就是在Service执行onCreate()、onBind()后回调),通过此方法获取Service传递的IBinder对象,从而通过这个IBinder对象,实现客户端与服务端的交互
*这里通过Binder对象得到了当前的Service对象,从而可以调用Service中的公共方法
*/
@Override
public void onServiceConnected(ComponentName name, IBinder service)
{
Log.d("wf", "TestBinderActivity onServiceConnected");
LocalBinderService.LocalBinder binder = (LocalBinderService.LocalBinder)service;
mservice = binder.getService();
}
/**
* 取消绑定时会被回调,但是正常情况下不会被回调,只有在Service意外被销毁时才会回调
* 例如内存不足等
*/
@Override
public void onServiceDisconnected(ComponentName name)
{
Log.d("wf", "TestBinderActivity onServiceDisconnected");
mservice = null;
}
};
}
@Override
public void onClick(View v)
{
Intent intent = new Intent(this, LocalBinderService.class);
switch (v.getId())
{
case R.id.btn_start:
bindService(intent,conn, Service.BIND_AUTO_CREATE);
break;
case R.id.btn_stop:
if(mservice!=null)
{
mservice = null;
unbindService(conn);
}
break;
case R.id.btn_getServiceData:
if(mservice!=null)
{
Log.d("wf", "TestBinderActivity getData="+mservice.getCount());
}else
{
Log.d("wf", "请绑定服务器以获得数据");
}
break;
}
}
}
从上图分析:
1>-调用顺序
点击绑定后,调用bindService(),则开始调用onCreate()-->onBind()-->ServiceConn中的
onServiceConnected(),同一组件重复点击绑定,则onCreate()和onBind()不再调用,
点击解除绑定,调用onUnbind() onDestory()(如果还有其他组件绑定,则不会调用Service的onDestory())
2>-
bindService(Intent intent,ServerConnction conn,int flags)
flags 代表组件与Service绑定时是否自动创建Service实例,0,表示不自动创建,BIND_AUTO_CREATE则代表自动创建。
之前提过一个疑问, 为什么只能在一个进程中 ?
因为扩展Binder类,是通过Binder对象去获取Service实例,从而去获取Service的公共方法,而公共方法的使用也需要在统一进程中,而没有办法在不同进程中通过获取实例的方法来使用它的公共方法。 这也就理解了为什么需要使用跨进程通信。
2、使用Messenger
之前介绍的扩展Binder类的方法只是针对同一进程中的客户端和服务端。但是对于跨进程之间的通信,则可以采用
Messenger
。
为什么
Messenger
可以实现跨进程的通信呢?这个目前我还不是很清楚,之后再深入研究一下。这里先讲怎么用。
Messenger是一种轻量级IPC方案,底层根本上是AIDL,实际上就是对AIDL进行了一个封装,而Messenger核心是利用Message来进行数据的传输,而Message可以携带的参数有what,arg1,arg2,obj,replyTo等
。具体流程如下:
分析流程图:
客户端和服务端进行通信的前提是建立连接,也就是绑定Service。
对于服务端,需要接收消息,而这里的消息是Message,那么我们就需要一个Handler来处理Message。而这里传递Message需要利用Messenger。因此在服务端利用Handler对象来创建服务端的一个Messenger。然后通过onBind()接口返回这个Messenger的IBinder对象。
接下来分析客户端,客户端需要发送消息,因此需要组装对应Message信息,并通过Messenger来发送,而Messenger则是通过服务端返回的Binder来创建。
这样就简单的建立一个从客户端到服务器的简单连接。这里需要明白,客户端有一个Messenger对象,而服务器也有一个Messenger对象,这两者之间又有什么关系,为什么通过Messenger可以建立跨进程的连接。
这里首先分析客户端,客户端接收消息,同样需要创建一个Handler,并根据这个Handler创建一个用于跨进程通信的Messenger对象。同时,修改上面客户端发送消息的代码,把该Messenger对象作为Message的replyTo参数,传递给服务端。
服务端接收到客户端创建的Messenger对象后,利用该对象传递Message给客户端。
总结来说,就是服务端根据自己的Handler创建Messenger,并返回Messenger底层Binder给客户端,而客户端拿到Binder后,再对应生成Messenger对象,发送消息。而在客户端发送消息时,会将自己创建的另一个Messenger作为replyTo参数,然后发送到服务端,这样两者之间就可以相互通信了。
以上就是利用Messenger的大致流程,接下来上代码:
1>服务端代码
package com.sky_wf.demoutils.activity;
import android.app.Service;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
import android.support.annotation.Nullable;
import android.util.Log;
/**
* @Date : 2018/2/26
* @Author : WF
* @Description :使用Messenger实现Service的跨进程通信
*/
public class LocalMessengerService extends Service
{
public final static int MSG_FROM_CLIENT = 1;
private final Messenger messenger = new Messenger(new MyHandler());
class MyHandler extends Handler
{
@Override
public void handleMessage(Message msg)
{
switch (msg.what)
{
case MSG_FROM_CLIENT:
Log.d("wf", "Service receiver Message from Client");
Messenger messenger = msg.replyTo;
Message message = Message.obtain(null,TestMessgenerActivity.MSG_FROM_SERVER);
Bundle bundle = new Bundle();
bundle.putString("wf","Service send msg to Client");
message.setData(bundle);
try {
messenger.send(message);
} catch (RemoteException e) {
e.printStackTrace();
}
break;
}
}
}
@Nullable
@Override
public IBinder onBind(Intent intent)
{
return messenger.getBinder();// 注意这里获取的Binder和Activity中ServicerConnection中的是同一个对象, 是跨进程通信的关键
}
@Override
public void onCreate()
{
super.onCreate();
Log.d("wf", "LocalService onCreate");
}
@Override
public boolean onUnbind(Intent intent)
{
Log.d("wf", "LocalService onUnbind");
return super.onUnbind(intent);
}
@Override
public void onDestroy()
{
Log.d("wf", "LocalService onDestroy");
super.onDestroy();
}
}
2>客户端代码
package com.sky_wf.demoutils.activity;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import com.sky_wf.demoutils.R;
/**
* @Date : 2018/3/1
* @Author : WF
* @Description :Messenger对应的客户端
*/
public class TestMessgenerActivity extends AppCompatActivity implements View.OnClickListener
{
private Button btn_startService, btn_stopService, btn_getServiceDatas;
private Messenger messenger;
private ServiceConnection conn;
private Messenger serverMessenger = new Messenger(new MyClientHandler());
public final static int MSG_FROM_SERVER = 2;
class MyClientHandler extends Handler
{
@Override
public void handleMessage(Message msg)
{
switch (msg.what)
{
case MSG_FROM_SERVER:
Log.d("wf", "the client receiver the reply from server");
break;
default:
super.handleMessage(msg);
}
}
};
@Override
protected void onCreate(@Nullable Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.layout_test);
btn_startService = (Button) findViewById(R.id.btn_start);
btn_stopService = (Button) findViewById(R.id.btn_stop);
btn_getServiceDatas = (Button) findViewById(R.id.btn_getServiceData);
btn_startService.setOnClickListener(this);
btn_stopService.setOnClickListener(this);
btn_getServiceDatas.setOnClickListener(this);
conn = new ServiceConnection()
{
@Override
public void onServiceConnected(ComponentName name, IBinder service)
{
Log.d("wf", "onServiceConnected");
messenger = new Messenger(service);
}
@Override
public void onServiceDisconnected(ComponentName name)
{
Log.d("wf", "onServiceDisconnected");
messenger = null;
}
};
}
public void sayHello()
{
Message msg = Message.obtain(null, LocalMessengerService.MSG_FROM_CLIENT, 0, 0);
msg.replyTo = serverMessenger;
try
{
messenger.send(msg);
} catch (RemoteException e)
{
e.printStackTrace();
}
}
@Override
public void onClick(View v)
{
Intent intent = new Intent(this, LocalMessengerService.class);
switch (v.getId())
{
case R.id.btn_start:
bindService(intent, conn, BIND_AUTO_CREATE);
break;
case R.id.btn_stop:
unbindService(conn);
break;
case R.id.btn_getServiceData:
sayHello();
break;
}
}
}
3>清单文件
android:process=":remote"/>//用于开启一个新的进程
注意:
(1)通常情况下我们会将service与客户端的生命周期相结合,这里以Activity的生命周期为例:
在Activity可见时执行Service,不可见时解绑,则需要在onStart()执行绑定操作,而在onStop()时执行解绑操作。
在Activity处于后台不可见情况下,仍然可以执行Service,则应在onCreate()情况下执行绑定,而在onDestory()情况下执行解绑。需要注意,这种情形下,Service在另一个进程中独立运行,因此需要提高进程权重,避免被系统杀死。
但是请勿在onResume()和onPause()中执行绑定和解绑,这样会因生命周期的转变而频繁的发生绑定和解绑,是不合理的。
(2)既然是客户端和服务端的连接,那么不可避免的就会有连接异常问题,因此我们应该对连接异常执行捕获,这里的连接异常是
DeadObjectException,通常表示调用的对象已经被销毁,也就是Service对象被销毁。
3、Service的启动和绑定转换
前面已经介绍Service有启动Service和绑定Service两种,但是同一个Service对象也可以有两种状态,也就是说,这个Service可以在启动和绑定状态中切换。
一个Service对象,首先是绑定状态,然后以启动状态去运行,则该Service会由绑定状态转变为启动状态,并且即使绑定的宿主销毁,该Service依旧正常运行,除非调用stopService()或者本身调用stopSelf()或者内存不足时才会停止。注意,在停止前需要调用解绑。
一个Service对象,先是启动状态,然后以绑定状态去执行,则仍然是启动状态。但是同样,需要调用解绑后才能利用stopService()停止服务。
从上面分析可以知道,启动服务的优先级要明显高于绑定服务。此外,如果服务没有在单独的进程中运行,那么它就是在托管进程的主线程中运行,也就是常说的UI线程,因此执行耗时任务的话也需要开启独立的线程。
4、Service在不同场景下的生命周期分析
(1)startService()-->按返回退出当前Activity
(2)bindService()-->按返回退出当前Activity
(3)startService()-->bindService-->stopService
(4)、
startService()-->bindService-->unbindService
(5)、 startService()-->bindService-->unbindService-->stopService
(6)、bindService-->startService-->unbindService
(7)、 bindService-->startService-->unbindService-->stopService
(8) bindService-->startService-->stopService
(9)、 startService()-->bindService-->stopService
(10)bingService()-->startService-->按返回退出当前Activity
通过上面的例子分析:
(1)无论是先启动后绑定还是先绑定后启动,最后停止或者销毁Service,都是调用stopService()方法,但是都有前提,就是先调用unBindService()做解绑操作,否则直接调用stopService并不能停止Service
(2)对于绑定服务,如果手动解绑,则执行onUnbind-->onDestory
如果不手动解绑,直接退出当前Activity,则执行onUnbind-->onDestory
如果是先启动后绑定或者先绑定后启动,且不执行手动解绑,则只执行onUnbind
(3)对于启动服务,如果不手动执行stopService或者stopSelf,则按返回退出当前Activity,则Service仍在,不销毁
如果先启动后绑定或者先绑定后启动,则只有执行stopService才会执行销毁,前提是已解绑
5、Service与Thread的关系
Thread是程序执行的最小单位,也是分配cpu的最小单位,通常用于执行一些耗时任务
Service则是android的一种组件,一般运行在主线程下,因此无法执行耗时任务。
通常是Service结合Thread+notification来使用。
6、针对Android5.0以上隐式启动Service报错 IllegaArgumentException
在Android5.0以上,针对隐式启动Service做了修改,如下:
会判断Component和package是否为空,如果为空,且是在API版本大于5.0,则会报异常。
而启动Service又分为显示启动和隐式启动,在统一应用中可以显示启动,但是在不同的应用中则只能隐式启动。
具体解决办法:
(1)添加包名
由于隐式启动会检查包名和Component以及API,因此可以在隐式启动时添加包名。
final Intent serviceIntent= new Intent();
serviceIntent.setAction( "com.android.ForegroundService" );
serviceIntent.setPackage(getPackageName()); //设置应用的包名
startService(serviceIntent);
(2)将隐式启动转换为显示启动
Intent mIntent=new Intent(); //辅助
Intent mIntent.setAction("com.android.ForegroundService");
final Intent serviceIntent=new Intent(getExplicitIntent(this,mIntent));
startService(serviceIntent);
四、IntentService
IntentService是继承自Service用于处理异步请求的一个类,在IntentService中你会有一个工作线程来处理耗时操作,并且在任务完成后自动停止。而当多次启动IntentService时,这些耗时任务会以工作队列的形式依次在onHandleIntent中执行。
public class MyIntentService extends IntentService
{
private boolean isRunning = true;
private int count = 0; private
LocalBroadcastManager mLocalBroadcastManager;
public MyIntentService() { super("MyIntentService");
}
@Override public void onCreate() {
super.onCreate(); mLocalBroadcastManager = LocalBroadcastManager.getInstance(this); sendServiceStatus("服务启动");
}
@Override protected void onHandleIntent(Intent intent)
{
try { sendThreadStatus("线程启动", count);
Thread.sleep(1_000); sendServiceStatus("服务运行中...");
isRunning = true; count = 0; while (isRunning)
{ count++; if (count >= 100) { isRunning = false; }
Thread.sleep(50); sendThreadStatus("线程运行中...", count); }
sendThreadStatus("线程结束", count); }
catch (InterruptedException e) { e.printStackTrace(); }
}
@Override public void onDestroy() {
super.onDestroy(); sendServiceStatus("服务结束");
} // 发送服务状态信息
private void sendServiceStatus(String status) {
Intent intent = new Intent(IntentServiceFragment.ACTION_TYPE_SERVICE); intent.putExtra("status", status);
mLocalBroadcastManager.sendBroadcast(intent); } // 发送线程状态信息
private void sendThreadStatus(String status, int progress)
{ Intent intent = new Intent(IntentServiceFragment.ACTION_TYPE_THREAD);
intent.putExtra("status", status); intent.putExtra("progress", progress);
mLocalBroadcastManager.sendBroadcast(intent);
}
}
1、为什么可以在onHandleIntent()方法中执行耗时任务
在IntentSerivice中的有一个继承自Thread的HandlerThread,而这个方法中的任务执行则是在该线程中执行的。
@Override public void onCreate() {
super.onCreate(); // HandlerThread 继承自 Thread,内部封装了 Looper,在这里新建线程并启动,所以启动 IntentService 不需要新建线程。
HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
thread.start(); // 获得工作线程的 Looper,并维护自己的工作队列。
mServiceLooper = thread.getLooper(); // mServiceHandler 是属于工作线程的。
mServiceHandler = new ServiceHandler(mServiceLooper);
}
2、IntentService启动是通过startService()还是bindService()
通过startService()启动,原因是在源码中的onBind()方法默认返回null
3、为什么多次启动IntentService会顺序执行,而停止服务后,后续的任务不再执行?
实际上,IntentService是通过Handler,Looper,MessageQueue把消息发送到线程中去执行的,而多次启动,只是把消息加入消息队列去执行。而服务停止后,会清空消息队列的信息,后续事件得不到执行。
五、应用进程保活问题
进程保活其实就是保证应用处于后台并且不能永远被杀死或者被杀死后又重启。
通常被杀死的情形分为两类:
第一类,是系统资源紧张或者由于系统自身的运行规则(比如锁屏清理)来杀死应用获得更多资源;
第二类,用户手动调用安全软件清理应用(比如360,华为自带的手机管家,腾讯管家)。
在Android5.0以前,我们基于这两种情形来做保活,而在Android5.0以后,由于系统的进程管理更为严格,杀进程也更加暴力彻底,因此只能基于第一种情况考虑保活。
目前Android中的进程分为前台进程,可见进程,服务进程,后台进程,空进程,重要性或者优先级从上到下依次递减
该状态下的进程正在与用户进行交互,因此必须存在。(除非系统错误或者用户手动杀掉,否则前台进程一定不会被杀死)
当前应用持有一个与用户交互的Activity,并且处于Resume状态
当前应用持有一个Service,并且处于以下情形:该Service与用户正在交互的Activity绑定(如果持有Service的进程和该Activity的进程是同一进程,那自然是前台进程,如果是其他进程的Service, 那么也是持有Service的进程也是前台进程
)
该Service调用startForeground()从而使进程位于前台
该Service正在执行它的某个声明周期的回调,比如onCreate,onDestory
当该应用有一个BroadCastReceiver并且该广播正在执行onReceiver()方法
可见进程情形中不会出前台进程中的任何情形,常见如下:
某进程持有一个Activity但是当前并非位于前台但仍然可见,也就是处于onPause还没到onStop状态。(比如由一个应用跳转到另一个应用时,此时则前一个应用会由前台进程转化为可见进程再到 后台进程)
某进程拥有一个Service,该Service与一个前台Activity绑定(需要区别前台进程中一个进程拥有Service,而Service与用户正在交互的Activity绑定,两者的区别在于,一个是与用户交互的Activity,一个是与前台应用的Activity绑定
)
服务进程一般是不属于上面两种,并且使用startService启动了该进程中的一个Service,那就是一个Service
这里指的是一个进程持有一个不可见的Activity,Activity处于onStop但是未处于onDestory
如果系统需要杀死进程,一般先空进程,然后后台进程,然后服务进程,然后可见进程,然后前台进程。
Android中根据系统资源杀死进程的规则成为Lower Memory Killer。
3、后台进程常驻策略与选择
前面我们已经提到过,对于用户的手动杀死进程我们没有很好的办法,但是对于系统这种杀死进程的行为,我们可以尝试去解决。而通常是因为我们进程的优先级太低同时系统可用资源太少然后被杀死。而我们解决这种问题的,通常会有两方面:
提升进程优先级,降低被杀死的概率
在进程被杀死后,进行拉活
前面我们已经说明了五种进程,其中各进程的的重要程度如下:
可以看到,前台进程为0,可见进程为1,服务进程为5,而OOM_ADJ,是LowMemoryKiller回收内存时对进程判断的依据,值越大越容易被回收。
而Android进程被杀死的情形如下:
5、
提升优先级——利用Activity提升权限
设计思想:
监控手机锁屏时间,在锁屏后启动一个像素的Activity,在用户解锁后销毁该Activity,用户无法感知该Activity
使用常场景:
可以解决第三方应用或者系统管理工具检测到锁屏事件后一段时间杀死后台进程,从而达到省电目的。
实现方式:
写一个宽高为1像素的Activity,并设置为透明,然后注册一个接收系统发送 锁屏和解锁的广播,从而在锁屏是启动Activity,解锁时销毁Activity
6、提升优先级——利用Notification权限
设计思想:
Android中Service的优先级为4,通过setForeground可以将后台的Service设置为前台Service,进程的优先级从4提升为2。
调用setForeground将后台Service设置为前台Service,但是需要在系统通知栏上发一条通知,此时可以再开一个Service,也发送一个相同ID的通知,然后取消掉,从而关闭通知。
7、进程拉活——利用系统广播
设计思想:在系统发生特定广播后,进程响应广播,拉活。
问题:只有在发生系统时间并发送广播时才可以拉活,并且如果广播无法正常接收的话也无法办到
8、进程拉活——利用第三方广播拉活
问题:首先需要知道第三方应用广播,其次不稳定,如果第三方应用广播在后续版本中修改了。
9、进程拉活——利用系统Service机制拉活
设计思想:将Service设置为START_STICKY,在系统机制让Service挂掉后自动拉活
适用范围:
Service在第一次异常杀死后5s复活,第二次10s,第三次20s。5次之后就不会重启
如果进程被root管理工具或者系统工具停止后,无法重启
10、利用该Native进程拉活
设计思想:利用linux的fork机制创建native进程,在native进程中监控主进程的存活,在主进程挂掉后,则在native进程中立即对主进程拉活
11、利用JobScheduler机制拉活
设计思想:在Android5.0之后对native进程加强了管理,native拉活的方式失效,但是Android5.0以上版本提供了JobScheduler接口。
使用范围:主要使用于Android5.0以上的手机,并且不收force-stop影响
,被强制停止的应用也可以拉活。但是小米手机仍然存在无法拉活的问题
12、利用账号同步机制拉活
设计思想: Android系统的账号同步机制会定期同步账号,可以利用账号同步机制进行拉活。
使用范围:使用与所有的Android版本,但是最新的AndroidN无法拉活
13、 其他
利用应用内的push通道,比如国外的GCM,国内的小米推送,华为推送
或者利用辅助功能,将应用加入厂商或者管理软件的白名单