安卓面试题集一

303 阅读9分钟

1.自定义view有哪些方式

1. 组合控件,将几种控件组合起来形成一个其他地方需要复用的控件,如标题栏。自定义一个**view继承五大布局,通过inflater.inflate将写了几个控件的layout加到控件里面去。

2. 继承系统控件,如很多地方使用的edittext需要加背景,就自定义一个控件继承edittext,然后给他设置上背景。

3.自定义View继承view,复写View的生命周期函数来完成View的渲染,先复写构造函数,再复写其余几个方法。

  • onMeasure:该方法就是测量绘制view所需要的大小。参数三种模式 at_most 限定了最大值,具体看实现 EXACTLY有精确值了 UNSPECIFIED 不限制大小
  • onLayout:就是放置每个子view的位置。
  • onDraw:就是将子view绘制到画布上面。
  • 设置点击事件要用onTouchEvent.

4. 自定义viewgroup继承viewgroup。

  • 只需要onMeasure与onLayout,与自定义view相同

  • 在onLayout()计算完每个子View的坐标之后,可以直接通过addView(child)的方式直接将该子View添加到ViewGroup中,无需复写onDraw()来将子View绘制到canvas。 

  • 在实现每个子View的点击事件,可以直接通过child.setOnClickListener来设置点击事件。 

  • zhuanlan.zhihu.com/p/651458770

  • onInterceptTouchEvent()是ViewGroup的一个方法,目的是在系统向该ViewGroup及其各个childView触发onTouchEvent()之前对相关事件进行一次拦截,Android这么设计的想法也很好理解,由于ViewGroup会包含若干childView,因此需要能够统一监控各种touch事件的机会,因此纯粹的不能包含子view的控件是没有这个方法的,如LinearLayout就有,TextView就没有。 true表示拦截了 false不拦截

总结:

  • 1.requestLayout()和invalidate() (1)requestLayout():会将mPrivateFlags 设置为PFLAG_FORCE_LAYOUT,从而调用到父控件的mParent.requestLayout()并标记为PFLAG_FORCE_LAYOUT,最后调用到ViewRootImpl的requestLayout(),这些被标记为PFLAG_FORCE_LAYOUT的View会重新调用到onMeasure()、onLayout()、onDraw(); (

  • 2)invalidate():并没有将mPrivateFlags设置为PFLAG_FORCE_LAYOUT,所以只会调用到onDraw(),而不会调用到onMeasure()、onLayout()。 这个问题出现就是在增加了ImageView的个数刷新View的时候,发现该View的所占的空间并没有发生变化,正是由于这两个方法没有合理使用引起的。 

  •  2.在复写onMeasure()、onLayout()、onDraw()的时候,要把padding计算在View控件中,否则设置的padding不起作用 

  •  3.在复写onLayout()的时候,其实可以直接将子View从paddingTop、paddingLeft开始放置子View,onLayout传入的left/top/right/bottom用处不大,因为这四个值返回的是该自定义控件本身在父容器中的坐标 

  •  4.继承于View在复写onDraw()的时候,需要canvas.translate,才能将子View绘制到canvas上,并且canvas平移的时候都是相对于当前位置平移。

2.glide如何做自动回收

glide会自己监听生命周期

3.MVVM

view-model-viewmodel

viewmodel存放与界面相关数据,减轻activity负担

当发生横竖屏切换、白天黑夜切换、语言切换等时,activity会重建,viewmodel不会,保持数据不损失,viewmodel有自己独立的生命周期,所有不能在activity中创建,要用ViewModelP rovider获取。

4.lifecycle 

生命周期组件,让类自己监听某个活动的生命周期

5.livedata 

数据绑定,数据主动同步组件

6.mvp 

presenter操作视图和数据

7.arraylist hashmap linkedHashmap

按照存和取的顺序来讲,ArrayList和LinkedList就属于有序集合,因为ArrayList底层是动态数组实现的,而数组是一块连续的空间,每次存的时候都是找到索引,一个接着一个的存储,取的时候也要按照索引遍历出来。

hashmap的容量必须是2的n次方,不管正反插入,hashmap会对key按一定规则来排序,调用interator.next方法时,不按存入的顺序取出。

linkedHashmap双向链表。即每个数据节点带有直接前后驱两个指针。跟hashmap都存在线程不安全的问题。容易死循环。不死循环的有HashTable和SynchronizedMap,都是对put get方法加锁或对map对象本身加锁。影响效率。推荐****ConcurrentHashMap。

8.前段实体类对象有规定的命名与后端返回的字段不一致,如何处理 

Gson 使用 @SerializedName 注解解决 

@SerializedName("code") private String password;

9.mvvm 数据如何双向绑定和刷新界面,原理是什么 

使用livedata来进行双向绑定。数据更新时livedata通知所有被观察者,dababinding生成的BindingImpl中有onFieldChange方法来更新界面。

10.recycleview 间距怎么设置的 

mRecyclerView.addItemDecoration

11.view如何自定义布局的 

12.内存优化 

打开Android Studio性能检测工具Profiler,可以获取到当前应用的内存动态使用情况。然后看那一部分内存使用多,进行相应的控制。图片优化,数据量优化。

13.OKhttp retrofit RxJava 原理 

14.viewstub merge include 区别

viewstub是一个默认宽高为0的控件,通常用于缓加载,先判断需不需要这部分内容再加载。

include是一个引用其他地方布局的控件。但会引入额外层级。

merge也可以在其中写其他控件,但不会引入层级。通常配合include,减少层级。

15.intent大小限制原因

Intent 传输数据的机制中,用到了 Binder。Intent 中的数据,会作为 Parcel 被存储在 Binder 的事务缓冲区(Binder transaction buffer)中的对象进行传输。

传 512K 以下的数据的数据可以正常传递。
传 512K~1024K 的数据有可能会出错,闪退。
传 1M以上的数据会报错:TransactionTooLargeException

16.哪些类不该被混肴

四大组件不能被混肴,因为要在ams.xml里面注册 混肴了找不到

17.intent四种启动模式

1.standard 系统默认的启动模式,栈结构,先进后出.只要你创建了Activity实例,一旦激活该Activity,则会向任务栈中加入新创建的实例,退出Activity则会在任务栈中销毁该实例。

2.singleTop 如果某个Activity自己激活自己,即任务栈栈顶就是该Activity,则不需要创建,其余情况都要创建Activity实例; 推送详情页和商品详情页这种多次重复打开的 只刷数据

3.singleTask 如果要激活的那个Activity在任务栈中存在该实例,则不需要创建,只需要把此Activity放入栈顶,并把该Activity以上的Activity实例都pop 一般app首页 

4. singleInstance 如果我们将某个activity设置成这个singleStance启动模式,则当激活这个activity之后单独放到一个栈,下次再使用的时候,直接使用这个栈,比如打电话应用就是一个singleStance模式启动的activity 电话 闹钟 日历

18.kotlin为什么可以在jvm运行

19.单例模式有哪几种

20.如何让某些线程在别的线程之后执行

countdownlatch

blog.csdn.net/hbtj\_1216/…

21.安卓线程有几种状态

1)、新建状态(New):新创建了一个线程对象。

 2)、就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权。

 3)、运行状态(Running):就绪状态的线程获取了CPU,执行run()方法。 

4)、阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种: (一)、等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中。 (二)、同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。 (三)、其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。 

5)、死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。 当调用start方法的时候,该线程就进入就绪状态。等待CPU进行调度执行,此时还没有真正执行线程。 当调用run方法的时候,是已经被CPU进行调度,执行线程的主要任务。

22.handler为什么会造成内存泄漏

1、handler创建时候就对持有当前Activity得引用,同时message持有对handler的引用。MessageQueue持有Message;Message持有了Handler;handler是匿名内部类,持有this Activity,Activity持有大量内存,就会造成内存泄漏。

2、 发送一个延迟消息时候,消息被处理前,该消息一直保存在消息队列中,在持续保存这段时间,messageque持有对message的引用,Message持有了handler;在我们的activity中建立一个Handler的实例,该handler实例默认持有了外部的对象acticity的引用,当我们调用acticity的ondestroy方法时,activity销毁了,但是根据可达性分析,我们的需要的activity存在被handler引用,只要handler不被释放,没办法会销毁,就造成了内存泄漏。

解决办法:

ondestroy方法中,清除消息队列中的消息;mHandler.removeCallbacksAndMessages(null)

或者将handler定义为静态内部类 但是需要将handler中需要使用的外部类或对象定义为静态或使用弱引用持有。使用第二种方法时,需要注意弱引用的生命周期

23.线程安全

1. synchronized加锁

2. volatile关键字能保证内存可见性,即不管那个线程使用该变量都是最新的。且能解决指令重排序问题。

24. 进程间通信方式

1. bundle,startactivity的时候传

2. 文件共享

3. contentprovider

4. aidl

25.内存优化

主要三方面,内存泄漏、内存抖动、内存溢出

当内存频繁分配和回收导致内存不稳定,出现内存抖动,内存抖动通常表现为频繁 GC、内存曲线呈锯齿状。

并且,内存抖动的危害严重,会导致页面卡顿,甚至 OOM。

内存抖动的优化:

1.使用StringBuilder拼接字符串。因为StringBuilder可以提前创建确定大小,避免扩容。

2.不在循环中创建变量

3.资源复用

4.使用合理的数据结构

内存泄漏可使用android profiler检测 开着该工具 运行一遍逻辑 然后看memory类别 会显示泄漏个数 再看具体的。

26. 弱引用

通常安卓中都是强引用。

如果一个对象只具有弱引用,则内存空间足够,垃圾回收器就不会回收它,如果内存空间不足了,就会回收这些对象的内存。弱引用通常用在对内存敏感的程序中,比如高速缓存就有用到软引用,内存够用的时候就保留,不够用就回收。

关键字 WeakReference。

27.音频焦点

requestAudioFocus()请求音频焦点

为 requestAudioFocus 方法传入的第3个参数: 

 如果计划在将来一段时间内播放音频,并且希望前一个持有音频焦点的应用停止播放,则应该请求永久性的音频焦点 (AUDIOFOCUS_GAIN)。 

如果只希望在短时间内播放音频,并且希望前一个持有音频焦点的应用暂停播放,则应该请求暂时性的焦点 (AUDIOFOCUS_GAIN_TRANSIENT)。

 如果只希望在短时间内播放音频,并允许前一个持有焦点的应用在降低音量的情况下继续播放,则应该请求“降低音量”的暂时性焦点(AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK) 。这两个音频会混合到音频流中同时输出。

abandonAudioFocus()放弃音频焦点

AudioManager.OnAudioFocusChangeListener ,以便接收回调并管理自己的音量

AUDIOFOCUS_GAIN:获取得到音频焦点。 

AUDIOFOCUS_LOSS:永久性失去音频焦点,后续不会再收到 AUDIOFOCUS_GAIN 回调。应用应立即暂停播放,此时其他应用会播放音频。 

AUDIOFOCUS_LOSS_TRANSIENT:暂时性失去音频焦点,应用应暂停播放。当抢占焦点的应用放弃焦点时、自己的应用可以收到 AUDIOFOCUS_GAIN 回调。

 AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:暂时性失去音频焦点,应用应降低音量。当抢占焦点的应用放弃焦点时、自己的应用可以收到 AUDIOFOCUS_GAIN 回调。 

28.小卡片

AppWidget 是通过 BroadCastReceiver 的形式进行控制的,开发 AppWidget 的主要类为 AppWidgetProvider, 该类继承自 BroadCastReceiver。为了实现桌面小部件,开发者只要开发一个继承自 AppWidgetProvider 的子类,并重写它的 onUpdate() 方法即可。重写该方法,一般来说可按如下几个步骤进行:   

1、创建一个 RemoteViews 对象,这个对象加载时指定了桌面小部件的界面布局文件。  

2、设置 RemoteViews 创建时加载的布局文件中各个元素的属性。   

3、创建一个 ComponentName 对象   

4、调用 AppWidgetManager 更新桌面小部件。 

29.安卓bug监控 使用bugly平台

30.Android为什么要设计出Bundle而不是直接使用HashMap来进行数据传递

  1. 性能优化Bundle内部使用的是ArrayMap实现,这种数据结构在内存使用和数据操作速度上相比HashMap更适合小数据量操作,这是因为ArrayMap内部使用两个数组来存储键值对,并通过二分法进行数据查找,而HashMap内部是数组加链表结构,适合处理大量数据。在Android中,组件间传递数据通常涉及的数据量较小,因此ArrayMap更适合这种场景

  2. 序列化效率:在Android中,Intent用于在组件间传递数据时,推荐使用的数据序列化方式是Parcelable,而不是SerializableBundle实现了Parcelable接口,这意味着它可以更高效地处理数据的序列化和反序列化过程。相比之下,HashMap默认使用Serializable接口进行序列化,这在Android平台上通常效率较低

  3. 兼容性和集成Bundle作为Android框架的一部分,与Android的组件通信机制(如Intent)紧密集成。它提供了一套专门为Android设计的API,这些API考虑了Android应用的特定需求,如跨进程通信和内存管理。直接使用HashMap可能需要额外的工作来确保与Android框架的兼容性

  4. 内存优化:Android对内存使用非常敏感,因此在设计框架时会特别关注内存效率。Bundle的设计考虑到了这一点,通过使用ArrayMapParcelable接口,它能够在不牺牲性能的前提下,提供一种轻量级的数据传递方式

31.  Parcelable与Serializable的区别

  1. 性能Parcelable 通常比 Serializable 更高效。Parcelable 是 Android 特有的接口,专门优化了在 Android 中的数据序列化和反序列化,适合在进程间传递数据。而 Serializable 是 Java 的标准接口,性能较低,因为它涉及到反射和较为复杂的对象图处理。

  2. 实现复杂度Parcelable 需要手动实现序列化和反序列化过程,代码较为复杂,但提供了更高的灵活性和性能,需要实现writeToParcel()和CREATOR字段。而 Serializable 只需要实现一个接口,只需添加implements Serializable即可,自动处理大部分序列化工作,但可能会在性能上有所妥协。

  3. 数据传递Parcelable 适合用于 Android 的 IntentBundleIPC 数据传递,而 Serializable 更适合于普通的 Java 对象存储和传递。

32 anr

输入事件在5秒内没有处理完成发生ANR。 

前台服务在20秒内,后台服务在200秒内没有处理完成发生ANR。 

BroadcastReceiver的onReceive方法在处理广播时,前台广播在10秒内,后台广播在60秒内没有处理完成发生ANR。 

ContentProvider在发布数据时在10秒内没有处理完成发生ANR。 

 分析anr步骤: 

1. 查看日志和traces文件分析anr的原因和大概位置 

2. 确定是什么原因导致的anr 

3. 死锁、拿不到某些资源导致一直等待,同时对已拿资源保持不放 

4. 主线程耗时操作 

5. 系统资源不足(内存太小了)

int type = (((int) bytes[0] ) & 0xff) | (((int) bytes[1]&0xff)<<8);

33.应用启动流程

1.向 System_Server 进程发起 startActivity 请求

2.System_Server 向 Zygote 进程发送创建新进程的请求

3.新进程实例化ActivityThread,ActivityThread 是应用进程的主线程类,它负责管理应用的生命周期和消息循环等。在这一步骤中,会创建 ApplicationThreadLooperHandler 对象,并开启主线程的消息循环 Looper.loop()

4.绑定 Application:ActivityThreadmain 方法调用 attach 方法进行 Binder 通信,通知 System_Server 进程执行 ActivityManagerService#attachApplication(mAppThread) 方法。在 System_Server 进程中,ActivityManagerService#attachApplication(mAppThread) 会依次初始化 ApplicationActivity

34.setContentView流程

1.根据传入的布局id,将其加载到内存中,递归生成view对象,然后将其设置给当前的window。

2.window将其传给ViewRootImpl, ViewRootImpl会执行measure(确定大小),layout(确定位置),draw以将view显示出来。