安卓面试题

安卓面试题

  • Android篇
    • 1、Activity启动模式
    • 2、RxJava、Retrofit使用
    • 3、MVC/MVP/MVVM
    • 4、图片加载框架
    • 5、网络框架
    • 6、View、ViewGroup事件分发机制
    • 7、理解Activity,View,Window三者关系
    • 8、Hander原理
    • 9、Handler造成内存泄漏的原因和解决方案(AnycTask、Thread、Runable同理)
    • 10、Service生命周期
    • 11、安卓动画
    • 12、View的绘制流程
    • 13、自定义View流程
    • 14、多线程、线程间通信
    • 15、andorid数据库使用(SQLite、Room、GreenDao?)
    • 16、设计模式
    • 17、Android线程池
    • 18、Android中性能优化
    • 19、内存泄漏相关
    • 20、android hook技术
  • Java篇
    • 1、ArrayList和LinkedList的区别,以及应用场景
    • 2、数组和链表的区别
    • 3、Java中堆和栈有什么不同?
    • 4、Java中实现多态的机制
    • 5、synchronized 和volatile关键字的区别
    • 6、强引用、软引用、弱引用、虚引用
    • 7、对Java反射的理解
    • 8、哪些情况下的对象会被垃圾回收机制处理掉?
    • 9、开启线程的三种方式?

Android篇

1、Activity启动模式

启动模式可在AndroidManifest.xml中,通过标签的android:launchMode属性设置

standard
特点:1.Activity的默认启动模式
2.每启动一个Activity就会在栈顶创建一个新的实例。例如:闹钟程序
缺点:当Activity已经位于栈顶时,而再次启动Activity时还需要在创建一个新的实例,不能直接复用。

singleTop
特点:该模式会判断要启动的Activity实例是否位于栈顶,如果位于栈顶直接复用,否则创建新的实例。 例如:浏览器的书签
缺点:如果Activity并未处于栈顶位置,则可能还会创建多个实例。

singleTask
特点:使Activity在整个应用程序中只有一个实例。每次启动Activity时系统首先检查栈中是否存在当前Activity实例,如果存在则直接复用,并把当前Activity之上所有实例全部出栈。例如:浏览器主界面

singleInstance
特点:该模式的Activity会启动一个新的任务栈来管理Activity实例,并且该实例在整个系统中只有一个。无论从那个任务栈中启动该Activity,都会是该Activity所在的任务栈转移到前台,从而使Activity显示。主要作用是为了在不同程序中共享一个Activity实例。

2、RxJava、Retrofit使用

RxJava

Retrofit :

简介
Retrofit 是基于OKhttp网络请求框架的二次封装,本质是OKhttp。所以说Retrofit并不是一个网络框架、它只是一个网络框架封装。
Android AsyncHttp 基于HttpClient ,已经停止维护,Android5.0之后不再使用HttpClient,不推荐应用。
Volley 是google推出的基于HttpUrlConnection 的适合轻量级数据传输的网路库,不适合大文件的上传和下载。
Retrofit优点:API设计简洁易用、注解化配置高度解耦、支持多种解析器、支持Rxjava。

使用
1、Retrofit开源库、OkHttp网络库、数据解析器集成、注册网络权限;

2、创建接口设置请求类型与参数:
新建UserInfoModel类和UserMgrService接口
@GET(“login”)
public Call login(@Query(“username”) String username, @Query(“pwd”) String pwd);

3、创建Retrofit对象、设置数据解析器
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(Api.API_BASE)
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create())
.build();

Gson com.squareup.retrofit2:converter-gson:2.0.2
Jackson con.squareup.retrofit2:converter-jackson:2.0.2
Simple XML com.squareup.retrofit2:converter-simplexml:2.0
Protobuf com.squareup.retrofit2:converter-protobuf:2.0.2
Moshi com.squareup.retrofit2:converter-moshi:2.0.2
Wire com.squareup.retrofit2:converter-wire:2.0.2
Scalars com.squareup.retrofit2:converter-scalars:2.0.2
4、生成接口对象:
UserMgrService service = retrofit.create(UserMgrService.class);

5、调用接口方法返回Call对象:
Call call = service.login(“zhangsan”,“123456”);

6、发送请求(同步、异步)
同步:调用Call对象的execute(),返回结果的响应体;
异步:调用Call对象的enqueue(),参数是一个回调;

7、处理返回数据

Retrofit2完全教程

3、MVC/MVP/MVVM

MVC:View是可以直接访问Model的!从而,View里会包含Model信息,不可避免的还要包括一些 业务逻辑。 在MVC模型里,更关注的Model的不变,而同时有多个对Model的不同显示,及View。所以,在MVC模型里,Model不依赖于View,但是 View是依赖于Model的。不仅如此,因为有一些业务逻辑在View里实现了,导致要更改View也是比较困难的,至少那些业务逻辑是无法重用的。
MV:MVP 是从经典的模式MVC演变而来,它们的基本思想有相通的地方:Controller/Presenter负责逻辑的处理,Model提供数据,View负 责显示。作为一种新的模式,MVP与MVC有着一个重大的区别:在MVP中View并不直接使用Model,它们之间的通信是通过Presenter (MVC中的Controller)来进行的,所有的交互都发生在Presenter内部,而在MVC中View会从直接Model中读取数据而不是通过 Controller。
MVVM:数据双向绑定,通过数据驱动UI,M提供数据,V视图,VM即数据驱动层

4、图片加载框架

ImageLoader :
优点:
① 支持下载进度监听;
② 可以在 View 滚动中暂停图片加载;
③ 默认实现多种内存缓存算法这几个图片缓存都可以配置缓存算法,不过 ImageLoader 默认实现了较多缓存算法,如 Size 最大先删除、使用最少先删除、最近最少使用、先进先删除、时间最长先删除等;
④ 支持本地缓存文件名规则定义;
缺点:
缺点在于不支持GIF图片加载, 缓存机制没有和http的缓存很好的结合, 完全是自己的一套缓存机制

Picasso :
优点:
① 自带统计监控功能,支持图片缓存使用的监控,包括缓存命中率、已使用内存大小、节省的流量等。
② 支持优先级处理
③ 支持延迟到图片尺寸计算完成加载
④ 支持飞行模式、并发线程数根据网络类型而变,手机切换到飞行模式或网络类型变换时会自动调整线程池最大并发数。
⑤ “无”本地缓存。Picasso 自己没有实现本地缓存,而由okhttp 去实现,这样的好处是可以通过请求 Response Header 中的 Cache-Control 及 Expired 控制图片的过期时间。
缺点:
于不支持GIF,默认使用ARGB_8888格式缓存图片,缓存体积大。

Glide :
优点:
① 图片缓存->媒体缓存 ,支持 Gif、WebP、缩略图。甚至是 Video。
② 支持优先级处理
③ 与 Activity/Fragment 生命周期一致,支持 trimMemory
④ 支持 okhttp、Volley。Glide 默认通过 UrlConnection 获取数据,可以配合 okhttp 或是 Volley 使用。实际 ImageLoader、Picasso 也都支持 okhttp、Volley。
⑤ 内存友好,内存缓存更小图片,图片默认使用默认 RGB565 而不是 ARGB888
缺点:
清晰度差,但可以设置

Fresco :
优点:
① 图片存储在安卓系统的匿名共享内存, 而不是虚拟机的堆内存中,所以不会因为图片加载而导致oom, 同时也减少垃圾回收器频繁调用回收Bitmap导致的界面卡顿,性能更高.
② 渐进式加载JPEG图片, 支持图片从模糊到清晰加载
③ 图片可以以任意的中心点显示在ImageView, 而不仅仅是图片的中心.
④ JPEG图片改变大小也是在native进行的, 不是在虚拟机的堆内存, 同样减少OOM
⑤ 很好的支持GIF图片的显示
缺点:
框架较大, 影响Apk体积,使用较繁琐

安卓面试题之图片加载框架

5、网络框架

Xutils :
这个框架非常全面,可以进行网络请求,可以进行图片加载处理,可以数据储存,还可以对view进行注解,使用这个框架非常方便,但是缺点也是非常明显的,使用这个项目,会导致项目对这个框架依赖非常的严重,一旦这个框架出现问题,那么对项目来说影响非常大的
OKhttp:
Android开发中是可以直接使用现成的api进行网络请求的。就是使用HttpClient,HttpUrlConnection进行操作。okhttp针对Java和Android程序,封装的一个高性能的http请求库,支持同步,异步,而且okhttp又封装了线程池,封装了数据转换,封装了参数的使用,错误处理等。API使用起来更加的方便。但是我们在项目中使用的时候仍然需要自己在做一层封装,这样才能使用的更加的顺手。
Volley:
Volley是Google官方出的一套小而巧的异步请求库,该框架封装的扩展性很强,支持HttpClient、HttpUrlConnection, 甚至支持OkHttp,而且Volley里面也封装了ImageLoader,所以如果你愿意你甚至不需要使用图片加载框架,不过这块功能没有一些专门的图片加载框架强大,对于简单的需求可以使用,稍复杂点的需求还是需要用到专门的图片加载框架。Volley也有缺陷,比如不支持post大数据,所以不适合上传文件。不过Volley设计的初衷本身也就是为频繁的、数据量小的网络请求而生。
Retrofit:
Retrofit是Square公司出品的默认基于OkHttp封装的一套RESTful网络请求框架,RESTful是目前流行的一套api设计的风格, 并不是标准。Retrofit的封装可以说是很强大,里面涉及到一堆的设计模式,可以通过注解直接配置请求,可以使用不同的http客户端,虽然默认是用http ,可以使用不同Json Converter 来序列化数据,同时提供对RxJava的支持,使用Retrofit + OkHttp + RxJava + Dagger2 可以说是目前比较潮的一套框架,但是需要有比较高的门槛。
Volley VS OkHttp:
Volley的优势在于封装的更好,而使用OkHttp你需要有足够的能力再进行一次封装。而OkHttp的优势在于性能更高,因为 OkHttp基于NIO和Okio ,所以性能上要比 Volley更快。IO 和 NIO这两个都是Java中的概念,如果我从硬盘读取数据,第一种方式就是程序一直等,数据读完后才能继续操作这种是最简单的也叫阻塞式IO,还有一种是你读你的,程序接着往下执行,等数据处理完你再来通知我,然后再处理回调。而第二种就是 NIO 的方式,非阻塞式, 所以NIO当然要比IO的性能要好了,而 Okio是 Square 公司基于IO和NIO基础上做的一个更简单、高效处理数据流的一个库。理论上如果Volley和OkHttp对比的话,更倾向于使用 Volley,因为Volley内部同样支持使用OkHttp,这点OkHttp的性能优势就没了, 而且 Volley 本身封装的也更易用,扩展性更好些。
OkHttp VS Retrofit:
毫无疑问,Retrofit 默认是基于 OkHttp 而做的封装,这点来说没有可比性,肯定首选 Retrofit。
Volley VS Retrofit:
这两个库都做了不错的封装,但Retrofit解耦的更彻底,尤其Retrofit2.0出来,Jake对之前1.0设计不合理的地方做了大量重构, 职责更细分,而且Retrofit默认使用OkHttp,性能上也要比Volley占优势,再有如果你的项目如果采用了RxJava ,那更该使用 Retrofit 。所以这两个库相比,Retrofit更有优势,在能掌握两个框架的前提下该优先使用 Retrofit。但是Retrofit门槛要比Volley稍高些,要理解他的原理,各种用法,想彻底搞明白还是需要花些功夫的,如果你对它一知半解,那还是建议在商业项目使用Volley吧。

6、View、ViewGroup事件分发机制

  • 1.Touch事件分发中只有两个主角:ViewGroup和View。ViewGroup包含onInterceptTouchEvent、dispatchTouchEvent、onTouchEvent三个相关事件。View包含dispatchTouchEvent、onTouchEvent两个相关事件。其中ViewGroup又继承于View。
  • 2.ViewGroup和View组成了一个树状结构,根节点为Activity内部包含的一个ViwGroup。
  • 3.触摸事件由Action_Down、Action_Move、Aciton_UP组成,其中一次完整的触摸事件中,Down和Up都只有一个,Move有若干个,可以为0个。
  • 4.当Acitivty接收到Touch事件时,将遍历子View进行Down事件的分发。ViewGroup的遍历可以看成是递归的。分发的目的是为了找到真正要处理本次完整触摸事件的View,这个View会在onTouchuEvent结果返回true。
  • 5.当某个子View返回true时,会中止Down事件的分发,同时在ViewGroup中记录该子View。接下去的Move和Up事件将由该子View直接进行处理。由于子View是保存在ViewGroup中的,多层ViewGroup的节点结构时,上级ViewGroup保存的会是真实处理事件的View所在的ViewGroup对象:如ViewGroup0-ViewGroup1-TextView的结构中,TextView返回了true,它将被保存在ViewGroup1中,而ViewGroup1也会返回true,被保存在ViewGroup0中。当Move和UP事件来时,会先从ViewGroup0传递至ViewGroup1,再由ViewGroup1传递至TextView。
  • 6.当ViewGroup中所有子View都不捕获Down事件时,将触发ViewGroup自身的onTouch事件。触发的方式是调用super.dispatchTouchEvent函数,即父类View的dispatchTouchEvent方法。在所有子View都不处理的情况下,触发Acitivity的onTouchEvent方法。
  • 7.onInterceptTouchEvent有两个作用:1.拦截Down事件的分发。2.中止Up和Move事件向目标View传递,使得目标View所在的ViewGroup捕获Up和Move事件。

Android onTouch事件传递机制

7、理解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等。

8、Hander原理

Android中主线程是不能进行耗时操作的,子线程是不能进行更新UI的。所以就有了handler,它的作用就是实现线程之间的通信。
handler整个流程中,主要有四个对象,handler,Message,MessageQueue,Looper。当应用创建的时候,就会在主线程中创建handler对象,
我们通过要传送的消息保存到Message中,handler通过调用sendMessage方法将Message发送到MessageQueue中,Looper对象就会不断的调用loop()方法
不断的从MessageQueue中取出Message交给handler进行处理。从而实现线程之间的通信。

Android 之 Handler 机制

9、Handler造成内存泄漏的原因和解决方案(AnycTask、Thread、Runable同理)

Handler造成内存泄漏的原因:

  • 当使用非静态内部类(包括匿名类)来创建Handler的时候,Handler对象会隐式地持有一个外部类对象(通常是一个Activity)的引用(不然你怎么可能通过Handler来操作Activity中的View?)。而Handler通常会伴随着一个耗时的后台线程(例如从网络拉取图片)一起出现,这个后台线程在任务执行完毕(例如图片下载完毕)之后,通过消息机制通知Handler,然后Handler把图片更新到界面。然而,如果用户在网络请求过程中关闭了Activity,正常情况下,Activity不再被使用,它就有可能在GC检查时被回收掉,但由于这时线程尚未执行完,而该线程持有Handler的引用(不然它怎么发消息给Handler?),这个Handler又持有Activity的引用,就导致该Activity无法被回收(即内存泄露),直到网络请求结束(例如图片下载完毕)。
  • 如果你执行了Handler的postDelayed()方法,该方法会将你的Handler装入一个Message,并把这条Message推到MessageQueue中,那么在你设定的delay到达之前,会有一条MessageQueue -> Message -> Handler -> Activity的链,导致你的Activity被持有引用而无法被回收

对于Android应用来说,就是你的用户打开一个Activity,使用完之后关闭它,内存泄露;又打开,又关闭,又泄露;几次之后,程序占用内存超过系统限制,FC。

另外,如果一组对象中只包含互相的引用,而没有来自它们外部的引用(例如有两个对象A和B互相持有引用,但没有任何外部对象持有指向A或B的引用),这仍然属于不可到达,同样会被GC回收。
解决方案:

  • 方法一:通过程序逻辑来进行保护。

    • 在关闭Activity的时候停掉你的后台线程。线程停掉了,就相当于切断了Handler和外部连接的线,Activity自然会在合适的时候被回收。
    • 如果你的Handler是被delay的Message持有了引用,那么使用相应的Handler的removeCallbacks()方法,把消息对象从消息队列移除就行了。
  • 方法二:将Handler声明为静态类。
    PS:在Java 中,非静态的内部类和匿名内部类都会隐式地持有其外部类的引用,静态的内部类不会持有外部类的引用。
    静态类不持有外部类的对象,所以你的Activity可以随意被回收。由于Handler不再持有外部类对象的引用,导致程序不允许你在Handler中操作Activity中的对象了。所以你需要在Handler中增加一个对Activity的弱引用(WeakReference)。

static class MyHandler extends Handler
    {
        WeakReference mWeakReference;
        public MyHandler(Activity activity) 
        {
            mWeakReference=new WeakReference(activity);
        }
        @Override
        public void handleMessage(Message msg)
        {
            final Activity activity=mWeakReference.get();
            if(activity!=null)
            {
                if (msg.what == 1)
                {
                    noteBookAdapter.notifyDataSetChanged();
                }
            }
        }
    }

PS:什么是WeakReference?

  WeakReference弱引用,与强引用(即我们常说的引用)相对,它的特点是,GC在回收时会忽略掉弱引用,即就算有弱引用指向某对象,但只要该对象没有被强引用指向(实际上多数时候还要求没有软引用,但此处软引用的概念可以忽略),该对象就会在被GC检查到时回收掉。对于上面的代码,用户在关闭Activity之后,就算后台线程还没结束,但由于仅有一条来自Handler的弱引用指向Activity,所以GC仍然会在检查的时候把Activity回收掉。这样,内存泄露的问题就不会出现了。

总结:使用非静态内部类(匿名内部类)时,都会隐式地持有其外部类的引用(比如:Activity),如果此时内部类的生命周期比引用的外部类的生命周期长时(比如内部类开启子线程执行耗时任务),就会造成内存泄漏;
最常见的如Handler、Thread、AsyncTask、EventBus、RxAndroid

10、Service生命周期

安卓面试题_第1张图片

  • 被启动的服务的生命周期:如果一个Service被某个Activity 调用 Context.startService 方法启动,那么不管是否有Activity使用bindService绑定或unbindService解除绑定到该Service,该Service都在后台运行。如果一个Service被startService 方法多次启动,那么onCreate方法只会调用一次,onStart将会被调用多次(对应调用startService的次数),并且系统只会创建Service的一个实例(因此你应该知道只需要一次stopService调用)。该Service将会一直在后台运行,而不管对应程序的Activity是否在运行,直到被调用stopService,或自身的stopSelf方法。当然如果系统资源不足,android系统也可能结束服务。

  • 被绑定的服务的生命周期:如果一个Service被某个Activity 调用 Context.bindService 方法绑定启动,不管调用 bindService 调用几次,onCreate方法都只会调用一次,同时onStart方法始终不会被调用。当连接建立之后,Service将会一直运行,除非调用Context.unbindService 断开连接或者之前调用bindService 的 Context 不存在了(如Activity被finish的时候),系统将会自动停止Service,对应onDestroy将被调用。

  • 被启动又被绑定的服务的生命周期:如果一个Service又被启动又被绑定,则该Service将会一直在后台运行。并且不管如何调用,onCreate始终只会调用一次,对应startService调用多少次,Service的onStart便会调用多少次。调用unbindService将不会停止Service,而必须调用 stopService 或 Service的 stopSelf 来停止服务。

  • 当服务被停止时清除服务:当一个Service被终止(1、调用stopService;2、调用stopSelf;3、不再有绑定的连接(没有被启动))时,onDestroy方法将会被调用,在这里你应当做一些清除工作,如停止在Service中创建并运行的线程。

生命周期方法简单介绍

  • startService()
    作用:启动Service服务
    手动调用startService()后,自动调用内部方法:onCreate()、onStartCommand()
    如果一个service被startService多次启动,onCreate()只会调用一次
    onStartCommand()调用次数=startService()次数

  • stopService()
    作用:关闭Service服务
    手动调用stopService()后,自动调用内部方法:onDestory()
    如果一个service被启动且被绑定,如果没有在绑定的前提下stopService()是无法停止服务的。

  • bindService()
    作用:绑定Service服务
    手动调用bindService()后,自动调用内部方法:onCreate()、onBind()

  • unbindService()
    作用:解绑Service服务
    手动调用unbindService()后,自动调用内部方法:onCreate()、onBind()、onDestory()

注意:

startService()和stopService()只能开启和关闭Service,无法操作Service;

bindService()和unbindService()可以操作Service

startService开启的Service,调用者退出后Service仍然存在;

BindService开启的Service,调用者退出后,Service随着调用者销毁。

11、安卓动画

总的来说,Android动画可以分为两类,最初的传统动画和Android3.0 之后出现的属性动画;
传统动画又包括 帧动画(Frame Animation)和补间动画(Tweened Animation)

1、Tween Animation:

  • 补间动画(视图动画)特性
    • 渐变动画支持4种类型:平移(Translate)、旋转(Rotate)、缩放(Scale)、不透明(Alpha)
    • 只是显示的位置变动,View的实际位置未改变,表现为View移动到其他地方,点击事件仍在原处才可响应。
    • 组合使用步骤较复杂。
    • View Animation也是指此动画。
    • 可以结合插值器(Interpolator)完整复杂动画,Interpolator 主要作用是可以控制动画的变化速率 ,就是动画进行的快慢节奏。

通过Animation类和AnimationUtils配合实现的。动画效果可以预先配置在res/anim目录下的xml文件中。

  • 补间动画优缺点
    • 缺点:当平移动画执行完停止最后的位置,结果焦点还在原来的位置(控件属性未改变)。
    • 优点:相较于帧动画,补间动画更为连贯自然。

2、Frame Animation:

这种动画(也叫Frame动画、帧动画)其实可以划分到视图动画的类别,专门用来一个一个的显示Drawable的resources,就像放幻灯片一样,

  • 帧动画特性
    • 用于生成连续的GIF动画。
    • Drawable Animation也是指此动画。
  • 帧动画优缺点
    • 缺点:效果单一,逐帧播放需要很多图片,占用空间较大
    • 优点:制作简单。

3、Property Animation:

  • 属性动画特性
    • 支持对所有View能更新的属性的动画(需要属性的set/get方法)。
    • 更改的是View的实际属性,不影响其动画执行后所在位置的正常使用。
    • Android 3.0(API 11)及以后的功能。
  • 属性动画的优缺点
    • 缺点:向下兼容的问题。
    • 优点:易定制,效果强。

4、视频动画

由UI设计师提供MP4视频,通过Android的VideoView播放完成的动画效果;

5、Lottie动画

由UI设计师通过AE的Lottie插件生成json数据并提供给Android或IOS开发者,由开发者集成完成的动画;

12、View的绘制流程

Android自定义view,我们都知道实现有三部曲:onMeasure()onLayout()onDraw()

View的绘制流程是从viewRoot的perfromTraversal方法开始的。它经过measure,layout,draw方法才能够将view绘制出来。其中 measure是测量宽高的,layout是确定view在父容器上的摆布位置的,draw是将view绘制到屏幕上的。

Measure:
view的测量是需要MeasureSpc(测量规格),它代表一个32位int值,高2位代表SpecMode(测量模式),低(30)位的代表SpecSize(某种测量模式下的规格大小)。而一组SpecMode和SpeSize可以打包为一个MeasureSpec,反之,MeasureSpec可以解包得到SpecMode和SpeSize的值。SpecMode有三类:

  • unSpecified:父容器不对view有任何限制,要多大有多大。一般系统用这个多。

  • Exactly:父容器已经检测出view所需要的精确大小,这个时候,view的大小就是SpecSize所指定的值,它对应着layout布局中的math_parent或者是具体的数值

  • At_most:父容器指定了一个可变大小的SpecSize,view的大小不能够大于这个值,它对应这布局中的wrap_content.
    对于普通的view,它的MeasureSpec是由父容器的MeasureSpec和自身的layoutParam共同决定的,一旦MeasureSpec确定后,onMeasure就可以确定view的宽高了。

View的measure过程:

onMeasure方法中有个setMeasureDimenSion方法来设置view的宽高测量值,而setMeasureDimenSion有个getDefaultSize()方法作为参数。一般情况下,我们只需要关注at_most和exactly两种情况,getDefaultSize的返回值就是measureSpec中的SpecSize,而这个值基本就是view测量后的大小。而UnSpecified这种情况,一般是系统内部的测量过程,它是需要考虑view的背景这些因素的。

前面说的是view的测量过程,而viewGroup的measure过程:

对于viewGroup来说,除了完成自己的measure过程以外,还要遍历去调用子类的measure方法,各个子元素在递归执行这个过程,viewGroup是一个抽象的类,没有提供有onMeasure方法,但是提供了一个measureChildren的方法。measureChild方法的思想就是取出子元素的layoutParams,然后通过getChildMeasureSpec来常见子元素的MeasureSpec,然后子元素在measure方法进行测量。由于viewGroup子类有不同的布局方式,导致他们的测量细节不一样,所以viewGroup不能象view一样调用onMeasure方法进行测量。

注意:在activity的生命周期中是没有办法正确的获取view的宽高的,原因就是view没有测量完。

1、在onWindowFocuschanged方法中获取 ----该方法含义是view已经初始化完毕。

2、View.post()方法,将消息队列的尾部。

3、使用viewTreeObserver的回调来完成。

4、通过view.measure方式手动测量。

View的Measure流程图:
安卓面试题_第2张图片

onLayout:

普通的view的话,可以通过setFrame方法来的到view四个顶点的位置,也就确定了view在父容器的位置,接着就调用onLayout方法,该方法是父容器确定子元素的位置。

onDraw:

该方法就是将view绘制到屏幕上。分以下几步

  • 绘制背景;
  • 绘制自己;
  • 绘制child;
  • 绘制装饰;

总结一下View的绘制流程

  • 第一步:OnMeasure():测量视图大小。从顶层父View到子View递归调用measure方法,measure方法又回调OnMeasure。
  • 第二步:OnLayout():确定View位置,进行页面布局。从顶层父View向子View的递归调用view.layout方法的过程,即父View根据上一步measure子View所得到的布局大小和布局参数,将子View放在合适的位置上。
  • 第三步:OnDraw():绘制视图。ViewRoot创建一个Canvas对象,然后调用OnDraw()。六个步骤:
    • ①、绘制视图的背景;
    • ②、保存画布的图层(Layer);
    • ③、绘制View的内容;
    • ④、绘制View子视图,如果没有就不用;
    • ⑤、还原图层(Layer);
    • ⑥、绘制滚动条。

13、自定义View流程

14、多线程、线程间通信

15、andorid数据库使用(SQLite、Room、GreenDao?)

16、设计模式

设计模式在Android源码中的运用

  • 创建型

    • 单例模式—Application、LayoutInflater、WindowsManagerService、ActivityManagerService
    • Builder模式—AlertDialog.Builder、StringBuilder、StringBuffer、Notification
    • 原型模式—Intent
    • 工厂方法模式—Activity的各种生命周期、ArrayList和HashSet
    • 抽象工厂模式—MediaPlayer
  • 行为型

    • 策略模式—动画插值器LinearInterpolator
    • 状态模式—Wi-Fi管理
    • 责任链模式—View事件的分发处理
    • 解释器模式—PackageParser
    • 命令模式—PackageHandler
    • 观察者模式—BaseAdapter、EventBus、Otto、AndroidEventBus
    • 备忘录模式—onSaveInstanceState和onRestoreInstanceState
    • 迭代器模式—Cursor
    • 模板方法模式—AsyncTask;Activity的生命周期
    • 访问者模式—ButterKnife、Dagger、Retrofit
    • 中介者模式—Keyguard解锁屏
  • 结构型

    • 代理模式—ActivityManagerProxy代理类
    • 组合模式—View和ViewGroup的嵌套组合
    • 适配器模式—ListView、GridView、RecyclerView
    • 装饰模式—Stream
    • 享元模式—String
    • 外观模式—Context
    • 桥接模式—Window与WindowManager

工厂方法模式和抽象工厂模式区别:

简单工厂模式

工厂方法模式

抽象工厂模式

17、Android线程池

Android中常见的线程池有四种,FixedThreadPoolCachedThreadPoolScheduledThreadPoolSingleThreadExecutor

  • FixedThreadPool

线程池是通过Executors的new FixedThreadPool方法来创建。它的特点是该线程池中的线程数量是固定的。即使线程处于闲置的状态,它们也不会被回收,除非线程池被关闭。当所有的线程都处于活跃状态的时候,新任务就处于队列中等待线程来处理。注意,FixedThreadPool只有核心线程,没有非核心线程。

  • CachedThreadPool

线程池是通过Executors的new CachedThreadPool进行创建的。它是一种线程数目不固定的线程池,它没有核心线程,只有非核心线程,当线程池中的线程都处于活跃状态,就会创建新的线程来处理新的任务。否则就会利用闲置的线程来处理新的任务。线程池中的线程都有超时机制,这个超时机制时长是60s,超过这个时间,闲置的线程就会被回收。这种线程池适合处理大量并且耗时较少的任务。这里得说一下,CachedThreadPool的任务队列,基本都是空的。

  • ScheduledThreadPool

线程池是通过Executors的new ScheduledThreadPool进行创建的,它的核心线程是固定的,但是非核心线程数是不固定的,并且当非核心线程一处于空闲状态,就立即被回收。这种线程适合执行定时任务和具有固定周期的重复任务。

  • SingleThreadExecutor

线程池是通过Executors的new SingleThreadExecutor方法来创建的,这类线程池中只有一个核心线程,也没有非核心线程,这就确保了所有任务能够在同一个线程并且按照顺序来执行,这样就不需要考虑线程同步的问题。

18、Android中性能优化

由于手机硬件的限制,内存和CPU都无法像pc一样具有超大的内存,Android手机上,过多的使用内存,会容易导致oom,过多的使用CPU资源,会导致手机卡顿,甚至导致anr。我主要是从一下几部分进行优化:

布局优化绘制优化内存泄漏优化响应速度优化listview优化bitmap优化线程优化

  • 布局优化:工具 hierarchyviewer,解决方式

    • 1、删除无用的空间和层级。

    • 2、选择性能较低的viewgroup,如Relativelayout,如果可以选择Relativelayout也可以使用LinearLayout,就优先使用LinearLayout,因为相对来说Relativelayout功能较为复杂,会占用更多的CPU资源。

    • 3、使用标签重用布局,减少层级,进行预加载,使用的时候才加载。

  • 绘制优化

    • 绘制优化指view在ondraw方法中避免大量的耗时操作,由于ondraw方法可能会被频繁的调用。

    • 1、ondraw方法中不要创建新的局部变量,ondraw方法被频繁的调用,很容易引起GC。

    • 2、ondraw方法不要做耗时操作。

  • 内存优化:参考内存泄漏

    • 单例模式

      context的使用,单列中传入的是activity的context,在关闭activity时,activity的内存无法被回收,因为单列持有activity的引用

      在context的使用上,应该传入application的context到单列模式中,这样就保证了单列的生命周期跟application的生命周期一样

    • Handler、AnycTask、Thread、Runable使用引起的内存泄漏

      Android使用Handler造成内存泄露的分析及解决方法

    • 非静态内部类或者匿名内部类都有隐式持有外部类(通常是Activity)引起的内存泄漏;

      通常Handler、AnycTask、Thread等内存泄漏都是由于创建了非静态内部类(或匿名内部类)并由子线程持有并执行耗时操作,导致Handler的生命周期比外部类的生命周期长;

    • BraodcastReceiver、File、Cursor等资源的使用未及时关闭

      应该在使用完成时主动销毁,或者在Activity销毁时注销BraodcastReceiver

    • MVP潜在的内存泄漏

      通常Presenter要同时持有View和Model的引用,如果Activity退出的时候,Presenter正在处理一些耗时操作,那么Presenter的生命周期会比Activity长,导致Activity无法回收。因此,我们在使用MVP架构的时候要做好生命周期的维护,在View被销毁的时候要通知Presenter释放其持有

  • 响应优化

主线程不能做耗时操作,触摸事件5s,广播10s,service20s。

  • listview优化

    • 1、getview方法中避免耗时操作。

    • 2、view的复用和viewholder的使用。

    • 3、滑动不适合开启异步加载。

    • 4、分页处理数据。

    • 5、图片使用三级缓存。

  • Bitmap优化

    • 1、等比例压缩图片。

    • 2、不用的图片,及时recycler掉

  • 线程优化

线程优化的思想是使用线程池来管理和复用线程,避免程序中有大量的Thread,同时可以控制线程的并发数,避免相互抢占资源而导致线程阻塞。

  • 其他优化

    • 1、少用枚举,枚举占用空间大。

    • 2、使用Android特有的数据结构,如SparseArray来代替hashMap。

    • 3、适当的使用软引用和弱引用。

19、内存泄漏相关

一、Handler 引起的内存泄漏。
解决:将Handler声明为静态内部类,就不会持有外部类SecondActivity的引用,其生命周期就和外部类无关,
如果Handler里面需要context的话,可以通过弱引用方式引用外部类
二、单例模式引起的内存泄漏。
解决:Context是ApplicationContext,由于ApplicationContext的生命周期是和app一致的,不会导致内存泄漏
三、非静态内部类创建静态实例引起的内存泄漏。
解决:把内部类修改为静态的就可以避免内存泄漏了
四、非静态匿名内部类引起的内存泄漏。
解决:将匿名内部类设置为静态的。
五、注册/反注册未成对使用引起的内存泄漏。
注册广播接受器、EventBus等,记得解绑。
六、资源对象没有关闭引起的内存泄漏。
在这些资源不使用的时候,记得调用相应的类似close()、destroy()、recycler()、release()等方法释放。
七、集合对象没有及时清理引起的内存泄漏。
通常会把一些对象装入到集合中,当不使用的时候一定要记得及时清理集合,让相关对象不再被引用。

Android内存泄漏全面总结

20、android hook技术

Java篇

1、ArrayList和LinkedList的区别,以及应用场景

ArrayList是基于数组实现的,ArrayList线程不安全。

LinkedList是基于双链表实现的:

使用场景:

(1)如果应用程序对各个索引位置的元素进行大量的存取或删除操作,ArrayList对象要远优于LinkedList对象;

( 2 ) 如果应用程序主要是对列表进行循环,并且循环时候进行插入或者删除操作,LinkedList对象要远优于ArrayList对象;

2、数组和链表的区别

数组:是将元素在内存中连续存储的;

它的优点:因为数据是连续存储的,内存地址连续,所以在查找数据的时候效率比较高;

它的缺点:在存储之前,我们需要申请一块连续的内存空间,并且在编译的时候就必须确定好它的空间的大小。在运行的时候空间的大小是无法随着你的需要进行增加和减少而改变的,当数据比较大的时候,有可能会出现越界的情况,数据比较小的时候,又有可能会浪费掉内存空间。在改变数据个数时,增加、插入、删除数据效率比较低。

链表:是动态申请内存空间,不需要像数组需要提前申请好内存的大小,链表只需在用的时候申请就可以,根据需要来动态申请或者删除内存空间,对于数据增加和删除以及插入比数组灵活。还有就是链表中数据在内存中可以在任意的位置,通过应用来关联数据(就是通过存在元素的指针来联系)

3、Java中堆和栈有什么不同?


函数中定义的基本类型变量,对象的引用变量都在函数的栈内存中分配。
栈内存特点,数数据一执行完毕,变量会立即释放,节约内存空间。
栈内存中的数据,没有默认初始化值,需要手动设置。


堆内存用来存放new创建的对象和数组。
堆内存中所有的实体都有内存地址值。
堆内存中的实体是用来封装数据的,这些数据都有默认初始化值。
堆内存中的实体不再被指向时,JVM启动垃圾回收机制,自动清除,这也是JAVA优于C++的表现之一(C++中需要程序员手动清除)。

4、Java中实现多态的机制

方法的重写Overriding和重载Overloading是Java多态性的不同表现

重写Overriding是父类与子类之间多态性的一种表现

重载Overloading是一个类中多态性的一种表现.

5、synchronized 和volatile关键字的区别

1.volatile本质是在告诉jvm当前变量在寄存器(工作内存)中的值是不确定的,需要从主存中读取;synchronized则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住。

2.volatile仅能使用在变量级别;synchronized则可以使用在变量、方法、和类级别的

3.volatile仅能实现变量的修改可见性,不能保证原子性;而synchronized则可以保证变量的修改可见性和原子性

4.volatile不会造成线程的阻塞;synchronized可能会造成线程的阻塞。

5.volatile标记的变量不会被编译器优化;synchronized标记的变量可以被编译器优化

6、强引用、软引用、弱引用、虚引用

从Java SE2开始,就提供了四种类型的引用:强引用、软引用、弱引用和虚引用。Java中提供这四种引用类型主要有两个目的:

1、可以让程序员通过代码的方式决定某些对象的生命周期;

2、有利于JVM进行垃圾回收。

  • 强引用(StrongReference)

    强引用就是指在程序代码之中普遍存在的,比如下面这段代码中的object和str都是强引用:

    Object object = new Object();
    String str = "hello";
    

    只要某个对象有强引用与之关联,JVM必定不会回收这个对象,即使在内存不足的情况下,JVM宁愿抛出OutOfMemory错误也不会回收这种对象。

  • 软引用(SoftReference)

    软引用是用来描述一些有用但并不是必需的对象,在Java中用java.lang.ref.SoftReference类来表示。对于软引用关联着的对象,只有在内存不足的时候JVM才会回收该对象。因此,这一点可以很好地用来解决OOM的问题,并且这个特性很适合用来实现缓存:比如网页缓存、图片缓存等。

    软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被JVM回收,这个软引用就会被加入到与之关联的引用队列中。下面是一个使用示例:

    import java.lang.ref.SoftReference;
    
    public class Main {
        public static void main(String[] args) {
         
            SoftReference sr = new SoftReference(new String("hello"));
            System.out.println(sr.get());
        }
    }
    
  • 弱引用(WeakReference)

    弱引用也是用来描述非必需对象的,当JVM进行垃圾回收时,无论内存是否充足,都会回收被弱引用关联的对象。在java中,用java.lang.ref.WeakReference类来表示。下面是使用示例:

    import java.lang.ref.WeakReference;
     
    public class Main {
        public static void main(String[] args) {
         
            WeakReference sr = new WeakReference(new String("hello"));
             
            System.out.println(sr.get());
            System.gc();                //通知JVM的gc进行垃圾回收
            System.out.println(sr.get());
        }
    }
    
    运行结果:
    hello
    null
    

    第二个输出结果是null,这说明只要JVM进行垃圾回收,被弱引用关联的对象必定会被回收掉。不过要注意的是,这里所说的被弱引用关联的对象是指只有弱引用与之关联,如果存在强引用同时与之关联,则进行垃圾回收时也不会回收该对象(软引用也是如此)。

  • 虚引用(PhantomReference)

    虚引用和前面的软引用、弱引用不同,它并不影响对象的生命周期。在java中用java.lang.ref.PhantomReference类表示。如果一个对象与虚引用关联,则跟没有引用与之关联一样,在任何时候都可能被垃圾回收器回收。

    import java.lang.ref.PhantomReference;
    import java.lang.ref.ReferenceQueue;
     
     
    public class Main {
        public static void main(String[] args) {
            ReferenceQueue queue = new ReferenceQueue();
            PhantomReference pr = new PhantomReference(new String("hello"), queue);
            System.out.println(pr.get());
        }
    }
    

    要注意的是,虚引用必须和引用队列关联使用,当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会把这个虚引用加入到与之 关联的引用队列中。程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。如果程序发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动。

7、对Java反射的理解

JAVA反射机制是在运行状态中, 对于任意一个类, 都能够知道这个类的所有属性和方法; 对于任意一个对象, 都能够调用它的任意一个方法和属性。

从对象出发,通过反射(Class类)可以取得类的完整信息(类名 Class类型,所在包、具有的所有方法 Method[]类型、某个方法的完整信息(包括修饰符、返回值类型、异常、参数类型)、所有属性 Field[]、某个属性的完整信息、构造器 Constructors),调用类的属性或方法自己的总结: 在运行过程中获得类、对象、方法的所有信息。

8、哪些情况下的对象会被垃圾回收机制处理掉?

1.所有实例都没有活动线程访问。

2.没有被其他任何实例访问的循环引用实例。

3.Java 中有不同的引用类型。判断实例是否符合垃圾收集的条件都依赖于它的引用类型。

要判断怎样的对象是没用的对象。这里有2种方法:

1.采用标记计数的方法:

给内存中的对象给打上标记,对象被引用一次,计数就加1,引用被释放了,计数就减一,当这个计数为0的时候,这个对象就可以被回收了。当然,这也就引发了一个问题:循环引用的对象是无法被识别出来并且被回收的。所以就有了第二种方法:

2.采用根搜索算法:

从一个根出发,搜索所有的可达对象,这样剩下的那些对象就是需要被回收的

9、开启线程的三种方式?

Java有三种创建线程的方式,分别是:

继承Thread类

public class FirstThread extends Thread {

    @Override
    public void run() {
       ...
    }
}

实现Runable接口

public class SecondThread implements Runnable{
    public void run() {
        ...
    }
}

实现callable 接口

public class Target implements Callable {
    int i=0;
    public Integer call() throws Exception {
        ...
        return i;
    }
}

public static void main(String[] args) {
    Target t1=new Target();
    FutureTask ft=new FutureTask(t1);
    Thread t2=new Thread(ft,"新线程");
    t2.start();
    try {
        System.out.println(ft.get());
    } catch (Exception e) {
        // TODO: handle exception
    }
}

三种方式的对比

1、采用实现Runnable、Callable接口的方式创建多线程时,

优势是:

线程类只是实现了Runnable接口或Callable接口,还可以继承其他类。

在这种方式下,多个线程可以共享同一个target对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将CPU、代码和数据分开,形成清晰的模型,较好地体现了面向对象的思想。

劣势是:

编程稍微复杂,如果要访问当前线程,则必须使用Thread.currentThread()方法。

2、使用继承Thread类的方式创建多线程时,

优势是:

编写简单,如果需要访问当前线程,则无需使用Thread.currentThread()方法,直接使用this即可获得当前线程。

劣势是:

线程类已经继承了Thread类,所以不能再继承其他父类。

3、Runnable和Callable的区别

(1) Callable规定(重写)的方法是call(),Runnable规定(重写)的方法是run()。

(2) Callable的任务执行后可返回值,而Runnable的任务是不能返回值的。

(3) call方法可以抛出异常,run方法不可以。

(4) 运行Callable任务可以拿到一个Future对象,表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并检索计算的结果。通过Future对象可以了解任务执行情况,可取消任务的执行,还可获取执行结果

你可能感兴趣的:(笔记)