中高级Android大厂面试秘籍,为你保驾护航金三银四,直通大厂(上

分别执行A onPause()、B onCreate()、B onStart()、B onResume()、A onStop()方法。

关闭B Activity

分别执行B onPause()、A onRestart()、A onStart()、A onResume()、B onStop()、B onDestroy()方法。

横竖屏切换A Activity

清单文件中不设置android:configChanges属性时,先销毁onPause()、onStop()、onDestroy()再重新创建onCreate()、onStart()、onResume()方法,

设置orientation|screenSize(一定要同时出现)属性值时,不走生命周期方法,只会执行onConfigurationChanged()方法。

Activity之间的切换

可以看出onPause()、onStop()这两个方法比较特殊,切换的时候onPause()方法不要加入太多耗时操作否则会影响体验。

2.Activity的启动过程(不要回答生命周期)


app启动的过程有两种情况

第一种是从桌面launcher上点击相应的应用图标

第二种是在activity中通过调用startActivity来启动一个新的activity。

我们创建一个新的项目,默认的根activity都是MainActivity,而所有的activity都是保存在堆栈中的,我们启动一个新的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组件,而页面的生命周期方法也就是在这个方法中进行调用。

3.Activity之间的通信方式


Intent

借助类的静态变量

借助全局变量/Application

借助外部工具

借助SharedPreference

使用Android数据库SQLite

赤裸裸的使用File

Android剪切板

借助Service

4.横竖屏切换的时候,Activity 各种情况下的生命周期


分两种情况:

1.不设置Activity的android:configChanges,或设置Activity的android:configChanges=“orientation”,或设置Activity的android:configChanges=“orientation|keyboardHidden”,切屏会重新调用各个生命周期,切横屏时会执行一次,切竖屏时会执行一次。

横竖屏切换造成 activity 的生命周期

onPause()-onSaveInstanceState()-onStop()-onDestroy()-onCreat()-onStart()-onRestoreInstanceState()-onResume()即会导致 activity 的销毁和重建 。

2.配置 android:configChanges=“orientation|keyboardHidden|screenSize”,才不会销毁 activity,且只调用 onConfigurationChanged方法。

onSaveInstanceState() 与onRestoreIntanceState() 资源相关的系统配置发生改变或者资源不足时(例如屏幕旋转),当前 Activity 会销毁,并且在 onStop 之前回调 onSaveInstanceState 保存数据,在重新创建 Activity 的时候在onStart 之后回调 onRestoreInstanceState。其中 Bundle 数据会传到 onCreate(不一定有数据)和 onRestoreInstanceState(一定有数据)。

用户或者程序员主动去销毁一个 Activity 的时候不会回调(如代码中 finish()或用户按下 back,不会回调),其他情况都会调用,来保存界面信息。

5.两个Activity 之间跳转时必然会执行的是哪几个方法?


a. 正常情况下 Activity A 跳转到 Activity B 时:

A调用 onCreate() 方法 -> onStart() 方法 -> onResume() 方法,此时 A 前台可见。当 A 跳转到 B 时,A 调用 onPause() 方法,然后调用新的 Activity B 中的 onCreate() 方法 -> onStart() 方法 -> onResume() 方法。最后 A 再调用onStop()方法。

b. 当 Activity B 为透明主题时:

除了最后 Activity A 不调用 onStop() 方法之外,其它都和 a 中的一样。

6.Activity状态保存于恢复


当 Activity 在异常情况( 系统内存不足或者系统配置发生了改变等 )被销毁重建后, 在销毁的时候 Activity 会调用 onSaveInstanceState() 方法用于保存 Activity 相关的状态和数据,然后在重建后的 Activity 的中我们可以通过 onCreate() 或者 onRestoreInstanceState() 方法恢复数据,这里我们需要注意的是如果通过 onCreate() 方法恢复,那么得先判断它的 intent 参数 是否为空,如果在 onRestoreInstanceState() 方法恢复就不会,因为只要 onRestoreInstanceState() 方法被调用就说明一定有数据,不会为空。Google 推荐使用 onRestoreInstanceState() 方法。

7.Fragment的生命周期方法


主要有onAttach()、onCreate()、onCreateView()、onActivityCreated()、onstart()、onResume()、onPause()、onStop()、onDestroyView()、onDestroy()、onDetach()等11个方法。

切换到该Fragment

分别执行onAttach()、onCreate()、onCreateView()、onActivityCreated()、onstart()、onResume()方法。

锁屏

分别执行onPause()、onStop()方法。

亮屏

分别执行onstart()、onResume()方法。

覆盖切换到其他Fragment

分别执行onPause()、onStop()、onDestroyView()方法。

从其他Fragment回到之前Fragment

分别执行onCreateView()、onActivityCreated()、onstart()、onResume()方法。

8.Service生命周期?


service 启动方式有两种

一种是通过startService()方式进行启动

另一种是通过bindService()方式进行启动。

不同的启动方式他们的生命周期是不一样.

通过startService()这种方式启动的service

生命周期是这样:

调用startService() --> onCreate()–> onStartConmon()–> onDestroy()。

这种方式启动的话,需要注意一下几个问题

第一:当我们通过startService被调用以后,多次在调用startService(),onCreate()方法也只会被调用一次,而onStartConmon()会被多次调用,当我们调用stopService()的时候,onDestroy()就会被调用,从而销毁服务。

第二:当我们通过startService启动时候,通过intent传值,在onStartConmon()方法中获取值的时候,一定要先判断intent是否为null。

通过bindService()方式进行绑定,这种方式绑定service

生命周期:

bindService–>onCreate()–>onBind()–>unBind()–>onDestroy()

bindservice 这种方式进行启动service好处是更加便利activity中操作service,如果要在activity中调用,在需要在activity获取ServiceConnection对象,通过ServiceConnection来获取service中内部类的类对象,然后通过这个类对象就可以调用类中的方法,当然这个类需要继承Binder对象

9.service和activity怎么进行数据交互?


1.通过 broadcast:通过广播发送消息到 activitry

2.通过 Binder:通过与 activity 进行绑定

(1)添加一个继承 Binder 的内部类,并添加相应的逻辑方法。

(2)重写 Service 的 onBind 方法,返回我们刚刚定义的那个内部类实例。

(3)Activity 中创建一个 ServiceConnection 的匿名内部类,并且 重 写 里 面 的 onServiceConnected 方 法 和onServiceDisconnected 方法,这两个方法分别会在活动与服务成功绑定以及解除绑定的时候调用(在onServiceConnected方法中,我们可以得到一个刚才那个 service 的 binder 对象,通过对这个 binder 对象进行向下转型,得到我们那个自定义的 Binder 实例,有了这个实例,做可以调用这个实例里面的具体方法进行需要的操作了)。

10.Android启动Service的两种方式是什么? 它们的适用情况是什么?


如果后台服务开始后基本可以独立运行的话,可以用startService。音乐播放器就可以这样用。它们会一直运行直到你调用 stopSelf或者stopService。你可以通过发送Intent或者接收Intent来与正在运行的后台服务通信,但大部分时间,你只是启动服务并让它独立运行。如果你需要与后台服务通过一个持续的连接来比较频繁地通信,建议使用bind()。比如你需要定位服务不停地把更新后的地理位置传给UI。Binder比Intent开发起来复杂一些,但如果真的需要,你也只能使用它。

startService:

生命周期与调用者不同。启动后若调用者未调用stopService而直接退出,Service仍会运行。

bindService:

生命周期与调用者绑定,调用者一旦退出,Service就会调用unBind->onDestroy

11.Broadcast注册方式与区别


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

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

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

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

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

11.讲解一下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)

12.Service的onCreate回调在UI线程中吗?


Service生命周期的各个回调和其他的应用组件一样,是跑在主线程中,会影响

到你的UI操作或者阻塞主线程中的其他事情

13.请介绍下AsyncTask的内部实现,适用的场景是?


AsyncTask内部也是Handler机制来完成的,只不过Android提供了执行框架来提供线程池来执行相应地任务,因为线程池的大小问题,所以AsyncTask只应该用来执行耗时时间较短的任务,比如HTTP请求,大规模的下载和数据库的更改不适用于AsyncTask,因为会导致线程池堵塞,没有线程来执行其他的任务,导致的情形是会发生,AsyncTask根本执行不了的问题。

由于篇幅有限,只能分享部分面试题,更多面试题及答案可以我的【Github】阅读下载哦~无偿分享给大家,算是一个感恩回馈吧

中高级Android大厂面试秘籍,为你保驾护航金三银四,直通大厂(上

14.说下你所知道的设计模式与使用场景


建造者模式:

观察者模式:

代理模式:

门面模式:

单例模式:

生产者消费者模式:

15.Java语言的特点与OOP思想


这个通过对比来描述,比如面向对象和面向过程的对比,针对这两种思想的对比,还可以举个开发中的例子,比如播放器的实现,面向过程的实现方式就是将播放视频的这个功能分解成多个过程,比如,加载视频地址,获取视频信息,初始化解码器,选择合适的解码器进行解码,读取解码后的帧进行视频格式转换和音频重采样,然后读取帧进行播放,这是一个完整的过程,这个过程中不涉及类的概念,而面向对象最大的特点就是类,封装继承和多态是核心,同样的以播放器为例,一面向对象的方式来实现,将会针对每一个功能封装出一个对象,每一个功能对应一个对象,由这个对象来完成对应的功能,并且遵循单一职责原则,一个对象只做它相关的事情

##16.说下java中的线程创建方式,线程池的工作原理。

Java中有三种创建线程的方式,或者说四种

1.继承Thread类实现多线程

2.实现Runnable接口

3.实现Callable接口

4.通过线程池

17.线程池的工作原理:


线程池可以减少创建和销毁线程的次数,从而减少系统资源的消耗,当一个任务提交到线程池时.

a. 首先判断核心线程池中的线程是否已经满了,如果没满,则创建一个核心线程执行任务,否则进入下一步.

b. 判断工作队列是否已满,没有满则加入工作队列,否则执行下一步.

c. 判断线程数是否达到了最大值,如果不是,则创建非核心线程执行任务,否则执行饱和策略,默认抛出异常.

18.说下handler原理


Handler,Message,looper和MessageQueue构成了安卓的消息机制,handler创建后可以通过sendMessage将消息加入消息队列,然后looper不断的将消息从MessageQueue中取出来,回调到Hander的handleMessage方法,从而实现线程的通信。

从两种情况来说,第一在UI线程创建Handler,此时我们不需要手动开启looper,因为在应用启动时,在ActivityThread的main方法中就创建了一个当前主线程的looper,并开启了消息队列,消息队列是一个无限循环,为什么无限循环不会ANR?因为可以说,应用的整个生命周期就是运行在这个消息循环中的,安卓是由事件驱动的,Looper.loop不断的接收处理事件,每一个点击触摸或者Activity每一个生命周期都是在Looper.loop的控制之下的,looper.loop一旦结束,应用程序的生命周期也就结束了。

我们可以想想什么情况下会发生ANR?

第一,事件没有得到处理

第二,事件正在处理,但是没有及时完成,而对事件进行处理的就是looper,所以只能说事件的处理如果阻塞会导致ANR,而不能说looper的无限循环会ANR

另一种情况就是在子线程创建Handler,此时由于这个线程中没有默认开启的消息队列,所以我们需要手动调用looper.prepare(),并通过looper.loop开启消息

主线程Looper从消息队列读取消息,当读完所有消息时,主线程阻塞。子线程往消息队列发送消息,并且往管道文件写数据,主线程即被唤醒,从管道文件读取数据,主线程被唤醒只是为了读取消息,当消息读取完毕,再次睡眠。因此loop的循环并不会对CPU性能有过多的消耗。

19.内存泄漏的场景和解决办法


1.非静态内部类的静态实例

非静态内部类会持有外部类的引用,如果非静态内部类的实例是静态的,就会长期的维持着外部类的引用,组织被系统回收,解决办法是使用静态内部类

2.多线程相关的匿名内部类和非静态内部类

匿名内部类同样会持有外部类的引用,如果在线程中执行耗时操作就有可能发生内存泄漏,导致外部类无法被回收,直到耗时任务结束,解决办法是在页面退出时结束线程中的任务

3.Handler内存泄漏

Handler导致的内存泄漏也可以被归纳为非静态内部类导致的,Handler内部message是被存储在MessageQueue中的,有些message不能马上被处理,存在的时间会很长,导致handler无法被回收,如果handler是非静态的,就会导致它的外部类无法被回收,

解决办法是

1.使用静态handler,外部类引用使用弱引用处理

2.在退出页面时移除消息队列中的消息

4.Context导致内存泄漏

根据场景确定使用Activity的Context还是Application的Context,因为二者生命周期不同,对于不必须使用Activity的Context的场景(Dialog),一律采用Application的Context,单例模式是最常见的发生此泄漏的场景,比如传入一个Activity的Context被静态类引用,导致无法回收

5.静态View导致泄漏

使用静态View可以避免每次启动Activity都去读取并渲染View,但是静态View会持有Activity的引用,导致无法回收

解决办法是在Activity销毁的时候将静态View设置为null(View一旦被加载到界面中将会持有一个Context对象的引用,在这个例子中,这个context对象是我们的Activity,声明一个静态变量引用这个View,也就引用了activity)

6.WebView导致的内存泄漏

WebView只要使用一次,内存就不会被释放,所以WebView都存在内存泄漏的问题,通常的解决办法是为WebView单开一个进程,使用AIDL进行通信,根据业务需求在合适的时机释放掉

7.资源对象未关闭导致

如Cursor,File等,内部往往都使用了缓冲,会造成内存泄漏,一定要确保关闭它并将引用置为null

8.集合中的对象未清理

集合用于保存对象,如果集合越来越大,不进行合理的清理,尤其是入股集合是静态的

9.Bitmap导致内存泄漏

bitmap是比较占内存的,所以一定要在不使用的时候及时进行清理,避免静态变量持有大的bitmap对象

10.监听器未关闭

很多需要register和unregister的系统服务要在合适的时候进行unregister,手动添加的listener也需要及时移除

20.如何避免OOM?


《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》

【docs.qq.com/doc/DSkNLaERkbnFoS0ZF】 完整内容开源分享


1.使用更加轻量的数据结构:

如使用ArrayMap/SparseArray替代HashMap,HashMap更耗内存,因为它需要额外的实例对象来记录Mapping操作,SparseArray更加高效,因为它避免了Key Value的自动装箱,和装箱后的解箱操作

2.枚举的使用

可以用静态常量或者注解@IntDef替代

3.Bitmap优化:

a.尺寸压缩:

通过InSampleSize设置合适的缩放

b.颜色质量:

设置合适的format,ARGB_6666/RBG_545/ARGB_4444/ALPHA_6,存在很大差异.

c.inBitmap:

使用inBitmap属性可以告知Bitmap解码器去尝试使用已经存在的内存区域,新解码的Bitmap会尝试去使用之前那张Bitmap在Heap中所占据的pixel data内存区域,而不是去问内存重新申请一块区域来存放Bitmap。利用这种特性,即使是上千张的图片,也只会仅仅只需要占用屏幕所能够显示的图片数量的内存大小,但复用存在一些限制,具体体现在:在Android 4.4之前只能重用相同大小的Bitmap的内存,而Android 4.4及以后版本则只要后来的Bitmap比之前的小即可。使用inBitmap参数前,每创建一个Bitmap对象都会分配一块内存供其使用,而使用了inBitmap参数后,多个Bitmap可以复用一块内存,这样可以提高性能

d.StringBuilder替代String: 在有些时候,代码中会需要使用到大量的字符串拼接的操作,这种时候有必要考虑使用StringBuilder来替代频繁的“+”

e.避免在类似onDraw这样的方法中创建对象,因为它会迅速占用大量内存,引起频繁的GC`甚至内存抖动

f.减少内存泄漏也是一种避免OOM的方法

21.onRestart的调用场景


(1)按下home键之后,然后切换回来,会调用onRestart()。

(2)从本Activity跳转到另一个Activity之后,按back键返回原来Activity,会调用onRestart();

(3)从本Activity切换到其他的应用,然后再从其他应用切换回来,会调用onRestart();

22.如何实现进程保活


a: Service设置成START_STICKY kill后会被重启(等待5秒左右),重传Intent,保持与重启前一样

b: 通过 startForeground将进程设置为前台进程, 做前台服务,优先级和前台应用一个级别,除非在系统内存非常缺,否则此进程不会被kill

c: 双进程Service: 让2个进程互相保护对方,其中一个Service被清理后,另外没被清理的进程可以立即重启进程

d: 用C编写守护进程(即子进程) : Android系统中当前进程(Process)fork出来的子进程,被系统认为是两个不同的进程。当父进程被杀死的时候,子进程仍然可以存活,并不受影响(Android5.0以上的版本不可行)联系厂商,加入白名单

e.锁屏状态下,开启一个一像素Activity

23.说下冷启动与热启动是什么,区别,如何优化,使用场景等。


app冷启动:

当应用启动时,后台没有该应用的进程,这时系统会重新创建一个新的进程分配给该应用, 这个启动方式就叫做冷启动(后台不存在该应用进程)。冷启动因为系统会重新创建一个新的进程分配给它,所以会先创建和初始化Application类,再创建和初始化MainActivity类(包括一系列的测量、布局、绘制),最后显示在界面上。

app热启动:

当应用已经被打开, 但是被按下返回键、Home键等按键时回到桌面或者是其他程序的时候,再重新打开该app时, 这个方式叫做热启动(后台已经存在该应用进程)。

热启动因为会从已有的进程中来启动,所以热启动就不会走Application这步了,而是直接走MainActivity(包括一系列的测量、布局、绘制),所以热启动的过程只需要创建和初始化一个MainActivity就行了,而不必创建和初始化Application

冷启动的流程

当点击app的启动图标时,安卓系统会从Zygote进程中fork创建出一个新的进程分配给该应用,之后会依次创建和初始化Application类、创建MainActivity类、加载主题样式Theme中的windowBackground等属性设置给MainActivity以及配置Activity层级上的一些属性、再inflate布局、当onCreate/onStart/onResume方法都走完了后最后才进行contentView的measure/layout/draw显示在界面上

冷启动的生命周期简要流程:

Application构造方法 –>attachBaseContext()–>onCreate –>Activity构造方法 –>onCreate() –> 配置主体中的背景等操作 –>onStart()–> onResume()–> 测量、布局、绘制显示

冷启动的优化主要是视觉上的优化,解决白屏问题,提高用户体验,所以通过上面冷启动的过程。能做的优化如下:

1、减少onCreate()方法的工作量

2、不要让Application参与业务的操作

3、不要在Application进行耗时操作

4、不要以静态变量的方式在Application保存数据

5、减少布局的复杂度和层级

6、减少主线程耗时

24.为什么冷启动会有白屏黑屏问题?


原因在于加载主题样式Theme中的windowBackground等属性设置给MainActivity发生在inflate布局当onCreate/onStart/onResume方法之前,而windowBackground背景被设置成了白色或者黑色,所以我们进入app的第一个界面的时候会造成先白屏或黑屏一下再进入界面。解决思路如下

1.给他设置windowBackground背景跟启动页的背景相同,如果你的启动页是张图片那么可以直接给windowBackground这个属性设置该图片那么就不会有一闪的效果了

上一篇:Android—简单路由框架实践


下一篇:Android学习的三个终极问题及学习路线规划,真香定律