安卓面试中高级面试

Posted android与kotlin以及java架构

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了安卓面试中高级面试相关的知识,希望对你有一定的参考价值。


  1. 什么情况下会造成内存泄漏,怎么避免


  • 1.单例造成的内存泄漏
    由于单例的静态特性使得其生命周期和应用的生命周期一样长,如果一个对象已经不再需要使用了,而单例对象还持有该对象的引用,就会使得该对象不能被正常回收,从而导致了内存泄漏。

  • 2.非静态内部类创建静态实例造成的内存泄漏
    例如,有时候我们可能会在启动频繁的Activity中,为了避免重复创建相同的数据资源,可能会出现如下写法:

public class MainActivity extends AppCompatActivity {  
private static TestResource mResource = null;  
 @Override  protected void onCreate(Bundle savedInstanceState) {    
  super.onCreate(savedInstanceState);       setContentView(R.layout.activity_main);    
   if(mResource == null){          mResource = new TestResource();       }      //...   }   class TestResource {  //...   } }

这样在Activity内部创建了一个非静态内部类的单例,每次启动Activity时都会使用该单例的数据。虽然这样避免了资源的重复创建,但是这种写法却会造成内存泄漏。因为非静态内部类默认会持有外部类的引用,而该非静态内部类又创建了一个静态的实例,该实例的生命周期和应用的一样长,这就导致了该静态实例一直会持有该Activity的引用,从而导致Activity的内存资源不能被正常回收。

解决方法:将该内部类设为静态内部类或将该内部类抽取出来封装成一个单例,如果需要使用Context,就使用Application的Context。

  • 3.Handler造成的内存泄漏

示例:创建匿名内部类的静态对象

public class MainActivity extends AppCompatActivity {  
private final Handler handler = new Handler() {  
 @Override   public void handleMessage(Message msg) {      
   // ...    }  };  @Override   protected void onCreate(Bundle savedInstanceState) {      
    super.onCreate(savedInstanceState);      setContentView(R.layout.activity_main);    
     new Thread(new Runnable() {        
      @Override          public void run() {            
       // ...             handler.sendEmptyMessage(0x123);           }     });  } }

1、从Android的角度
当Android应用程序启动时,该应用程序的主线程会自动创建一个Looper对象和与之关联的MessageQueue。当主线程中实例化一个Handler对象后,它就会自动与主线程Looper的MessageQueue关联起来。所有发送到MessageQueue的Message都会持有Handler的引用,所以Looper会据此回调Handle的handleMessage()方法来处理消息。只要MessageQueue中有未处理的Message,Looper就会不断的从中取出并交给Handler处理。另外,主线程的Looper对象会伴随该应用程序的整个生命周期。
2、 Java角度
在Java中,非静态内部类和匿名类内部类都会潜在持有它们所属的外部类的引用,但是静态内部类却不会。

对上述的示例进行分析,当MainActivity结束时,未处理的消息持有handler的引用,而handler又持有它所属的外部类也就是MainActivity的引用。这条引用关系会一直保持直到消息得到处理,这样阻止了MainActivity被垃圾回收器回收,从而造成了内存泄漏。

解决方法:将Handler类独立出来或者使用静态内部类,这样便可以避免内存泄漏。

  • 4.线程造成的内存泄漏

示例:AsyncTask和Runnable
AsyncTask和Runnable都使用了匿名内部类,那么它们将持有其所在Activity的隐式引用。如果任务在Activity销毁之前还未完成,那么将导致Activity的内存资源无法被回收,从而造成内存泄漏。
解决方法:将AsyncTask和Runnable类独立出来或者使用静态内部类,这样便可以避免内存泄漏。

  • 5.资源未关闭造成的内存泄漏

对于使用了BraodcastReceiver,ContentObserver,File,Cursor,Stream,Bitmap等资源,应该在Activity销毁时及时关闭或者注销,否则这些资源将不会被回收,从而造成内存泄漏。
1)比如在Activity中register了一个BraodcastReceiver,但在Activity结束后没有unregister该BraodcastReceiver。
2)资源性对象比如Cursor,Stream、File文件等往往都用了一些缓冲,我们在不使用的时候,应该及时关闭它们,以便它们的缓冲及时回收内存。它们的缓冲不仅存在于 java虚拟机内,还存在于java虚拟机外。如果我们仅仅是把它的引用设置为null,而不关闭它们,往往会造成内存泄漏。
3)对于资源性对象在不使用的时候,应该调用它的close()函数将其关闭掉,然后再设置为null。在我们的程序退出时一定要确保我们的资源性对象已经关闭。
4)Bitmap对象不在使用时调用recycle()释放内存。2.3以后的bitmap应该是不需要手动recycle了,内存已经在java层了。

  • 6.使用ListView时造成的内存泄漏

初始时ListView会从BaseAdapter中根据当前的屏幕布局实例化一定数量的View对象,同时ListView会将这些View对象缓存起来。当向上滚动ListView时,原先位于最上面的Item的View对象会被回收,然后被用来构造新出现在下面的Item。这个构造过程就是由getView()方法完成的,getView()的第二个形参convertView就是被缓存起来的Item的View对象(初始化时缓存中没有View对象则convertView是null)。
构造Adapter时,没有使用缓存的convertView。
解决方法:在构造Adapter时,使用缓存的convertView。

  • 7.集合容器中的内存泄露

我们通常把一些对象的引用加入到了集合容器(比如ArrayList)中,当我们不需要该对象时,并没有把它的引用从集合中清理掉,这样这个集合就会越来越大。如果这个集合是static的话,那情况就更严重了。
解决方法:
在退出程序之前,将集合里的东西clear,然后置为null,再退出程序。

  • 8.WebView造成的泄露

当我们不要使用WebView对象时,应该调用它的destory()函数来销毁它,并释放其占用的内存,否则其长期占用的内存也不能被回收,从而造成内存泄露。
解决方法:
为WebView另外开启一个进程,通过AIDL与主线程进行通信,WebView所在的进程可以根据业务的需要选择合适的时机进行销毁,从而达到内存的完整释放。



  1. Android中ARN的情况,怎么解决


如果开发机器上出现问题,我们可以通过查看/data/anr/traces.txt即可,最新的ANR信息在最开始部分。

  • 主线程被IO操作(从4.0之后网络IO不允许在主线程中)阻塞。

  • 主线程中存在耗时的计算

  • 主线程中错误的操作,比如Thread.wait或者Thread.sleep等 Android系统会>* 监控程序的响应状况,一旦出现下面两种情况,则弹出ANR对话框

  • 应用在5秒内未响应用户的输入事件(如按键或者触摸)

  • BroadcastReceiver未在10秒内完成相关的处理

  • Service在特定的时间内无法处理完成 20秒
    修正

  • 使用AsyncTask处理耗时IO操作。

  • 使用Thread或者HandlerThread时,调用Process.setThreadPriority(Process.THREADPRIORITYBACKGROUND)设置优先级,否则仍然会降低程序响应,因为默认Thread的优先级和主线程相同。

  • 使用Handler处理工作线程结果,而不是使用Thread.wait()或者Thread.sleep()来阻塞主线程。

  • Activity的onCreate和onResume回调中尽量避免耗时的代码

  • BroadcastReceiver中onReceive代码也要尽量减少耗时,建议使用IntentService处理。


  • 内存泄漏和内存溢出的区别,怎么避免,什么情况下回内存溢出

  • 内存溢出 out of memory,是指程序在申请内存时,没有足够的内存空间供其使用,出现out of memory;比如申请了一个integer,但给它存了long才能存下的数,那就是内存溢出。

  • 内存泄露 memory leak,是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光。

  • 避免方法  和上面OOM的方法类似答案类似




  • 启动一个应用程序可以从桌面入口进去,也可以从别的应用跳转进去,这样有什么区别

  •     是因为启动程序(主界面也是一个app),发现了在这个程序中存在一个设置为的activity,所以这个launcher会把icon提出来,放在主界面上。当用户点击icon的时候,发出一个Intent:Intent intent = mActivity.getPackageManager().getLaunchIntentForPackage(packageName);mActivity.startActivity(intent);跳过去可以跳到任意允许的页面,如一个程序可以下载,那么真正下载的页面可能不是首页(也有可能是首页),这时还是构造一个Intent,startActivity.这个intent中的action可能有多种view,download都有可能。系统会根据第三方程序向系统注册的功能,为你的Intent选择可以打开的程序或者页面。所以唯一的一点不同的是从icon的点击启动的intent的action是相对单一的,从程序中跳转或者启动可能样式更多一些。本质是相同的。


  • Activity因为某些原因被回收了,状态怎么保存

    在内存不足或者其他原因导致Activity被异常销毁的时候,会调用Activity的onSaveInstanceState(Bundle bundle)方法,这个方法会在onPause之后OnStop之前调用   执行顺序 onPause---onSaveInstanceState---onStop,所以不要在onPause里面做过多的耗时操作。通过onSaveInstanceState方法将需要保存的状态保存到Bundle里面。

在界面跳转的时候,需要取出状态,我们创建Activity的时候,AS默认给我创建的模板会自动创建出onCreate(Bundle savedInstanceState)方法,该方法中的Bundle对象就是之前保存的状态值,然后可以进行相应操作。

如果Activity没有被异常销毁的话,如果跳转该Activity的话,会执行onResume--onStart

  • Activity中的context和AppLication的Context有什么区别

  • AIDL是什么简要说明(了解)

    Android系统中的进程之间不能共享内存,因此,需要提供一些机制在不同进程之间进行数据通信。

为了使其他的应用程序也可以访问本应用程序提供的服务,Android系统采用了远程过程调用(Remote Procedure Call,RPC)方式来实现。与很多其他的基于RPC的解决方案一样,Android使用一种接口定义语言(Interface Definition Language,IDL)来公开服务的接口。我们知道4个Android应用程序组件中的3个(Activity、BroadcastReceiver和ContentProvider)都可以进行跨进程访问,另外一个Android应用程序组件Service同样可以。因此,可以将这种可以跨进程访问的服务称为AIDL(Android Interface Definition Language)服务。


  • 自定义View的流程(View的绘制流程)


View在Activity中显示出来,要经历测量、布局和绘制三个步骤,分别对应三个动作:measure、layout和draw。

  • 测量:onMeasure()决定View的大小;

自定义View的onMeasure(): --> 测量View的大小

系统帮我们测量的高度和宽度都是MATCH_PARENT;当我们设置明确的宽度和高度时,系统测量的结果就是我们设置的结果。

当设置为WRAP_CONTENT,或者是MATCH_PARENT时,系统测量的结果就是MATCH_PARENT的长度。

当设置为WRAP_CONTENT时,而有需要进行自我测量时,就需要覆写onMeasure()。

重写之前先了解MeasureSpec的specMode,一共三种类型:

EXACTLY:一般是设置为明确的值或者是精确的值,Parent为子View决定了一个绝对尺寸,子View会被赋予这个边界限制,不管子View自己想要多大;

AT_MOST:表示子布局限制在一个最大值内,代表最大可获取的空间;代表子View可以是任意的大小,但是有一个绝对尺寸上限;

UNSPECIFIED:表示子布局想要多大就多大,很少使用;代表Parent没有对子View强加任何限制,子View可以是它想要的任何尺寸;

  • 布局:onLayout()决定View在ViewGroup中的位置;(继承ViewGroup的时候才会重写onLayout方法,)


1. 子视图的具体位置都是相对于父视图而言的。View的onLayout()方法为空实现,而ViewGroup的onLayout为abstract,因此,自定义的View要继承ViewGroup时,必须实现onLayout函数。

2. 在Layout过程中,子视图会调用getMeasuredWidth()和getMeasuredHeight()方法获取到measure过程得到mMeasuredWidth和mMeasuredHeight,作为自己的width和height。然后调用每一个子视图的layout(),来确定每个子视图在父视图中的位置。

  • 绘制:onDraw()决定绘制这个View。

1.所有视图最终都是调用View的draw方法进行绘制。 在自定义视图中, 也不应该复写该方法, 而是复写onDraw()方法进行绘制, 如果自定义的视图确实要复写该方法,先调用super.draw()完成系统的绘制,再进行自定义的绘制。

2.  onDraw()方法默认是空实现,自定义绘制过程需要复写方法,绘制自身的内容。

3. dispatchDraw()发起对子视图的绘制,在View中默认为空实现,ViewGroup复写了dispatchDraw()来对其子视图进行绘制。自定义的ViewGroup不应该对dispatchDraw()进行复写。


  • bitmap的内存计算公式(如何压缩)

如果在不涉及其他因素的影响下,一张图片的内存大小为(长*宽*一个像素点所占的字节数)一个像素点所占的内存大小是由图片的显示质量来觉得的,也就是我

们常说的RGB8888(8byte)、RGB565(2byte)、RGB4444(4byte)、ALRHA_8(1byte)如果涉及到手机分辨率的因素影响的话还有其他的变化详情

上面说了内存计算公式,那么的话  如果我的手机上面有一张4208*3120的图片  ,假如图片的质量很高的话,那么一张图片占用的内存就是4208*3120*8≈100MB,这样的话没几张图片就把内存全部都占用完了,而可能我们的手机展示根本就不需要那么大,这样的情况就需要我们将图片进行压缩。安卓为我们提供了相关的API

BitmapFactory类提供了几个解析方法,能从几种资源创建Bitmap。基于你Image资源选出最恰当的方法。这些方法会为创建Bitmap分配内存,因此可能会造成OutOfMemory异常。每种解码方法都可以让你通过BitmapFactory.Options设置标签。设置inJustDecodeBounds 为true,解码的时候会避免内存的分配,返回的bitmap对象为空,但是却可以得到 outWidth, outHeight 和outMimeType。这个技术可以是你在生成Bitmap之前获得到图片的尺寸和类型。

安卓面试(二)中高级面试

 

现在知道了图片的尺寸,就能知道到底是原图加载到内存中还是加载一个样品。下面几点可以考虑:

  • 估算加载完整图片使用的内存大小。

  • 加载了完整图片,给应用其他的内存需求大小

  • 目标Imageview尺寸或者加载图片的UI组建大小

  • 当前设备屏幕尺寸或者大小 
    告诉解码器对Image进行采样,加载一个较小版本图片到内存中,设置BitmapFactory.Options 对象inSampleSize为true。比如一个2048*1536密度的图片,inSampleSize为4生成的样品图就是512*384。加载这个样品需要内存0.75MB,而不是原来的12MB。下面提供了方法计算样本大小。

安卓面试(二)中高级面试

用下面地方法,第一步设置inJustDecodeBounds = true,然后通过新的inSampleSize解码,最后设置inJustDecodeBounds = false。

安卓面试(二)中高级面试

这个方法就可以把任意尺寸图显示成100*100的缩略图

安卓面试(二)中高级面试

如果还是觉得图片太大的话,那就对图片的解码方式进行修改,通过options.inPreferredConfigj进行设置,就是上面说的RGB8888。 

  • 如何对安卓进行优化(内存泄漏的解决方式)

  • 描述一下handler的原理

安卓面试(二)中高级面试

上面这张图片介绍的很详细了,下面来讲解一下

首先会介绍到的几个概念Looper、Handler、MessageQuene(铁三角)


1、每个Activity再创建的时候都会生成一个Looper,需要介绍的两个方法是

Looper.prepare():是创建Looper对象(每个Activity仅有一个,重复调用prepare方法会报错),

Looper.loop()通过阻塞时的死循环从MessageQuene里面取出Message消息,Message消息里面会携带发送者handler的引用,通过message.target得到的就是handler的引用,message.target.dispatchMessage(msg),调用该方法之后会调用Hanlder的hanldeMessage(msg)方法。

2、Handler的主要作用就是发送和接受消息

发送:两种形式,一种是send和post ,两种方式最终调用的方法是sendMessageAtTime(Message msg, long uptimeMillis),在发送消息的时候,会把handler的引用设置给message( msg.target = this)。handler可以创建多个,设置target的目的一个是为了识别是哪个hanlder发送的消息,需要发送给对应的handler   一个就是上面说的handler.dispatchMessage(msg)将消息给handler处理

3、MessageQuene中文翻译就是消息队列

MessageQuene,它内部存储了一组信息,存放的是Message,以队列的形式对外提供了插入和删除的工作(虽然名字叫做队列,但是其内部的 存储结构是单链表)

主要 插入 和 读取 两个操作,这两个操作对应着两个方法:

插入(入队) enqueueMessage(Message msg, long when),我们再调用handler发送消息的时候,会发现走到最后调用的就是enqueueMessage方法,该方法的作用就是将message消息放到MessageQuene里面

读取(出队) next()

next方法在这里是一个无限循环的方法,如果消息队列里面没有消息,那么他就会处于阻塞状态,当有新的消息到来的时,next就会返回这条消息并且将其从单链表中移除。

  • 简要说出图片加载框架的原理,


  • Eventbus的实现原理

能够在不同组件Activity等等通信的原理即 一个static的单例对象,成员变量是一个List等容器==>
注册时 用来存放观察者(List.add)
发送时 用来通知观察者

通过对List容器进行循环,找到里面对应的方法名字、对应的参数的type,然后执行相应的方法。


  • 捕获异常的第三方框架Bugly

Bugly是腾讯开源的收集应用Crash信息的工具,能够记录应用的运行轨迹,代码收集、还可以对产生的bug进行管理,能快速准确的定位到问题所在!另外需要说的就是它里面还集成了Tinker框架(热修复),无需重新发版让用户无感知就能把问题修复的一项能力。Bugly目前采用微信Tinker的开源方案,开发者只需要集成我们提供的SDK就可以实现自动下载补丁包、合成、并应用补丁的功能,我们也提供了热更新管理后台让开发者对每个版本补丁进行管理。

  • Activity、window、View的区别,以及Fragment的特点


Activity:是Android 四大组件之一, 是存放View对象的容器,也是我们界面的载体,可以用来展示一个界面。它有一个SetContentView()方法 ,可以将我们定义的布局设置到界面上。

View:就是一个个视图的对象,实现了KeyEvent.Callback和Drawable.Callback。

Window:是一个抽象类,是一个顶层的窗口,它的唯一实例是PhoneWindow它提供标准的用户界面策略,如背景、标题、区域,默认按键处理等。


分析下三者之间的关系吧

      View包含很多,TextView 、Imageview  、Listview 、 Button..就是一个一个展示不同图形的对象。我们可以把view通过xml布局,或者通过new View(),然后通过addview方法或动态或静态添加到Activity的布局上。我们都知道我们定义了layout布局,通过SetContentView就可以设置到Activity上,而Activity中的SetContentView()方法,又调用了Window的SetContentView方法,也就是View通过Activity最终添加到了Window上面。

  • View的绘制流程(上面的自定义View)

  • Android中的事件传递

1).Android事件分发机制的本质是要解决:点击事件由哪个对象发出,经过哪些对象,最终达到哪个对象并最终得到处理。这里的对象是指Activity、ViewGroup、View.

2).Android中事件分发顺序:Activity(Window) -> ViewGroup -> View.

3).事件分发过程由dispatchTouchEvent() 、onInterceptTouchEvent()和onTouchEvent()三个方法协助完成


设置Button按钮来响应点击事件事件传递情况:(如下图)

布局如下:




最外层:Activiy A,包含两个子View:ViewGroup B、View C

中间层:ViewGroup B,包含一个子View:View C

最内层:View C


假设用户首先触摸到屏幕上View C上的某个点(如图中黄色区域),那么Action_DOWN事件就在该点产生,然后用户移动手指并最后离开屏幕。


按钮点击事件:


DOWN事件被传递给C的onTouchEvent方法,该方法返回true,表示处理这个事件;

因为C正在处理这个事件,那么DOWN事件将不再往上传递给B和A的onTouchEvent();

该事件列的其他事件(Move、Up)也将传递给C的onTouchEvent();



(记住这个图的传递顺序,面试的时候能够画出来,就很详细了)


  • 事件分发中onTouch和onTouchEvent的区别

  • 怎么可以让程序在后台不被杀死

  • 动画框架的实现原理

  • 优化自定义View

  • 谈一谈MVP、MVC和MVVM

  • 如何保证网络通信安全(做一个充值系统)

  • 安卓和H5交互有哪些



  • java基础面试题

  1. 接口和抽象类的意义  以及区别

  2. 内部类的作用

  3. 父类的静态方法可不可以被重写,为什么

  4. 举例两个排序算法

  5. 说出java中的集合以及继承关系

  6. 线程和进程的区别

  7. java中“==”和“equals”的区别

  8. 虚拟机的特性

  9. 垃圾回收机制的原理

  10. ArrayList和HashMap的实现原理

  11. int和Integer的区别

  12. String StringBuffer和StringBuild的区别

  13. java多态

  14. 线程的生命周期(状态)

  15. 线程在什么情况下会被堵塞

  16. 什么情况下会造成死锁



以上是关于安卓面试中高级面试的主要内容,如果未能解决你的问题,请参考以下文章

Java进阶之光!2021必看-Java高级面试题总结

超全Android中高级面试复习大纲,安卓系列学习进阶视频

经验总结:Java高级工程师面试题-字节跳动,成功跳槽阿里!

准备两个月,面试五分钟,Java中高级岗面试为何越来越难?

准备两个月,面试五分钟,Java中高级岗面试为何越来越难?

一位30K大佬的面试经验!安卓高级开发岗必问知识点,趁面试赶紧收藏