面试题

        onCreate()  --  onStart()  --  onRause()  --  onStop()  --  onDetroy()

  1、onCreate() 生命周期的一个方法,表示activity正在创建启动,特别说明:若在该方法中调用finish方法,则会立即出发onDestroy回调,其他生命周期不会执行。

  2、onRestart() 该方法触发的前提是onStop方法被调用。onStop方法被调用而导致  Activity不可见再到Activitu再次可见时被调用,该方法被调用之后系统会相继触发  onStart和onResume方法。

  3、onStart()  Activitu可见时被调用(此时Activity尚未出于前台),在onCreate方法之后或由onStop方法被调用而导致Activity不可见到Activity再次可见时被调用。

  4、onResume()  该方法被调用表示Activity出于前台,官方文档指明,这里比较适合动画启动及排他性设备访问(如相继)等。

  5、onPause()  Activity即将进入后台时回调此方法,需要特别注意的是,若Activity A启动Activity B,则Activity A的onPause方法回调完成后,Activity B才会创建,因此不要在该方法中做耗时操作。

  6、onStop()  Activity由可见到不可见时回调此方法。

  7、onDestroy()  生命周期的最后一个方法,表示Activity即将被销毁,官方文档指明,在某种情况下,系统会简单粗暴的杀掉Activity的宿主进程,因此我们不应该依赖此方法做数据数据存储工作,可在此方法中做资源释放操作。

 

  下图为官方文档给出的示意图:

 

 

  

此图很清晰严谨,但是官方文档对各个生命周期回调方法的描述并不是很详细,因此如果不认真研读文档,很难明白图中的生命周期方法的走向,并可能对某些方法产生误解。例如对于onStop方法,可能会有部分童鞋对其有误解,误认为Activity进入后台时回调该方法,其实进入后台时回调的是onPause方法,不可见时回调onStop方法;也有童鞋误以为onResume方法回调意味着可以进行交互,其实我们应该以public void onWindowFocusChanged (boolean hasFocus)回调中的hasFocus参数为依据来判定是否可以进行交互。

Activity可见与否与其是否处于前台是两回事,onStart 与 onStop 配对描述Activity是否可见;onPause 与
onResume 配对描述Activity是否处于前台。Activity可见并不意味着可以交互,同样的其处于前台也未必可以交互。直接的例子是,若Activity展示了Dialog,此时Activity仍处于前台,但我们却不能与Activity交互

另外,对于上述示意图中的标注2走向,我相信很多人并没有亲自验证过,因为通常你很难通过交互来复现这一场景。对于这种场景,我们可以通过代码控制来模拟:Activity A启动Activity B,在Activity B的onCreate方法中直接调用finish方法

 

二、Service 生命周期

  Service两种启动方式:一种是通过startService()方式进行启动,  另一种是通过bindService()方法启动,不同的启动方式他们的生命周期是不一样的。

  通过startService()这种方式启动的service生命周期:startServicr() --  onCreate()  --  onStartConmon()  --  onDestroy().这种方式需要注意几个问题:1、当我们通过startServicr()被调用以后,多次再调用startService(),onCreate()方法也只会被调用一次,而onStartConmon()会被多次调用,当我们stopService()的时候,onDestroy()就会被调用,从而销毁服务。2、当我们通过startService()启动时候,通过intent传值,在onStartConmon()方法中获取值的时候,一定要判断intent是否为null。

   通过bindService()方式进行绑定,这种方式绑定Service生命周期:bindService()  --  onCreate()  -- onBind()  --  onFDestroy()

  bindService()这种方式启动service好处是更加便利activity中操作service,比如假如service中有几个方法 a() 、b () ,如果要在activity中调用,在activity中获取ServiceConnectio对象,通过ServiceConnection获取service中内部类对象,然后通过这个内部类就可以调用service中的方法了,这个内部类必须继承Binder对象。

 

三、Activity的启动过程

  不要回答生命周期  

  app启动过程有两种情况,第一种是点击应用桌面相应的图标。第二种是在activity中调用startActivity来启动一个新的activity。

  我们创建一个新项目,默认activity都是MainActivity,而所有的acrtivity都是保存在堆栈中的,我们启动一个新的activity就会放在上一个activity上面,而我们点击桌面图标的时候,由于launcher本身也是一个应用,当我们点击图标的时候,系统就调用startActivitySately(),一般情况下,我们所启动的activity的相关信息都会保存到intent中,比如action、category等等,我们在装这个应用的时候,系统也会启动一个PackaManagerService的管理服务,这个管理服务会对AndroidManifest.xml文件进行解析,从而得到应用程序中的相关信息,比如service,activity,Broadcast等等,然后获得相关组件的信息。当我们点击应用图标的时候,就会调用startActivitySately()方法,而这个方法内部则是调用startActivty(),而startActivity()方法最终还是会调用startActivityForResult()这个方法。而在startActivityForResult()这个方法。因为startActivityForResult()方法是有返回结果的,所以系统就直接给一个-1,就表示不需要结果返回了。而startActivityForResult()这个方法实际是通过Instrumentation类中的execStartActivity()方法来启动activity,Instrumentation这个类主要作用就是监控程序和系统之间的交互。而在这个execStartActivity()方法中会获取ActivityManagerService的代理对象,通过这个代理对象进行启动activity。启动会就会调用一个checkStartActivityResult()方法,如果说没有在配置清单中配置有这个组件,就会在这个方法中抛出异常了。当然最后是调用的是Application.scheduleLaunchActivity()进行启动activity,而这个方法中通过获取得到一个ActivityClientRecord对象,而这个ActivityClientRecord通过handler来进行消息的发送,系统内部会将每一个activity组件使用ActivityClientRecord对象来进行描述,而ActivityClientRecord对象中保存有一个LoaderApk对象,通过这个对象调用handleLaunchActivity来启动activity组件,而页面的生命周期方法也就是在这个方法中进行调用。

四、Broadcast注册方式与区别

  此处延伸:什么情况下用动态注册

Broadcast广播,注册方式主要有两种.

  第一种是静态注册,也可成为常驻型广播,这种广播需要在Androidmanifest.xml中进行注册,这中方式注册的广播,不受页面生命周期的影响,即使退出了页面,也可以收到广播这种广播一般用于想开机自启动啊等等,由于这种注册的方式的广播是常驻型广播,所以会占用CPU的资源。

  第二种是动态注册,而动态注册的话,是在代码中注册的,这种注册方式也叫非常驻型广播,收到生命周期的影响,退出页面后,就不会收到广播,我们通常运用在更新UI方面。这种注册方式优先级较高。最后需要解绑,否会会内存泄露

  广播是分为有序广播和无序广播。

1.无序广播

  通过Context.sendBroadcast()方法来发送,它是完全异步的。

所有的receivers(接收器)的执行顺序不确定,因此所有的receivers(接收器)接收broadcast的顺序不确定。

这种方式效率更高,但是BroadcastReceiver无法使用setResult系列、getResult系列及abortbroadcast(中止)系列API。

广播不能被终止,数据不能被修改。

2.有序广播

  有序广播,即从优先级别最高的广播接收器开始接收,接收完了如果没有丢弃,就下传给下一个次高优先级别的广播接收器进行处理,依次类推,直到最后。如果多个应用程序设置的优先级别相同,则谁先注册的广播,谁就可以优先接收到广播。通过Context.sendorderBroadCast()方法来发送,sendOrderedBroadcast(intent, receiverPermission, resultReceiver, scheduler, initialCode, initialData, initialExtras);,其中的参数resultReceiver,可以自己重写一个类,作为一个最终的receive 最后都能够接收到广播,最终的receiver 不需要再清单文件里面配置,initialData可以作为传输的数据

广播可以被终止,数据传输过程中可以被修改。

 

五、HttpClient与HttpUrlConnection的区别

  此处延伸:Volley里用的哪种请求方式(2.3前HttpClient,2.3后HttpUrlConnection)

  首先HttpClient和HttpUrlConnection 这两种方式都支持Https协议,都是以流的形式进行上传或者下载数据,也可以说是以流的形式进行数据的传输,还有ipv6,以及连接池等功能。HttpClient这个拥有非常多的API,所以如果想要进行扩展的话,并且不破坏它的兼容性的话,很难进行扩展,也就是这个原因,Google在Android6.0的时候,直接就弃用了这个HttpClient.

  而HttpUrlConnection相对来说就是比较轻量级了,API比较少,容易扩展,并且能够满足Android大部分的数据传输。比较经典的一个框架volley,在2.3版本以前都是使用HttpClient,在2.3以后就使用了HttpUrlConnection。

六、java虚拟机和Dalvik虚拟机的区别

  Java虚拟机:

1、java虚拟机基于栈。 基于栈的机器必须使用指令来载入和操作栈上数据,所需指令更多更多。

2、java虚拟机运行的是java字节码。(java类会被编译成一个或多个字节码.class文件)

 

Dalvik虚拟机:

1、dalvik虚拟机是基于寄存器的

2、Dalvik运行的是自定义的.dex字节码格式。(java类被编译成.class文件后,会通过一个dx工具将所有的.class文件转换成一个.dex文件,然后dalvik虚拟机会从其中读取指令和数据

3、常量池已被修改为只使用32位的索引,以 简化解释器。

4、一个应用,一个虚拟机实例,一个进程(所有android应用的线程都是对应一个linux线程,都运行在自己的沙盒中,不同的应用在不同的进程中运行。每个android dalvik应用程序都被赋予了一个独立的linux PID(app_*)

 

七、进程保活(不死进程)

此处延伸:进程的优先级是什么

当前业界的Android进程保活手段主要分为** 黑、白、灰 **三种,其大致的实现思路如下:

黑色保活:不同的app进程,用广播相互唤醒(包括利用系统提供的广播进行唤醒)

白色保活:启动前台Service

灰色保活:利用系统的漏洞启动前台Service

黑色保活

所谓黑色保活,就是利用不同的app进程使用广播来进行相互唤醒。举个3个比较常见的场景:

场景1:开机,网络切换、拍照、拍视频时候,利用系统产生的广播唤醒app

场景2:接入第三方SDK也会唤醒相应的app进程,如微信sdk会唤醒微信,支付宝sdk会唤醒支付宝。由此发散开去,就会直接触发了下面的 场景3

场景3:假如你手机里装了支付宝、淘宝、天猫、UC等阿里系的app,那么你打开任意一个阿里系的app后,有可能就顺便把其他阿里系的app给唤醒了。(只是拿阿里打个比方,其实BAT系都差不多)

白色保活

白色保活手段非常简单,就是调用系统api启动一个前台的Service进程,这样会在系统的通知栏生成一个Notification,用来让用户知道有这样一个app在运行着,哪怕当前的app退到了后台。如下方的LBE和QQ音乐这样:

灰色保活

灰色保活,这种保活手段是应用范围最广泛。它是利用系统的漏洞来启动一个前台的Service进程,与普通的启动方式区别在于,它不会在系统通知栏处出现一个Notification,看起来就如同运行着一个后台Service进程一样。这样做带来的好处就是,用户无法察觉到你运行着一个前台进程(因为看不到Notification),但你的进程优先级又是高于普通后台进程的。那么如何利用系统的漏洞呢,大致的实现思路和代码如下:

思路一:API < 18,启动前台Service时直接传入new Notification();

思路二:API >= 18,同时启动两个id相同的前台Service,然后再将后启动的Service做stop处理

熟悉Android系统的童鞋都知道,系统出于体验和性能上的考虑,app在退到后台时系统并不会真正的kill掉这个进程,而是将其缓存起来。打开的应用越多,后台缓存的进程也越多。在系统内存不足的情况下,系统开始依据自身的一套进程回收机制来判断要kill掉哪些进程,以腾出内存来供给需要的app。这套杀进程回收内存的机制就叫 Low Memory Killer ,它是基于Linux内核的 OOM Killer(Out-Of-Memory killer)机制诞生。

进程的重要性,划分5级:

前台进程 (Foreground process)

可见进程 (Visible process)

服务进程 (Service process)

后台进程 (Background process)

空进程 (Empty process)

了解完 Low Memory Killer,再科普一下oom_adj。什么是oom_adj?它是linux内核分配给每个系统进程的一个值,代表进程的优先级,进程回收机制就是根据这个优先级来决定是否进行回收。对于oom_adj的作用,你只需要记住以下几点即可:

进程的oom_adj越大,表示此进程优先级越低,越容易被杀回收;越小,表示进程优先级越高,越不容易被杀回收

普通app进程的oom_adj>=0,系统进程的oom_adj才可能<0

有些手机厂商把这些知名的app放入了自己的白名单中,保证了进程不死来提高用户体验(如微信、QQ、陌陌都在小米的白名单中)。如果从白名单中移除,他们终究还是和普通app一样躲避不了被杀的命运,为了尽量避免被杀,还是老老实实去做好优化工作吧。

所以,进程保活的根本方案终究还是回到了性能优化上,进程永生不死终究是个彻头彻尾的伪命题!

 八、讲解一下Context

  Context是一个抽象基类。在翻译为上下文,也可以理解为环境,是提供一些程序的运行环境基础信息。Context下有两个子类,ContextWrapper是上下文功能的封装类,而ContextImpl则是上下文功能的实现类。而ContextWrapper又有三个直接的子类, ContextThemeWrapper、Service和Application。其中,ContextThemeWrapper是一个带主题的封装类,而它有一个直接子类就是Activity,所以Activity和Service以及Application的Context是不一样的,只有Activity需要主题,Service不需要主题。Context一共有三种类型,分别是Application、Activity和Service。这三个类虽然分别各种承担着不同的作用,但它们都属于Context的一种,而它们具体Context的功能则是由ContextImpl类去实现的,因此在绝大多数场景下,Activity、Service和Application这三种类型的Context都是可以通用的。不过有几种场景比较特殊,比如启动Activity,还有弹出Dialog。出于安全原因的考虑,Android是不允许Activity或Dialog凭空出现的,一个Activity的启动必须要建立在另一个Activity的基础之上,也就是以此形成的返回栈。而Dialog则必须在一个Activity上面弹出(除非是System Alert类型的Dialog),因此在这种场景下,我们只能使用Activity类型的Context,否则将会出错。

  getApplicationContext()和getApplication()方法得到的对象都是同一个application对象,只是对象的类型不一样。

  Context数量 = Activity数量 + Service数量 + 1 (1为Application)

 

九、理解Activity,View,Window三者关系  

  这个问题真的很不好回答。所以这里先来个算是比较恰当的比喻来形容下它们的关系吧。Activity像一个工匠(控制单元),Window像窗户(承载模型),View像窗花(显示视图)LayoutInflater像剪刀,Xml配置像窗花图纸。

1:Activity构造的时候会初始化一个Window,准确的说是PhoneWindow。

2:这个PhoneWindow有一个“ViewRoot”,这个“ViewRoot”是一个View或者说ViewGroup,是最初始的根视图。

3:“ViewRoot”通过addView方法来一个个的添加View。比如TextView,Button等

4:这些View的事件监听,是由WindowManagerService来接受消息,并且回调Activity函数。比如onClickListener,onKeyDown等。

十、四种LaunchMode及其使用场景

  此处延伸:栈(First In Last Out)与队列(First In First Out)的区别

  栈与队列的区别:

1、队列先进先出,栈先进后出
2. 对插入和删除操作的"限定"。 栈是限定只能在表的一端进行插入和删除操作的线性表。 队列是限定只能在表的一端进行插入和在另一端进行删除操作的线性表。

3. 遍历数据速度不同

  standard 模式

  这是默认模式,每次激活Activity时都会创建Activity实例,并放入任务栈中。使用场景:大多数Activity。

  singleTop 模式

  如果在任务的栈顶正好存在该Activity的实例,就重用该实例( 会调用实例的 onNewIntent() ),否则就会创建新的实例并放入栈顶,即使栈中已经存在该Activity的实例,只要不在栈顶,都会创建新的实例。使用场景如新闻类或者阅读类App的内容页面。

  singleTask 模式

  如果在栈中已经有该Activity的实例,就重用该实例(会调用实例的 onNewIntent() )。重用时,会让该实例回到栈顶,因此在它上面的实例将会被移出栈。如果栈中不存在该实例,将会创建新的实例放入栈中。使用场景如浏览器的主界面。不管从多少个应用启动浏览器,只会启动主界面一次,其余情况都会走onNewIntent,并且会清空主界面上面的其他页面。

  singleInstance 模式

  在一个新栈中创建该Activity的实例,并让多个应用共享该栈中的该Activity实例。一旦该模式的Activity实例已经存在于某个栈中,任何应用再激活该Activity时都会重用该栈中的实例( 会调用实例的 onNewIntent() )。其效果相当于多个应用共享一个应用,不管谁激活该 Activity 都会进入同一个应用中。使用场景如闹铃提醒,将闹铃提醒与闹铃设置分离。singleInstance不要用于中间页面,如果用于中间页面,跳转会有问题,比如:A -> B (singleInstance) -> C,完全退出后,在此启动,首先打开的是B。

十一、View的绘制流程

自定义控件:

1、组合控件。这种自定义控件不需要我们自己绘制,而是使用原生控件组合成的新控件。如标题栏。

2、继承原有的控件。这种自定义控件在原生控件提供的方法外,可以自己添加一些方法。如制作圆角,圆形图片。

3、完全自定义控件:这个View上所展现的内容全部都是我们自己绘制出来的。比如说制作水波纹进度条。

View的绘制流程:OnMeasure()——>OnLayout()——>OnDraw()

第一步:OnMeasure():测量视图大小。从顶层父View到子View递归调用measure方法,measure方法又回调OnMeasure。

第二步:OnLayout():确定View位置,进行页面布局。从顶层父View向子View的递归调用view.layout方法的过程,即父View根据上一步measure子View所得到的布局大小和布局参数,将子View放在合适的位置上。

第三步:OnDraw():绘制视图。ViewRoot创建一个Canvas对象,然后调用OnDraw()。六个步骤:①、绘制视图的背景;②、保存画布的图层(Layer);③、绘制View的内容;④、绘制View子视图,如果没有就不用;

⑤、还原图层(Layer);⑥、绘制滚动条。

 

posted @ 2020-04-17 09:53  光-哥  阅读(55)  评论(0)    收藏  举报