什么是Service
有些用时比较长的操作希望在后台运行,不耽误当前操作
常见操作:访问网络、文件IO操作、大数据的数据库任务,播放音乐等
Service在后台运行,不与用户进行交互,在默认情况下Service运行在应用程序进程的主线程中,
如果需要在Service中处理一些网络连接等耗时的操作,那么应该讲这些任务放在单独线程中处理,避免阻塞用户界面
Questions:
1、启动服务后,按Home键,服务是否还在运行 —— YES
2、启动服务后,退出进程,是否服务还会运行 —— NO
3、启动服务,不用多线程,界面是否会阻塞 —— YES
4、服务是个新的进程么,服务是个新的线程么 —— 服务不是一个新的进程,也不是一个新的线程,是与当前进程绑定的
Service的分类
(一)按照启动方式分类
1、Started Service
(1)startService()启动
(2)一旦启动,就运行在后台,即便启动它的对象Activity都销毁了
(3)通常只启动,不返回值
(4)通常网络上传或者下载,操作完成后,自动停止
onStartCommand()
2、Bound Service
(1)bindService()来绑定
(2)提供客户端服务器接口来启动
(3)发送请求,得到返回值,甚至通过IPC来通讯
(4)一个服务可以被多个调用者绑定,只要有一个绑定,服务运行,所有绑定者都退出,服务退出
onnBind()
(二)按服务性质分
Local Service和Remote Service(有可能是进程与进程之间)
(三)按实现方法分
Java Service和Native Service
简单在Manifest中配置一下即可
<manifest ... >
...
<application ... >
<service android:name=".ExampleService" />
...
</application>
</manifest>
生命周期
public class ExampleService extends Service {
int mStartMode; // indicates how to behave if the service is killed
IBinder mBinder; // interface for clients that bind
boolean mAllowRebind; // indicates whether onRebind should be used
@Override
public void onCreate() {
// The service is being created
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
// The service is starting, due to a call to startService()
return mStartMode;
}
@Override
public IBinder onBind(Intent intent) {
// A client is binding to the service with bindService()
return mBinder;
}
@Override
public boolean onUnbind(Intent intent) {
// All clients have unbound with unbindService()
return mAllowRebind;
}
@Override
public void onRebind(Intent intent) {
// A client is binding to the service with bindService(),
// after onUnbind() has already been called
}
@Override
public void onDestroy() {
// The service is no longer used and is being destroyed
}
}
创建及使用Service步骤
1、继承Service类实现自己的服务
2、在AndroidManifest中注册服务
3、启动服务startService(),
onStartCommand()返回值:START_STICKY、START_NOT_STICKY、START_REDELIVER_INTENT
·START_STICKY:如果被系统杀死了,可能会尝试重启服务,但不传递Intent
·START_NOT_STICKY:被杀死了不尝试重启
·START_NOT_STICKY:与STICKY一样会尝试重启,会传递Intent
4、停止服务stopService(),传入启动Service时的Intent,
stopSelf()也能够停止服务
IntentService:继承自Service,专门处理异步请求
通过startService(Intent)启动,通过onHandleIntnet(Intent)实现(不是通过onStartCommand())
看一下IntentService的源码
我们看到定义了一个HandlerThread,正因为有了这个HandlerThread,是启动了多线程,所以能够处理异步请求
说明:异步处理服务,新开了一个线程:HandlerThread
在线程中发消息,然后接收
处理完成后,会清除县城,并且关掉服务
BoundService
一、Local BoundService
RPC: Remote Procedure Call
Binder的介绍
kernel层 字符设备驱动 Binder 负责进程间通讯(还有其他通讯方法如管道piper、socket等)
Lib层 Binder框架 配合ServiceManager
framework层 Binder类和IBinder接口
进程之间想互相访问之间的内存地址不能直接访问
所以需要像服务器与客户端之间一样,先由客户端发起一个请求,
服务器端接受请求,找内存地址,进行处理,并返回处理结果给客户端
如何实现呢?
首先要在相当于服务器端的进程中定义并实现一个IBinder接口,暴露给客户端
相当于客户端的进程发起请求,服务器端将内存地址映射到客户端
成为stub(桩程序),也可以理解为句柄handle
服务器端是有指针能够指向内存地址的,而这个stub是一个指向指针的指针
有了这个映射后就可以读取内存了
要明白的是这个调用不是真正的调用
而是先发起的请求,然后服务器端进程调用,然后返回的值
这样的进程与进程之间通信的实现是通过Binder调用的
绑定及使用Service的步骤
1、继承Service或者IntentService类实现自己的服务
2、在AndroidManifest中注册服务
3、绑定服务bindService()
4、取消绑定服务unbindService()
public class LocalService extends Service {
// Binder given to clients
private final IBinder mBinder = new LocalBinder();
// Random number generator
private final Random mGenerator = new Random();
/**
* Class used for the client Binder. Because we know this service always
* runs in the same process as its clients, we don't need to deal with IPC.
*/
public class LocalBinder extends Binder {
LocalService getService() {
// Return this instance of LocalService so clients can call public methods
return LocalService.this; // 返回这个服务的一个对象
}
}
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
/** method for clients */
public int getRandomNumber() {
return mGenerator.nextInt(100);
}
}
onBind方法返回的是一个IBinder接口的实现,要么实现IBinder接口,要么继承Binder类
而LocalBinder是一个继承自Binder的内部类
onBind方法其实就是将Binder类暴露给客户端
调用时要使用bindService方法
public class BindingActivity extends Activity {
LocalService mService;
boolean mBound = false;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
@Override
protected void onStart() {
super.onStart();
// Bind to LocalService
Intent intent = new Intent(this, LocalService.class);
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
}
@Override
protected void onStop() {
super.onStop();
// Unbind from the service
if (mBound) {
unbindService(mConnection);
mBound = false;
}
}
/** Called when a button is clicked (the button in the layout file attaches to
* this method with the android:onClick attribute) */
public void onButtonClick(View v) {
if (mBound) {
// Call a method from the LocalService.
// However, if this call were something that might hang, then this request should
// occur in a separate thread to avoid slowing down the activity performance.
int num = mService.getRandomNumber();
Toast.makeText(this, "number: " + num, Toast.LENGTH_SHORT).show();
}
}
/** Defines callbacks for service binding, passed to bindService() */
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName className,
IBinder service) {
// We've bound to LocalService, cast the IBinder and get LocalService instance
LocalBinder binder = (LocalBinder) service; // 就是之前onBind返回的那个实现了IBinder接口的类的实例
mService = binder.getService(); // 拿到了服务的对象,就可以调用服务的方法了
mBound = true;
}
@Override
public void onServiceDisconnected(ComponentName arg0) {
mBound = false;
}
};
}
binderService有三个参数
Intent : 传值
ServiceConnection:Service连接对象,类似于数据库连接对象,有两个方法onServiceConnected和onServiceDisconnected
int flag:Operation options for the binding
说明:startService方法是通过Activity,new了一个Service
而bindService是Service实例化的自己
所以bindService只是发起一个请求,而Service收到后在考虑要不要实例化
bindService这一步操作是异步的
问题:
1、Service的onBind方法和ServiceConnecton的onServiceConnected方法谁先执行?
回答:onBind方法先于onServiceConnected方法执行
2、Service的onUnBind方法和ServiceConnecton的onServiceDisconnected方法谁先执行?
回答:
3、理解清楚Binder的作用么?
说明:ServiceManager是非常重要的
二、Messenger
三、AIDL
Messenger和AIDL都属于远程Service,将在高级部分中讲解
StartedService & BoundService的区别
1、StartedService和启动它的Acitivity不同生共死
BoundService和启动它的Activity同生共死
如果当前进程关闭了,StartedService也就消亡了
2、每次无论是start还是bind,只要service没有实例化,就都会被create
但start每次都会调用onStartCommand
而bind如果已经绑定,就不会在调用onBind
本质区别是BoundService其实是有一个客户端服务器的概念,借助Binder来通讯
音乐播放器采用StartedService服务的原因:
1、StartedService能与Activity分离,可以后台播放,Activity退出不影响
2、StartedService每次启动都会调用onStartCommand,可以传入不同的命令
现在按home键是能够继续在后台继续运行的,例如播放音乐
但是有可能会被系统杀死的,因为处于Service Progress级别,比可见进程级别低
可以通过在onStartCommand中使用startForeground(int, notification)将服务变为可见进程(在高级篇中讲解)
Service运行中如何与用户交互
在Service中调用Activity是不合理的
所以在Service运行中与用户交互有如下方法:
1、Toast Notification
2、StatusBar Notification
这里研究一下Toast的源码
首先是makeText方法
public static Toast makeText(Context context, CharSequence text, int duration) {
Toast result = new Toast(context);
LayoutInflater inflate = (LayoutInflater)
context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View v = inflate.inflate(com.android.internal.R.layout.transient_notification, null);
TextView tv = (TextView)v.findViewById(com.android.internal.R.id.message);
tv.setText(text);
result.mNextView = v;
result.mDuration = duration;
return result;
}
makeText主要是inflate了显示界面,并把显示的值放到TextView中,最后返回Toast对象
这里注意LayoutInflater是context.getSystemService得来的,说明也是系统服务
下面是show方法
public void show() {
if (mNextView == null) {
throw new RuntimeException("setView must have been called");
}
INotificationManager service = getService();
String pkg = mContext.getPackageName();
TN tn = mTN;
tn.mNextView = mNextView;
try {
service.enqueueToast(pkg, tn, mDuration);
} catch (RemoteException e) {
// Empty
}
}
这里能看到service.enqueueToast,是有一个消息队列的
TN是Toast的一个内部类
private static class TN extends ITransientNotification.Stub {
final Runnable mShow = new Runnable() {
public void run() {
handleShow();
}
};
final Runnable mHide = new Runnable() {
public void run() {
handleHide();
// Don't do this in handleHide() because it is also invoked by handleShow()
mNextView = null;
}
};
private final WindowManager.LayoutParams mParams = new WindowManager.LayoutParams();
final Handler mHandler = new Handler();
int mGravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
int mX, mY;
float mHorizontalMargin;
float mVerticalMargin;
View mView;
View mNextView;
WindowManagerImpl mWM;
TN() {
// XXX This should be changed to use a Dialog, with a Theme.Toast
// defined that sets up the layout params appropriately.
final WindowManager.LayoutParams params = mParams;
params.height = WindowManager.LayoutParams.WRAP_CONTENT;
params.width = WindowManager.LayoutParams.WRAP_CONTENT;
params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
| WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON;
params.format = PixelFormat.TRANSLUCENT;
params.windowAnimations = com.android.internal.R.style.Animation_Toast;
params.type = WindowManager.LayoutParams.TYPE_TOAST;
params.setTitle("Toast");
}
/**
* schedule handleShow into the right thread
*/
public void show() {
if (localLOGV) Log.v(TAG, "SHOW: " + this);
mHandler.post(mShow);
}
/**
* schedule handleHide into the right thread
*/
public void hide() {
if (localLOGV) Log.v(TAG, "HIDE: " + this);
mHandler.post(mHide);
}
public void handleShow() {
if (localLOGV) Log.v(TAG, "HANDLE SHOW: " + this + " mView=" + mView
+ " mNextView=" + mNextView);
if (mView != mNextView) {
// remove the old view if necessary
handleHide();
mView = mNextView;
mWM = WindowManagerImpl.getDefault();
final int gravity = mGravity;
mParams.gravity = gravity;
if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.FILL_HORIZONTAL) {
mParams.horizontalWeight = 1.0f;
}
if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.FILL_VERTICAL) {
mParams.verticalWeight = 1.0f;
}
mParams.x = mX;
mParams.y = mY;
mParams.verticalMargin = mVerticalMargin;
mParams.horizontalMargin = mHorizontalMargin;
if (mView.getParent() != null) {
if (localLOGV) Log.v(TAG, "REMOVE! " + mView + " in " + this);
mWM.removeView(mView);
}
if (localLOGV) Log.v(TAG, "ADD! " + mView + " in " + this);
mWM.addView(mView, mParams);
trySendAccessibilityEvent();
}
}
private void trySendAccessibilityEvent() {
AccessibilityManager accessibilityManager =
AccessibilityManager.getInstance(mView.getContext());
if (!accessibilityManager.isEnabled()) {
return;
}
// treat toasts as notifications since they are used to
// announce a transient piece of information to the user
AccessibilityEvent event = AccessibilityEvent.obtain(
AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED);
event.setClassName(getClass().getName());
event.setPackageName(mView.getContext().getPackageName());
mView.dispatchPopulateAccessibilityEvent(event);
accessibilityManager.sendAccessibilityEvent(event);
}
public void handleHide() {
if (localLOGV) Log.v(TAG, "HANDLE HIDE: " + this + " mView=" + mView);
if (mView != null) {
// note: checking parent() just to make sure the view has
// been added... i have seen cases where we get here when
// the view isn't yet added, so let's try not to crash.
if (mView.getParent() != null) {
if (localLOGV) Log.v(TAG, "REMOVE! " + mView + " in " + this);
mWM.removeView(mView);
}
mView = null;
}
}
}
可以看到这个内部类TN的show方法是交由Handler去处理
handleShow方法是设定显示的位置
如何在Service和线程之间选择
Service是一个很方便的组件让你运行后台操作,并且不影响当前操作(但还是是在主线程内的)
当你需要在你当前操作的时候另外起一个更独立操作时候可以考虑线程,比如当前程序不关闭的情况下运行音乐
从逻辑上你认为需要一个后台操作,但跟我们现在主线程操作通讯频繁,逻辑关系紧密,就用多线程(直接用)
能够从逻辑上区分开来,就用后台服务