Service这个组件好歹也是四大组件之一,但是因为业务的需求不同很可能有很多的小伙伴并没有用过。或者用过之后也感觉还是不是了解的很透彻,今天我就来说说我的理解吧,这里我主要是基于Local Service,对于Remote Service我会在下一篇文章中探讨。
从一个栗子开始
对于Service看看它的源码还是比较简单的,方法也不是太多。为了更好的理解Local Service的使用和注意事项在这里写一个简单的Demo,通过对这个Demo的分析逐步理解它。这个Demo的主要功能是开启一个后台服务,然后回到主界面有一个浮动窗口,通过浮动窗口可以开启一个Dialog(类似于360内存清理等浮动窗口)。具体效果如下所示:
分析栗子
这里简单的通过一个流程图,看看Service的启动和停止的过程吧,如下所示:
为了更好的理解上面的过程,现在通过上面的栗子验证上图所示的过程。浮动窗口主要是使用WindowsManager添加View实现,主要的代码如下所示:
/**
* initialize the {@link #mWindowManager} and
* {@link #mFloatView} which is floating view
* */
private void init(){
mWindowManager = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
mFloatView = LayoutInflater.from(mContext).inflate(R.layout.float_window_layout, null);
mWindowPosition = new Point();
mTouchPosition = new PointF();
mFloatView.findViewById(R.id.start_activity).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(mContext, LocalService.TipActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mContext.startActivity(intent);
}
});
}
/**
* show the floating view,
* just add view to {@link WindowManager}
* */
public void show(){
if (!mIsShowed){
WindowManager.LayoutParams wl = new WindowManager.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.TYPE_TOAST,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
PixelFormat.TRANSLUCENT);
wl.gravity = Gravity.LEFT | Gravity.TOP;
mWmLayoutParams = wl;
mWindowManager.addView(mFloatView, wl);
mFloatView.setOnTouchListener(this);
mIsShowed = true;
}
}
/**
* if there is view already added to {@link WindowManager}
* remove this view immediately
* */
public void hide(){
if (mIsShowed){
mWindowManager.removeViewImmediate(mFloatView);
mIsShowed = false;
}
}
LocalService.java中的onCreate()和onStartCommand()方法如下:
@Override
public void onCreate() {
super.onCreate();
mFloatWindow = new FloatWindow(this);
Toast.makeText(this, "Service onCreate", Toast.LENGTH_LONG).show();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d(TAG, " onStartCommand intent " + intent);
if (intent == null){
Toast.makeText(this, "null intent", Toast.LENGTH_LONG).show();
}else {
String action = intent.getAction();
if (ACTION_SHOW_FLOATING_WINDOW.equals(action)){
mFloatWindow.show();
}else if (ACTION_HIDE_FLOATING_WINDOW.equals(action)){
mFloatWindow.hide();
}
}
return super.onStartCommand(intent, flags, startId);
}
如果我们想使用Service,第一步需要做的就是启动它,就像我们使用startActivity()等一系列的方法启动一个新的Activity一样,启动Service当然是使用startService(),当调用startService()后,如果Service为第一次启动,会顺序调用onCreate()和onStartCommand()方法。如果当前Service已经启动则只会调用onStartCommand()方法。onCreate()方法主要就是做一些初始化的工作,这里实例化了一个浮动窗口类,onStartCommand()方法可以通过startService多次调用,利用该方法可以实现浮动窗口的现实和隐藏。这里做了一个非空的判断,那么为什么要做这个工作呢,不是通过调用startService()方法才会触发该方法吗,其实不然,我们看看下面的效果:
我在最近中把当前的应用kill掉,但是系统却帮我们重新启动了Service(这里要注意一下,这个和手机的系统有关系的,我这里使用的是模拟器,但是我手头上的华为P7是不会出现这种现象的,同时这个关于重新创建的问题,我在实际开发的过程中发现有的手机使用360清理内存的时候会把开启在后台的Service给kill掉但是系统会重新启动,这时的情况和这个现象一样),这个时候Intent就是为null值,那么为什么会重新启动呢,是通过什么控制的呢,这个是和onStartCommand()方法的返回值有关系的,该方法的返回值有三个参数,它们对应的功能如下:
START_NOT_STICKY
如果系统在正常调用onStartCommand()方法返回后终止了服务(stopService),则除非有挂起 Intent 要传递(就是在其他地方还有没来得及调用传递Intent的地方,例如在第一次启动Service时,然后在停止的时候立马又启动Service),否则系统不会重建服务。这是最安全的选项,可以避免在不必要时以及应用能够轻松重启所有未完成的作业时运行服务。
START_STICKY
如果系统在onStartCommand()返回后终止服务,则会重建服务并调用onStartCommand(),但绝对不会重新传递最后一个Intent。相反,除非有挂起 Intent 要启动服务在这种情况下,将传递这些 Intent),否则系统会通过空Intent调用onStartCommand()。这适用于不执行命令、但无限期运行并等待作业的媒体播放器(或类似服务)。(如果默认情况下没有修改onStartCommand()方法的返回值,该值为默认值)
START_REDELIVER_INTENT
如果系统在 onStartCommand() 返回后终止服务,则会重建服务,并通过传递给服务的最后一个 Intent 调用 onStartCommand()。任何挂起 Intent 均依次传递。这适用于主动执行应该立即恢复的作业(例如下载文件)的服务。
这里我们使用多次调用startService方法触发onStartCommand()方法显示和隐藏浮动窗口,所以可以通过startService()来和Service进行交互,Service与其他组件的通信也都是通过广播来实现。。但是这个startService()和广播也是有自己的缺陷的,那就是如果需要传递参数都是需要通过Intent进行携带的,用起来就不是那么方便了,现在大家用EventBus来解决这块通信的问题,这样就比较的方便了。
对于Service的停止必须调用stopService()方法(bind方式启动的就是unbind方法)才可以,对于一开始的Demo效果图大家可以看到,如果我直接按下返回键,启动的Service并没有调用OnDestroy方法(如果调用会弹出Toast),说明Service并没有停止。这个大家还是自己实践一下认识的深刻一点。
Servcie、Activity和Thread
Thread:开启一个线程主要是为了异步处理耗时的事件;
Activity:运行在UI线程,主要是为了用户界面的显示和处理事件的分发;
Service:运行在UI线程,处理一些不需要及时的显示用户界面,但是在有处理结果后可以及时的通过UI给用户反馈(暂时我是这么理解的,但是岂止这么点呢_)。
这里的Service、Activity是在同一个线程中,我们称这个线程为UI线程,该线程的主要作用就是处理与用户的交互,主要包括UI显示以及触摸事件的处理。在开始的时候我曾经很困惑,总是感觉Service就是为了做一些耗时操作的,和UI交互没有太多的关系,为什么Android系统要搞出这么一个东西呢,后来我发现,说Service没有UI其实是不完全正确的,对于Service来说是可以获取Context对象,获取Context对象之后就可以获取相应的服务、启动Activity、弹出Dialog等一系列UI界面。我们通常说的下载就需要一个Service,那么为什么不直接在Activity当中启动一个Thread呢,我们知道因为Activity是有生命周期的,并且是为了界面切换所使用的,一旦退出后,Thread所依赖的上下文环境就没有了。但是对于Service就不会了,它的生命周期是可以在外部控制的,并且常驻在内存中,随时使用随时唤醒。
Service使用注意事项
1.不使用的时候及时的stop或者unbind
当Service在后台运行的时候是占用一定的内存的,如果你不使用了,最好及时的将其stop,这样就可以释放一定的内存。其实启动Service就像在酒店开了一个房间一样,如果你不住了,但是你不把它退掉,你就需要付钱。其实Android系统中如果在内存不够用的情况下,系统会优先将处于后台的Activity对象kill掉,这里就涉及到当Activity和Service同时处于后台时,它们的级别那一个更高的问题。我们可以看看这里对不同类型的process介绍。这里面有一个visible process,那么哪些是visible process呢,例如我们启动一个应用,当前的应用处于用户交互状态,那么它就是visible process,相应的处于后台的应用就是invisible process。其实这里我们可以把service 所在的process提升为visible级别, 通过调用startForeground(id, notification)方法实现,对于这个方法的详细使用大家可以看看源码的注释吧,这里就不再讨论了。对于visible process 在所有的process当中级别是最高的,所以在内存不够用的情况下不会轻易的被kill掉。
2.防止Service被杀掉
对于Service来说,当用户退到后台时,如果手机的内存不够时会触发Android系统杀掉处于后台的应用,但是在内存紧张时会都用如下的方法:
@Override
public void onTrimMemory(int level) {
super.onTrimMemory(level);
}
@Override
public void onLowMemory() {
super.onLowMemory();
}
其中onTrimMemory(int levle)方法中的level代表当前进程的memory处于哪个级别,这里主要有下面几种级别:
static final int TRIM_MEMORY_COMPLETE = 80;
static final int TRIM_MEMORY_MODERATE = 60;
static final int TRIM_MEMORY_BACKGROUND = 40;
static final int TRIM_MEMORY_UI_HIDDEN = 20;
static final int TRIM_MEMORY_RUNNING_CRITICAL = 15;
static final int TRIM_MEMORY_RUNNING_LOW = 10;
static final int TRIM_MEMORY_RUNNING_MODERATE = 5;
这里的每个级别对应的注释可以看ComponentCallbacks2接口(这里需要注意的是这个接口是在Android3.0以后才用的,并且Activity也实现了该接口),对于onLowMemory()方法则表示系统没有太多内存可使用了,如果没有足够的内存使用将会kill掉当前的进程(这个时候要看当前进程的级别了,主要是1里面分析的),这个时候我们要将我们cache的内容释放掉,例如使用ImageLoader缓存在内存中的图片、优先级不是很高的Service或者Activity kill掉。很多人或许都和我一样,感觉自己好像从来没有遇到过手机内存很紧张的情况,其实当我们压缩图片或者使用WebView打开网页的时候都会消耗很大的内存的。
总结
今天通过一个小demo简单的讲解了Service的内容,主要也添加了自己的一些理解吧,对于Service的另一种启动方法——bind大家可以自行分析,主要的思路都差不多的。其实关于Service的东西还有很多,大家只有多实践,或者在实际的项目开发中解决相应的问题才会对它有更深刻的理解吧。这里的demo源码可以到这里去看看。
希望在Android学习的路上,大家共同成长!