Android面试题精选

14 篇文章 1 订阅

目录

内容持续更新中…

内存相关

1 导致内存泄漏的原因有哪些?

内存泄漏的根本原因是:长生命周期的对象持有短生命周期的对象,短生命周期的对象就无法及时释放。

造成内存泄漏的原因有:
(1) 单例造成的内存泄漏

因为某些单例的初始化需要引入一个Context变量,在调用单例时会传入当前类的实例,而单例的对象是静态的,那么它将与Application拥有一样长的生命周期,如果当前类需要销毁,由于单例持有该类的引用,所以无法销毁,从而造成内存泄漏,代码如下:

public class Singleton{
    private Singleton(Context context){
    }
    private static volatile Singleton instance = null;
    
    public static Singleton getInstance(Context context){
        if(instance==null){
            synchronized(Singleton.class){
                if(instance==null){
                    instance = new Singleton(context);
                }
            }
        }
        return instance;
    }
}

解决方法:使用getApplicationContext或者对Activity的引用使用弱引用。

(2)非静态内部类实例化一个静态的对象

非静态内部类隐性持有外部类的对象,如果将内部类实例化为一个静态对象,那么它将与Application拥有一样长的生命周期,如果外部类需要销毁,由于内部类持有该类的引用,所以无法销毁,从而造成内存泄漏,代码示例如下:

public class MyActivity extends Activity{
    private static InnerClass innerclass = new InnerClass();
    protected void onCreate(Bundle savedInstanceState){
        super.onCreate(saveInstanceState);
        
    }
    private class InnerClass{
    }
}

解决办法有两个,一个是不要把对象实例化成静态的;另外一个是把内部类改为静态内部类,静态内部类不会隐性持有外部类对象
(3) Handler/Runnable造成的内存泄漏

Handler是一个非静态内部类,它隐性地持有外部类的对象。如果外部类需要结束,但消息队列中还有消息未处理完,则Handler不会释放外部类的对象,从而造成内存泄漏,代码示例如下:

Handler handler = new Handler(){
    public void handleMessage(Message msg){
        super.handleMessage(msg);
    }
};

解决方法:

  • 将Handler/Runnable改为静态的,如果是Handler,则在onDestroy中调用removeCallbackAndMessage(null)方法。
  • 如果Handler/Runnable持有外部类的对象,则应该改成弱引用。

(4)资源未关闭造成的内存泄漏

如使用流资源,比如InputStream,Database等,还有就是BroadcastReceiver未取消注册等。

2 导致内存溢出的原因有哪些?

内存溢出通俗理解就是内存不够用,内存溢出有以下几种原因:

  1. 从Java虚拟机角度来说
  • Java堆溢出
    Java堆用于存储对象实例的。只要不停创建对象,然后保证对象到GC Roots之间有可达路径来避免垃圾回收机制回收这些对象,那么对象数量达到最大堆容量后就会出现内存溢出,所以内存泄漏可能造成内存溢出。
  • 虚拟机栈和本地方法栈溢出
    如果线程请求的栈深度大于虚拟机所允许的最大深度,则抛出StackOverflowError异常;如果虚拟机在扩展时无法请求到足够的内存空间,则抛出OutOfMemoryError异常。
  • 方法区和运行时常量池溢出
    这类溢出经常出现在使用的第三方软件中的BUG。
  • 本地直接内存溢出
  1. 从App实际应用来说
  • 图片造成内存溢出
  • 内存泄漏导致的内存溢出
  • ListView未复用ConvertView导致重复创建Item布局最终可能导致内存溢出

3 如何避免OOM?

  1. Bitmap优化
  • 压缩图片尺寸,避免使用大图片
  • 设置合适的format,ARGB_6666/RBG_545/ARGB_4444/ALPHA_6,存在很大差异
  1. 使用StringBuilder替代String
    有时候代码中会需要使用到大量的字符串拼接操作,这种时候有必要考虑使用StringBuilder来替代频繁的“+”。
  2. 可以复用系统自带的资源,如字符串、图片、颜色、样式等;ListView和GridView中可以使用ConvertView复用子组件。
  3. 避免内存泄漏从而避免内存溢出
    因为内存泄漏也可能导致内存溢出。
  4. 避免在类似onDraw这样的方法中创建对象,因为它会迅速占用大量内存,引起频繁的GC,甚至内存抖动

Android原理相关

1 理解Activity、View、Window三者之间的关系

可以说,Window加载在Acitivy上,View加载在Window上,它们之间的逻辑关系如下:

  1. Activity构造的时候会初始化一个Window对象,准确说是PhoneWindow对象。
  2. 这个PhoneWindow有一个DecorView,DecorView有一个唯一的子View,它是垂直的LinearLayout类型,包含两个子View,一个是TitleView,另一个是ContentView.
  3. ContentView是一个FrameLayout布局,通过setContentView方法来添加View。比如TextView、Button等。
  4. 这些View的事件监听是由WindowManagerService来接受消息,并且回调Activity函数,比如onClickListener,onKeyDown等。

具体可参考下图
在这里插入图片描述

2 Handler机制(消息机制、Handler原理)

可参考文章:Android消息机制
简单回答:

Handler是用来发送消息和处理消息的。Handler流程中最主要的有四个对象:Handler、Message、MessageQueue和Looper。我们将要发送的消息封装到Message里,然后通过Handler将消息发送到MessageQueue中,Looper不停地从MessageQueue里取出消息交给Handler进行处理,从而实现了线程之间的通信。

详细的回答:

Handler是用来发送消息和处理消息的,所以Handler的机制主要表现为消息机制。Handler流程中最主要的有四个对象:Handler、Message、MessageQueue和Looper。从消息发送到处理可分为以下四个阶段:

  1. 准备阶段
    在子线程中Looper.prepare()方法或者在主线程中调用Looper.prepareMainLooper()方法创建当前的Looper对象。Looper通过loop()方法获取到当前线程的Looper并启动循环,从MessageQueue不断提取Message,如果MessageQueue没有消息,则处于阻塞状态
  2. 发送消息阶段
    使用当前线程创建的Handler对象在其它线程通过调用sendMessage()发送Message到MessageQueue中,MessageQueue收到消息,然后插入新的Message并唤醒阻塞。
  3. 获取消息
    Looper被唤醒,则重新检查MessageQueue并获取新插入的Message。Looper获取Message后,通过Message的target,即Handler调用dispatchMessage方法分发获取到的消息,然后Handler使用handleMessage方法处理消息。
  4. 阻塞等待
    当MessageQueue没有消息时,重新进入阻塞状态。

3 Android事件分发

当发生了一个事件时,会将该事件进行传递,从父控件往子控件传递,如果有控件拦截该事件,则消费该事件并不再往下传递;如果没有拦截该事件,则继续往下传递;如果最终的子控件也没有消费该事件,则往上一级回传该事件,直到被消费为止;如果最终都没有被消费,则该事件被抛弃。

  • 红色箭头方向表示事件分发方向
  • 绿色箭头方向表示事件回传方向。
    事件分发流程

【四大组件】Activity相关

1 说说Activity的启动模式

Activity的启动模式包含四种:Standard,SingleTop,SingleTask,SingleInstance.

  • Standard:Android默认的模式。每次启动Activity,无论Activity栈中是否已经有这个Activity的实例,系统都会创建一个新的Activity实例。
  • SingleTop:当一个SingleTop模式的Activity处于栈顶时,再去启动它时,系统不会再去创建一个Activity实例,而是直接复用这个实例;如果不在栈顶,则系统会重新创建新的实例。
  • SingleTask:位于栈顶,则直接复用;如果不位于栈顶,则将其上的所有Activity实例出栈,然后再复用。
  • SingleInstance:会重新创建一个单独的任务栈。

2 Activity的生命周期有哪些?Activity在什么时候可见在什么时候不可见。

在这里插入图片描述

Activity生命周期图

Activity生命周期包括:onCreate、onStart,onRestart、onResume,onPause,onStop,onDestroy。Activity在onStart到onPause之间都是可见的,其它不可见(可见和是否处于前台是不一样,处于前台是从onResume到onPause,而且google官方文档也是将这两种状态分开的,在Activity源码也可以看到这两个状态是不一样的)。

3 Activity各个阶段可以做哪些事?

  1. onCreate:该方法在Activity被创建时回调,它是Activity生命周期的第一个方法。我们在创建Activity时一般都需要重写该方法,然后在该方法中做一些初始化的操作,如通过setContentView设置界面布局的资源,初始化所需要的组件信息等。
  2. onStart:此方法被回调时表示Activity正在启动,此时Activity已处于可见状态,只是还没有在前台显示,因此无法与用户进行交互。可以简单理解为Activity已显示而我们无法看见罢了。
  3. onResume:当此方法回调时,则说明Activity已在前台可见,可与用户交互了,onResume方法和onStart方法相似的地方在于,此时的Activity已经可见,只是区别在于onStart被回调时Activity还处于后台,无法与用户进行交互;而onResume被回调时Activity已经处于前台,可以与用户进行交互。如上述流程图所示,在Activity被停止后(onPause和onStop被调用),重新回到前台也会调用onResume方法,因此可以在onResume方法中初始化一些资源。
  4. onPause:调用此方法时Activity被暂停,一般onStop方法会紧接着被调用。但也有特殊情况,如流程图所示,另外的Activity2来到前台而使当前Activity1转入到后台的情况,当然这里的另外的Activity2必须设置成窗口模式使得Activity处于可见状态,此时按返回键则会直接调用onResume方法。在onpause方法中,我们可以做一些数据的存储或者动画停止或者资源回收操作(比较少做此操作),但是不能做耗时操作,因为耗时操作可能会影响到新的Activity的显示,因为旧的Activity的onPause方法执行完成后,新的Activity的onResume方法才会被执行。
  5. onStop:一般是在onPause被调用,且Activity完全不可见时被调用。同样的,在onStop方法中可以做一些资源释放操作(比较少做此操作)。
  6. onRestart:表示Activity被重新启动,当Activity由不可见状态变为可见状态时该方法被回调。
  7. onDestroy:这是Activity生命周期的最后一个方法,执行Activity的销毁操作,一般我们在这里进行回收工作和最终的资源释放。

【四大组件】Service

1 Service可以分几类

  • 按照服务所在地分为:远程服务与本地服务。
  • 按照是否可以执行耗时操作可以分为:IntentService和普通Service

2 InentService与普通Service的区别

  • intentService可以执行耗时操作。在IntentService里的onHandleIntent里做耗时操作,且该方法里,每次都只执行一条任务,在所有任务都执行完后,会自动关闭服务。
  • 普通Service,一般情况下是不能做耗时操作的,它是运行在UI线程中的。

3 Service启动方式

Service启动方式分为两种:

  • startService:通过startService启动后,service会一直无限期运行下去,只有外部调用了stopService()或stopSelf()方法时,该Service才会停止运行并销毁。且Service里面的方法不能被调用
  • bindService:通过bindService启动后,Service可以和开启者通信,而且开启者被销毁时,Service会自动解绑,也可以通过方法unbindService方法解绑。

4 Service两种启动方式的生命周期

在这里插入图片描述

  • onCreate()
    如果service没被创建过,调用startService()后会执行onCreate()回调; 如果service已处于运行中,调用startService()不会执行onCreate()方法。
  • onStartCommand()
    如果多次执行了Context的startService()方法,那么Service的onStartCommand()方法也会相应的多次调用。onStartCommand()方法很重要,我们在该方法中根据传入的Intent参数进行实际的操作,比如会在此处创建一个线程用于下载数据或播放音乐等。
  • onBind()
    Service中的onBind()方法是抽象方法,Service类本身就是抽象类,所以onBind()方法是必须重写的,即使我们用不到。当一个组件想通过调用 bindService() 与服务绑定时,系统将调用此方法(如果是startService则不会调用此方法)。在此方法的实现中,必须通过返回 IBinder 提供一个接口,供客户端用来与服务进行通信
  • onDestory()
    在销毁的时候会执行Service该方法。

【四大组件】BroadcastReceiver

1 广播有几种?

目前广播只有3种,粘性广播已被移除。

  • 普通广播
    普通广播在发出之后,所有广播接收器几乎都会在同一时间内接收到该广播。
  • 有序广播
    广播发出后,同一时刻,只会有一个广播接收器能接收到这条广播消息,当这个广播接收器逻辑执行完毕后,广播才会继续传递。它是根据优先级priority来确定谁先接收的,priority数值越大,优先级越高。
  • 本地广播
    本地广播只能在应用程序内部执行,使用LocalBroadcastManager来对广播进行管理。

2 广播有几种注册形式?区别在哪里?

  • 静态注册:常驻系统,不受组件生命周期影响,即便应用退出,还是可以接收到广播,耗电、占内存。
  • 动态注册:非常驻,跟随组件的生命周期变化,组件结束,广播结束。在组件结束前,需要先移除广播,否则容易造成内存泄漏。

【四大组件】ContentProvider

1 为什么要使用ContentProvider?

contentProvider为存储和读取数据提供了统一的接口,其它进程如果需要访问该进程的数据,就可以通过该接口访问,实现了数据的共享。

2 使用ContentProvider的步骤

  • 新建一个继承ContentProvider的类
  • 在manifest里面配置ContentProvider,并给其添加authorities属性
  • 声明并实例化一个UriMatcher对象
  • 在静态代码块中添加Uri
  • 然后暴露需要暴露的方法

Fragment相关

1 Fragment的生命周期有哪些?

在这里插入图片描述解释如下:

  • onAttach():Fragment和Activity相关联时调用。可以通过该方法获取Activity引用,还可以通过getArguments()获取参数。
  • onCreate():Fragment被创建时调用。
  • onCreateView():创建Fragment的布局。
  • onActivityCreated():当Activity完成onCreate()时调用。
  • onStart():当Fragment可见时调用。
  • onResume():当Fragment可见且可交互时调用。
  • onPause():当Fragment不可交互但可见时调用。
  • onStop():当Fragment不可见时调用。
  • onDestroyView():当Fragment的UI从视图结构中移除时调用。
  • onDestroy():销毁Fragment时调用。
  • onDetach():当Fragment和Activity解除关联时调用。

2 Fragment和Activity的生命周期是如何对应的

可以参考如下图:

在这里插入图片描述

3 Activity向Fragment传递数据

  • 获取Fragment对象,并调用Fragment方法,如Fragment中成员变量的setXX()方法。
  • 通过setArguments传参数,通过getArguments获取参数

4 Fragment向Activity传递数据

  • 使用SharedPreference
  • 使用文件
  • 在Fragment中定义接口,Activity实现该接口
public interface OnFragmentInteractionListener {    
	void onItemClick(String str);  //将str从Fragment传递给Activity
}

5 Fragment有几种加载方式

Fragment有两种加载方式,分别是

  • 静态加载:直接在XML布局里面声明,如普通控件一样
  • 动态加载:动态加载有两种方法
    (1)通过使用replace方法:replace方法每次都会将当前的Fragment移除并销毁,然后加载新的Fragment,这样会调用Fragment的各个生命周期方法,如果Fragment里的数据比较多,布局比较复杂,这样加载会比较慢,可能比较卡。
    (2)通过使用add、show、hide来显示,则Fragment的生命周期都不会被调用,但是界面数据可能不是最新的,要自己手动刷新。

Android功能优化相关

1 Android如何实现进程保活

  1. 双进程互相唤醒
  2. 1像素Activity
    在手机屏幕黑屏时,我们启动一个1像素的Activity,其占用内存很小毕竟只有1像素嘛,无形中减小了内存的回收几率,在屏幕亮的时候就关闭该页面。

2 Android的冷启动与热启动

冷启动:启动时,后台没有该应用的进程,系统会重新创建一个进程分配给该应用。
热启动:后台有该应用的进程,再次启动时不需要重新创建进程。所以不会走Application这步,直接走的MainActivity。

3 为什么冷启动会有白屏黑屏问题?如何解决?

因为在加载主题样式Theme中的windowBackground等属性给Activity发生在onCreate/onStart/onResume方法之前,而windowBackground背景被设置成了白色或者黑色,所以我们进入app的第一个界面时会先白屏一会儿。解决方法有:

  • 设置windowBackground背景跟启动页的背景相同。
  • 禁用Preview Window,即设置windowDisablePreview为true
  • 给Activity设置一个透明的主题

Android进程与线程相关

1 Android进程间(IPC)通信

Android进程间通信方式有以下几种:

  1. Bundle实现进程间通信
  2. 使用文件
  3. AIDL
  4. ContentProvider
  5. Messenger
  6. Socket

2 ThreadLocal的理解

详解

  1. ThreadLocal不是为了解决线程安全而提出的,存放在ThreadLocal的变量是当前线程本身独一无二的变量,其它线程不能访问。
  2. ThreadLocal的设计原理是将ThreadLocalMap的引用作为Thread的一个属性,利用当前ThreadLocal作为Key,保存的变量值作为value保存在当前线程的ThreadLocalMap中,所以ThreadlocalMap是伴随Thread本身存在而存在的。

3 与主线程通信的几种方式

  1. 通过Handler机制
  2. 使用runOnUiThread方法
  3. View.post(Runnable r)方法
  4. AsyncTask方法

4 子线程之间通信方式

  1. 使用wait()、notify()、notifyAll()三个方法
  2. Handler方法

设计模式相关

1 Android中常见的设计模式有哪些?

  • 单例模式
  • Builder模式
  • 观察者模式
  • 原型模式(克隆模式)
  • 策略模式
  • 责任链模式

2 手写一个单例模式

public class Singleton{
	private volatile static Singleton instance = null;
	private Singleton(){
	}
	public static Singleton getInstance(){
		if(instance==null){
			synchronized(Singleton.class){
				if(instance==null){
					instance = new Singleton();
				}
			}
		}
		return instance;
	}
}

3 这些常见的模式在哪些地方使用到了,举下例。

  • 单例模式:单例模式在ActivityManager、LocalBroadcastManager等系统服务中使用。
  • Builder模式:在okHttp3中大量使用,如OkHttpClient,Request等。还有如AlertDialog等。
  • 观察者模式:广播机制,EventBus,事件监听等
  • 原型模式:Bundle类,Intent类、OkHttpClient等
  • 策略模式:Adapter
  • 责任链模式:OkHttpClient中的拦截器

动画相关

1. Android有哪几种动画?

Android一共有3种动画:逐帧动画、补间动画和属性动画。

2. 这些动画的原理是什么?

  • 逐帧动画

原理:通过一系列静态图片依次播放,利用人眼“视觉暂留”的原理实现动画。

  • 补间动画

原理:补间动画就是指开发者指定动画的开始和结束时的“关键帧”,而动画变化的“中间帧”由系统计算并补齐。补间动画有4种方式:淡入淡出(alpha),位移(translate),缩放(scale),旋转(rotate)

  • 属性动画

是对于对象属性的变化。它是通过不断地对值进行操作来实现的,初始值和结束值之间的动画过度就是由ValueAnimator这个类来计算。它的内部使用一种时间循环的机制来计算动画过渡的,我们只需要将初始值和结束值提供给ValueAnimator,并告诉它动画所需要运行的时长,那么ValueAnimator就会自动帮我们完成从初始值平滑地过度到结束值的相关。

3. 属性动画和补间动画的优缺点是什么?

  1. 属性动画真正实现了View的移动,补间动画对View的移动更像是在不同的地方绘制了一个影子,实际的对象还是处于原来的地方。
  2. 属性动画可能会使Activity无法释放而造成内存泄漏,而补间动画没有。因此,使用属性动画时切记在Activity执行onStop时顺便将动画停止。
  3. xml文件实现补间动画,复用效率极高,在Activity切换、窗口弹出等情景中有着很好的效果。

其他

1 实现弹窗有哪几种方式?

  1. AlertDialog
  2. Activity实现弹窗,设置Activity的theme为Theme.Dialog
  3. 使用popupWindow实现弹窗
    (1)写一个弹窗的xml布局,然后加载该布局
    (2)声明并实例化一个PopupWindow对象
    (3)设置PopupWindow的相关属性

2 什么情况下会出现ANR?

  1. 按键和触摸事件5s内没被处理。
  2. BroadcastReceiver事件在规定的时间内没有处理完成。前台广播为10s,后台广播为60s
  3. Service,前台20s,后台200s未完成启动

3 如何避免ANR?

  1. 不要在主线程里进行耗时操作,如网络请求,数据库查询等
  2. 不要在BroadcastReceiver的onReceive()方法中执行耗时操作,如果要执行耗时操作,可以开线程或者使用IntentService。
  3. 主线程中避免死锁的发生。

4 ListView和RecyclerView的缓存对比

  1. 层级不同。ListView是两级缓存,RecyclerView是四级缓存
  • ListView缓存两级缓存:
    (1)mActiveViews:用于屏幕内ItemView快速重用
    (2)mScrapViews:用于缓存离开屏幕的ItemView,目的是让进入屏幕的ItemView复用
  • RecyclerView缓存四级缓存:
    (1)mAttachedScrap:用于屏幕内的ItemView快速复用
    (2)mCacheViews:默认上限为2,缓存屏幕外的两个ItemView
    (3)mViewCacheExtension:用户定制的缓存
    (4)mRecyclerPool:默认上限为5,主要用于存储ViewHolder,可供多个RecyclerView使用
  1. 缓存不同。
  • RecyclerView的缓存可以抽象理解为View+ViewHolder+flag。
  • ListView缓存View。

ListView获取缓存的流程:
图1.1 ListView获取缓存的流程图
RecyclerView获取缓存流程
图1.2 RecyclerView获取缓存流程图

5、RecyclerView与ListView的对比优缺点。

  1. ListView只支持垂直布局,而RecyclerView支持线性布局(垂直、水平)、网格布局和瀑布流布局
  2. ListView和RecyclerView的缓存机制不同
  3. RecyclerView封装好了ViewHolder,而ListView需要自己写。
  4. RecyclerView可以局部刷新,只需要调用notifyItemChange(),而ListView则没有实现局部刷新,如果需要局部刷新则需要自己写。
  5. ListView可以通过addHeaderView()和addFooterView()给布局添加头部Item和底部Item;但RecyclerView并没有这两个方法,需要头部和底部则需要自己在adapter里面编写
  6. RecyclerView里封装好了自己的动画效果,而ListView里则没有
  7. ListView里面有各种点击监听,如onItemClickListener()、onItemLongClickListener()等;但RecyclerView里面则没有,它只提供了一个addOnItemTouchListener()监听,其它监听效果则需要自己去实现。

6 ListView优化

  1. convertView的使用,主要优化加载布局问题
  2. ViewHolder的使用,主要优化getView方法中控件绑定问题
  3. 数据优化,可以分页加载
  4. 对于图片,使用缓存机制;如果加载的是网络图片,则滑动时不要加载图片,等到滑动结束后再加图片;压缩图片尺寸。
  5. 减少ItemView的布局层级
  6. getView中避免大量创建对象
  7. getView中尽量少做耗时操作

7 什么是图片的三级缓存?

图片的三级缓存指的是:

  • 内存缓存
  • 本地缓存
  • 网络缓存

三级缓存原理:首次加载APP时,通过网络请求将获取到的图片保存到内存和本地SD卡中,之后再次运行时,优先访问内存中的图片缓存,如果内存中没有,则再加载本地SD卡中的缓存,如果本地缓存中也没有,则再进行网络请求访问。

  • 10
    点赞
  • 35
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值