Android面试收集录1 Activity+Service
1.Activity的生命周期
1.1.首先查看一下Activity生命周期经典图片。
在正常情况下,一个Activity从启动到结束会以如下顺序经历整个生命周期:
onCreate()->onStart()->onResume()->onPause()->onStop()->onDestory()。
包含了六个部分,还有一个onRestart()没有调用,下面我们一一介绍这七部分内容。
(1) onCreate():当 Activity 第一次创建时会被调用。这是生命周期的第一个方法。
在这个方法中,可以做一些初始化工作,比如调用setContentView去加载界面布局资源,初始化Activity所需的数据。
也可借助onCreate()方法中的Bundle对象来恢复异常情况下Activity结束时的状态。
(2) onRestart():表示Activity正在重新启动。
一般情况下,当前Activity从不可见重新变为可见状态时,onRestart就会被调用。
这种情形一般是用户行为导致的,比如用户按Home键切换到桌面或打开了另一个新的Activity,接着用户又回到了这个Actvity。
(3) onStart(): 表示Activity正在被启动,即将开始,这时Activity已经出现了,但是还没有出现在前台,无法与用户交互。
这个时候可以理解为Activity已经显示出来,但是我们还看不到。
(4) onResume():表示Activity已经可见了,并且出现在前台并开始活动。
需要和onStart()对比,onStart的时候Activity还在后台,onResume的时候Activity才显示到前台。
(5) onPause():表示 Activity正在停止,仍可见,正常情况下,紧接着onStop就会被调用。
在特殊情况下,如果这个时候快速地回到当前Activity,那么onResume就会被调用(极端情况)。
onPause中不能进行耗时操作,会影响到新Activity的显示。
因为onPause必须执行完,新的Activity的onResume才会执行。
(6) onStop():表示Activity即将停止,不可见,位于后台。
可以做稍微重量级的回收工作,同样不能太耗时。
(7) onDestory():表示Activity即将销毁,这是Activity生命周期的最后一个回调,可以做一些回收工作和最终的资源回收。
在平常的开发中,我们经常用到的就是 onCreate()和onDestory(),做一些初始化和回收操作。
1.2.生命周期的几种普通情况
①针对一个特定的Activity,第一次启动,回调如下:onCreate()->onStart()->onResume()
②用户打开新的Activiy的时候,上述Activity的回调如下:onPause()->onStop()
③再次回到原Activity时,回调如下:onRestart()->onStart()->onResume()
④按back键回退时,回调如下:onPause()->onStop()->onDestory()
⑤按Home键切换到桌面后又回到该Actitivy,回调如下:onPause()->onStop()->onRestart()->onStart()->onResume()
⑥调用finish()方法后,回调如下:onDestory()
(以在onCreate()方法中调用为例,不同方法中回调不同,通常都是在onCreate()方法中调用)
1.3.特殊情况下的生命周期
①横竖屏切换
在横竖屏切换的过程中,会发生Activity被销毁并重建的过程。
在了解这种情况下的生命周期时,首先应该了解这两个回调==>
onSaveInstanceState和onRestoreInstanceState
在Activity由于异常情况下终止时,系统会调用onSaveInstanceState来保存当前Activity的状态。
这个方法的调用是在onStop之前,它和onPause没有既定的时序关系,该方法只在Activity被异常终止的情况下调用。
当异常终止的Activity被重建以后,系统会调用onRestoreInstanceState
并且把Activity销毁时onSaveInstanceState方法所保存的Bundle对象参数同时传递给onRestoreInstanceState和onCreate方法。
因此,可以通过onRestoreInstanceState方法来恢复Activity的状态,该方法的调用时机是在onStart之后。
其中onCreate和onRestoreInstanceState方法来恢复Activity的状态的区别:
onRestoreInstanceState回调则表明其中Bundle对象非空,不用加非空判断。
onCreate需要非空判断。建议使用onRestoreInstanceState。
横竖屏切换的生命周期:
onPause()->onSaveInstanceState()-> onStop()->onDestroy()
->onCreate()->onStart()->onRestoreInstanceState->onResume()
可以通过在AndroidManifest文件的Activity中指定如下属性:
android:configChanges = "orientation| screenSize"
不需要横竖屏切换时,则会回调一个onConfigurationChanged方法。
@Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); }
②资源内存不足导致优先级低的Activity被杀死
Activity优先级的划分和下面的Activity的三种运行状态是对应的。
(1) 前台Activity——正在和用户交互的Activity,优先级最高。
(2) 可见但非前台Activity——比如Activity中弹出了一个对话框,导致Activity可见但是位于后台无法和用户交互。
(3) 后台Activity——已经被暂停的Activity,比如执行了onStop,优先级最低。
当系统内存不足时,会按照上述优先级从低到高去杀死目标Activity所在的进程。
我们在平常使用手机时,能经常感受到这一现象。
这种情况下数组存储和恢复过程和上述情况一致,生命周期情况也一样。
1.4.Activity的三种运行状态
①Resumed(活动状态)
又叫Running状态,这个Activity正在屏幕上显示,并且有用户焦点。
这个很好理解,就是用户正在操作的那个界面。
②Paused(暂停状态)
这是一个比较不常见的状态。
这个Activity在屏幕上是可见的,但是并不是在屏幕最前端的那个Activity。
比如有另一个非全屏或者透明的Activity是Resumed状态,没有完全遮盖这个Activity。
③Stopped(停止状态)
当Activity完全不可见时,此时Activity还在后台运行,仍然在内存中保留Activity的状态,并不是完全销毁。
这个也很好理解,当跳转的另外一个界面,之前的界面还在后台,按回退按钮还会恢复原来的状态,
大部分软件在打开的时候,直接按Home键,并不会关闭它,此时的Activity就是Stopped状态。
2.Activity的启动模式
2.1.启动模式的类别
Android提供了四种Activity启动方式:
标准模式(standard)
栈顶复用模式(singleTop)
栈内复用模式(singleTask)
单例模式(singleInstance)
2.2.启动模式的结构--栈
Activity的管理是采用任务栈的形式,任务栈采用“后进先出”的栈结构。
2.3.Activity的LaunchMode启动模式
(1)标准模式(standard)
每启动一次Activity,就会创建一个新的Activity实例并置于栈顶。
谁启动了这个Activity,那么这个Activity就运行在启动它的那个Activity所在的栈中。
例如:Activity A启动了Activity B,则就会在A所在的栈顶压入一个新的Activity。
特殊情况,如果在Service或Application中启动一个Activity,其并没有所谓的任务栈,可以使用标记位Flag来解决。
解决办法:为待启动的Activity指定FLAG_ACTIVITY_NEW_TASK标记位,创建一个新栈。
应用场景: 绝大多数Activity。
如果以这种方式启动的Activity被跨进程调用,在5.0之前新启动的Activity实例会放入发送Intent的Task的栈的顶部,
所以在5.0之后,上述情景会创建一个新的Task,新启动的Activity就会放入刚创建的Task中。
(2)栈顶复用模式(singleTop)
如果需要新建的Activity位于任务栈栈顶,那么此Activity的实例就不会重建,而是重用栈顶的实例。并回调如下方法:
@Override protected void onNewIntent(Intent intent) { super.onNewIntent(intent); }
由于不会重建一个Activity实例,则不会回调其他生命周期方法。
如果栈顶不是新建的Activity,就会创建该Activity新的实例,并放入栈顶。
应用场景: 在通知栏点击收到的通知,然后需要启动一个Activity,
这个Activity就可以用singleTop,否则每次点击都会新建一个Activity。
当然实际的开发过程中,测试妹纸没准给你提过这样的bug:
某个场景下连续快速点击,启动了两个Activity。
如果这个时候待启动的Activity使用 singleTop模式也是可以避免这个Bug的。
(3)栈内复用模式(singleTask)
该模式是一种单例模式,即一个栈内只有一个该Activity实例。
该模式,可以通过在AndroidManifest文件的Activity中指定该Activity需要加载到那个栈中,
即singleTask的Activity可以指定想要加载的目标栈。
singleTask和taskAffinity配合使用,指定开启的Activity加入到哪个栈中。
<activity android:name=".Activity1" android:launchMode="singleTask" android:taskAffinity="com.lvr.task" android:label="@string/app_name"> </activity>
关于taskAffinity的值: 每个Activity都有taskAffinity属性,这个属性指出了它希望进入的Task。
如果一个Activity没有显式的指明该Activity的taskAffinity,那么它的这个属性就等于Application指明的taskAffinity。
如果Application也没有指明,那么该taskAffinity的值就等于包名。
执行逻辑:在这种模式下,如果Activity指定的栈不存在,则创建一个栈,并把创建的Activity压入栈内。
如果Activity指定的栈存在,如果其中没有该Activity实例,则会创建Activity并压入栈顶。
如果其中有该Activity实例,则把该Activity实例之上的Activity杀死清除出栈,
重用并让该Activity实例处在栈顶,然后调用onNewIntent()方法。
对应如下三种情况:
应用场景: 大多数App的主页。。对于大部分应用,当我们在主界面点击回退按钮的时候都是退出应用,
那么当我们第一次进入主界面之后,主界面位于栈底,以后不管我们打开了多少个Activity,
只要我们再次回到主界面,都应该使用将主界面Activity上所有的Activity移除的方式来让主界面Activity处于栈顶
而不是往栈顶新加一个主界面Activity的实例,通过这种方式能够保证退出应用时所有的Activity都能报销毁。
在跨应用Intent传递时,如果系统中不存在singleTask Activity的实例,
那么将创建一个新的Task,然后创建SingleTask Activity的实例,将其放入新的Task中。
(4)单例模式(singleInstance)
作为栈内复用模式(singleTask)的加强版,打开该Activity时,直接创建一个新的任务栈,并创建该Activity实例放入新栈中。
一旦该模式的Activity实例已经存在于某个栈中,任何应用再激活该Activity时都会重用该栈中的实例。
应用场景: 呼叫来电界面。这种模式的使用情况比较罕见,在Launcher中可能使用。
或者你确定你需要使Activity只有一个实例。建议谨慎使用。
2.4.特殊情况--前台栈和后台栈的交互
假如目前有两个任务栈。前台任务栈为AB,后台任务栈为CD。
这里假设CD的启动模式均为singleTask,现在请求启动D,那么这个后台的任务栈都会被切换到前台,这个时候整个后退列表就变成了ABCD。
当用户按back返回时,列表中的activity会一一出栈,如下图。
如果不是请求启动D而是启动C,那么情况又不一样,如下图。
调用SingleTask模式的后台任务栈中的Activity,会把整个栈的Actvity压入当前栈的栈顶。
singleTask会具有clearTop特性,把之上的栈内Activity清除。
2.5.Activity的Flags
Activity的Flags很多,这里介绍集中常用的,用于设定Activity的启动模式。
可以在启动Activity时,通过Intent的addFlags()方法设置。
(1)FLAG_ACTIVITY_NEW_TASK 其效果与指定Activity为singleTask模式一致。
(2)FLAG_ACTIVITY_SINGLE_TOP 其效果与指定Activity为singleTop模式一致。
(3)FLAG_ACTIVITY_CLEAR_TOP 具有此标记位的Activity。
当它启动时,在同一个任务栈中所有位于它上面的Activity都要出栈。
如果和singleTask模式一起出现,若被启动的Activity已经存在栈中,则清除其之上的Activity,并调用该Activity的onNewIntent方法。
如果被启动的Activity采用standard模式,那么该Activity连同之上的所有Activity出栈,然后创建新的Activity实例并压入栈中。
3.Service详解
3.1.Service简介
Service是Android程序中四大基础组件之一,它和Activity一样都是Context的子类,只不过它没有UI界面,是在后台运行的组件。
Service是Android中实现程序后台运行的解决方案,它非常适用于去执行那些不需要和用户交互而且还要求长期运行的任务。
Service默认并不会运行在子线程中,它也不运行在一个独立的进程中,它同样执行在UI线程中。
因此,不要在Service中执行耗时的操作,除非你在Service中创建了子线程来完成耗时操作。
3.2.Service种类
按运行地点分类:
按运行类型分类:
按使用方式分类:
3.3.Service生命周期
OnCreate()
系统在service第一次创建时执行此方法,来执行只运行一次的初始化工作。
如果service已经运行,这个方法不会被调用。
onStartCommand()
每次客户端调用startService()方法启动该Service都会回调该方法(多次调用)。
一旦这个方法执行,service就启动并且在后台长期运行。通过调用stopSelf()或stopService()来停止服务。
OnBind()
当组件调用bindService()想要绑定到service时(比如想要执行进程间通讯)
系统调用此方法(一次调用,一旦绑定后,下次再调用bindService()不会回调该方法)。
在你的实现中,你必须提供一个返回一个IBinder来以使客户端能够使用它与service通讯,
你必须总是实现这个方法,但是如果你不允许绑定,那么你应返回null。
OnUnbind()
当前组件调用unbindService(),想要解除与service的绑定时系统调用此方法
(一次调用,一旦解除绑定后,下次再调用unbindService()会抛出异常)。
OnDestory()
系统在service不再被使用并要销毁时调用此方法(一次调用)。
service应在此方法中释放资源,比如线程,已注册的侦听器,接收器等等.这是service收到的最后一个调用。
3.4.下面介绍三种不同情况下Service的生命周期
1.startService/stopService
生命周期顺序:onCreate->onStartCommand->onDestroy
如果一个Service被某个Activity 调用 Context.startService方法启动,
那么不管是否有Activity使用bindService绑定或unbindService解除绑定到该Service,
该Service都在后台运行,直到被调用stopService,或自身的stopSelf方法。
当然如果系统资源不足,android系统也可能结束服务,还有一种方法可以关闭服务,在设置中,通过应用->找到自己应用->停止。
注意点:
①第一次 startService 会触发 onCreate 和 onStartCommand,以后在服务运行过程中,每次 startService 都只会触发 onStartCommand
②不论 startService 多少次,stopService 一次就会停止服务
2.bindService/unbindService
生命周期顺序:onCreate->onBind->onUnBind->onDestroy
如果一个Service在某个Activity中被调用bindService方法启动,
不论bindService被调用几次,Service的onCreate方法只会执行一次,同时onStartCommand方法始终不会调用。
当建立连接后,Service会一直运行,除非调用unbindService来接触绑定、断开连接或调用该Service的Context不存在了
(如Activity被Finish——即通过bindService启动的Service的生命周期依附于启动它的Context),系统在这时会自动停止该Service。
注意点:
第一次 bindService 会触发 onCreate 和 onBind,以后在服务运行过程中,每次 bindService 都不会触发任何回调。
3.混合型(上面两种方式的交互)
当一个Service在被启动(startService)的同时又被绑定(bindService),
该Service将会一直在后台运行,并且不管调用几次,onCreate方法始终只会调用一次,
onStartCommand的调用次数与startService调用的次数一致(使用bindService方法不会调用onStartCommand)。
同时,调用unBindService将不会停止Service,必须调用stopService或Service自身的stopSelf来停止服务。
在什么情况下使用 startService 或 bindService 或 同时使用startService 和 bindService?
①如果你只是想要启动一个后台服务长期进行某项任务那么使用 startService 便可以了。
②如果你想要与正在运行的 Service 取得联系,那么有两种方法,一种是使用 broadcast ,另外是使用 bindService 。
前者的缺点是如果交流较为频繁,容易造成性能上的问题。
并且 BroadcastReceiver 本身执行代码的时间是很短的(也许执行到一半,后面的代码便不会执行)
而后者则没有这些问题,因此我们肯定选择使用 bindService,
这个时候你便同时在使用 startService 和 bindService 了,这在 Activity 中更新 Service 的某些运行状态是相当有用的)。
③如果你的服务只是公开一个远程接口,供连接上的客服端(android 的 Service 是C/S架构)远程调用执行方法。
这个时候你可以不让服务一开始就运行,而只用 bindService ,
这样在第一次 bindService 的时候才会创建服务的实例运行它,这会节约很多系统资源,
特别是如果你的服务是Remote Service,那么该效果会越明显
(当然在 Service 创建的时候会花去一定时间,你应当注意到这点)。
4.Service的几种典型使用实例
4.1.不可交互的后台服务
不可交互的后台服务即是普通的Service,通过startService()方式开启。
Service的生命周期很简单,分别为onCreate、onStartCommand、onDestroy这三个。
创建服务类:
public class BackService extends Service { private Thread mThread; @Override public void onCreate() { super.onCreate(); } @Nullable @Override public IBinder onBind(Intent intent) { System.out.println("onBind"); return null; } @Override public int onStartCommand(Intent intent, int flags, int startId) { //执行耗时操作 mThread = new Thread() { @Override public void run() { try { while (true) { //等待停止线程 if (this.isInterrupted()) { throw new InterruptedException(); } //耗时操作。 System.out.println("执行耗时操作"); } } catch (InterruptedException e) { e.printStackTrace(); } } }; mThread.start(); return super.onStartCommand(intent, flags, startId); } @Override public void onDestroy() { super.onDestroy(); //停止线程 mThread.interrupt(); } }
配置服务:
<service android:name=".BackService"> </service>
如果想配置成远程服务,需要加如下代码:
android:process="remote"
配置好Service类,只需要在前台,调用startService()方法,就会启动耗时操作。
注意:
①不运行在一个独立的进程中,它同样执行在UI线程中,因此,在Service中创建了子线程来完成耗时操作。
②当Service关闭后,如果在onDestory()方法中不关闭线程,你会发现我们的子线程进行的耗时操作是一直存在的,
此时关闭该子线程的方法需要直接关闭该应用程序。因此,在onDestory()方法中要进行必要的清理工作。
4.2.可交互的后台服务
可交互的后台服务是指前台页面可以调用后台服务的方法,通过bindService()方式开启。
Service的生命周期很简单,分别为onCreate、onBind、onUnBind、onDestroy这四个。
可交互的后台服务实现步骤是和不可交互的后台服务实现步骤是一样的,区别在于启动的方式和获得Service的代理对象。
创建服务类:
和普通Service不同在于这里返回一个代理对象,返回给前台进行获取,即前台可以获取该代理对象执行后台服务的方法。
public class BackService extends Service { @Override public void onCreate() { super.onCreate(); } @Nullable @Override public IBinder onBind(Intent intent) { //返回MyBinder对象 return new MyBinder(); } //需要返回给前台的Binder类 class MyBinder extends Binder { public void showTip(){ System.out.println("我是来此服务的提示"); } } @Override public void onDestroy() { super.onDestroy(); } }
前台调用:
通过以下方式绑定服务==>
bindService(mIntent,con,BIND_AUTO_CREATE);
其中第二个参数:
private ServiceConnection con = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { BackService.MyBinder myBinder = (BackService.MyBinder) service; myBinder.showTip(); } @Override public void onServiceDisconnected(ComponentName name) { } };
当建立绑定后,onServiceConnected中的service便是Service类中onBind的返回值。如此便可以调用后台服务类的方法,实现交互。
注意:
通过bindService启动的Service的生命周期依附于启动它的Context。因此当前台调用bindService的Context销毁后,那么服务会自动停止。
4.3.混合型后台服务
将上面两种启动方式结合起来就是混合性交互的后台服务了,即可以单独运行后台服务,也可以运行后台服务中提供的方法,
其完整的生命周期是:onCreate->onStartCommand->onBind->onUnBind->onDestroy
4.4.前台服务
所谓前台服务只不是通过一定的方式将服务所在的进程级别提升了。
前台服务会一直有一个正在运行的图标在系统的状态栏显示,非常类似于通知的效果。
由于后台服务优先级相对比较低,当系统出现内存不足的情况下,它就有可能会被回收掉,
所以前台服务就是来弥补这个缺点的,它可以一直保持运行状态而不被系统回收。
创建服务类:
前台服务创建很简单,其实就在Service的基础上创建一个Notification,然后使用Service的startForeground()方法即可启动为前台服务。
public class ForeService extends Service{ @Nullable @Override public IBinder onBind(Intent intent) { return null; } @Override public void onCreate() { super.onCreate(); beginForeService(); } private void beginForeService() { //创建通知 Notification.Builder mBuilder = new Notification.Builder(this) .setSmallIcon(R.mipmap.ic_launcher) .setContentText("2017-2-27") .setContentText("您有一条未读短信..."); //创建点跳转的Intent(这个跳转是跳转到通知详情页) Intent intent = new Intent(this,NotificationShow.class); //创建通知详情页的栈 TaskStackBuilder stackBulider = TaskStackBuilder.create(this); //为其添加父栈 当从通知详情页回退时,将退到添加的父栈中 stackBulider.addParentStack(NotificationShow.class); PendingIntent pendingIntent = stackBulider.getPendingIntent(0,PendingIntent.FLAG_UPDATE_CURRENT); //设置跳转Intent到通知中 mBuilder.setContentIntent(pendingIntent); //获取通知服务 NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); //构建通知 Notification notification = mBuilder.build(); //显示通知 nm.notify(0,notification); //启动前台服务 startForeground(0,notification); } }
启动前台服务:
startService(new Intent(this, ForeService.class));
关于TaskStackBuilder 这一段,可能不是看的很明白,下面详细介绍。
首先是用一般的PendingIntent来进行跳转
mBuilder = new NotificationCompat.Builder(this).setContent(view) .setSmallIcon(R.drawable.icon).setTicker("新资讯") .setWhen(System.currentTimeMillis()) .setOngoing(false) .setAutoCancel(true); Intent intent = new Intent(this, NotificationShow.class); PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); mBuilder.setContentIntent(pendingIntent);
这里是直接用PendingIntent来跳转到NotificationShow。
在运行效果上来看,首先发送了一条Notification到通知栏上,
然后这时,退出程序,即MainActivity已经不存在了,回到home主菜单,看到Notification仍然存在,
当然,我们还没有点击或者cancel它,现在去点击Notification,跳转到NotificationShow界面,
然后我们按下Back键,发现直接回到home菜单了。
现在大多数android应用都是在通知栏中如果有Notification通知的话,点击它,然后会直接跳转到对应的应用程序的某个界面,
这时如果回退,即按下Back键,会返回到该应用程序的主界面,而不是系统的home菜单。
所以用上面这种PendingIntent的做法达不到目的。这里我们使用TaskStackBuilder来做。
mBuilder = new NotificationCompat.Builder(this) .setContent(view) .setSmallIcon(R.drawable.icon).setTicker("新资讯") .setWhen(System.currentTimeMillis()) .setOngoing(false) .setAutoCancel(true); Intent intent = new Intent(this, NotificationShow.class); TaskStackBuilder stackBuilder = TaskStackBuilder.create(this); stackBuilder.addParentStack(NotificationShow.class); stackBuilder.addNextIntent(intent); PendingIntent pendingIntent = stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT); //PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); mBuilder.setContentIntent(pendingIntent);
显示用TaskStackBuilder.create(this)创建一个stackBuilder实例,接下来addParentStack();
关于这个方法,我们查一下官方API文档:...
这句话意思是:为跳转后的activity添加一个父activity,在activity中的manifest中添加parentActivityName即可。
那么我们就在manifest文件中添加这个属性
<activity android:name="com.lvr.service.NotificationShow" android:parentActivityName=".MainActivity" > </activity>
这里我让它的parentActivity为MainActivity,也就是说在NotificationShow这个界面点击回退时,
会跳转到MainActivity这个界面,而不是像上面一样直接回到了home菜单。
注意:
通过 stopForeground()方法可以取消通知,即将前台服务降为后台服务。
此时服务依然没有停止。通过stopService()可以把前台服务停止。
5.参考文章
本篇文章全部来源于:
Activity部分: https://github.com/LRH1993/android_interview/blob/master/android/basis/activity.md
Service部分: https://github.com/LRH1993/android_interview/blob/master/android/basis/service.md
更多内容请点击以上链接。