android开发Android开发面试题

2019Android面试总结

2019-03-07  本文已影响42人  小的橘子

[TODO]

  1. 存储方式网络存储

Java

equals和==、hashCode的区别

  1. 重写equals方法时需要重写hashCode方法,主要是针对Map、Set等集合类型的使用;
    a: Map、Set等集合类型存放的对象必须是唯一的;
    b: 集合类判断两个对象是否相等,是先判断equals是否相等,如果equals返回TRUE,还要再判断HashCode返回值是否ture,只有两者都返回ture,才认为该两个对象是相等的。
  2. 由于Object的hashCode返回的是对象的hash值,所以即使equals返回TRUE,集合也可能判定两个对象不等,所以必须重写hashCode方法,以保证当equals返回TRUE时,hashCode也返回Ture,这样才能使得集合中存放的对象唯一。

JVM内存模型

image.png

JVM 的内存区域可以分为两类:线程私有和区域和线程共有的区域。 线程私有的区域:程序计数器、JVM 虚拟机栈、本地方法栈;线程共有的区域:堆、方法区

类的加载过程

类的生命周期


image.png

其中前五步为加载阶段,而验证、准备、解析又可概括为链接,所以类加载大致的一个流程为加载、链接、初始化,下面我将对这三部分逐个进行详细讲解

  1. 加载
    将类class文件内容加载到内存中,然后把静态数据转换为方法区需要的数据结构 (是转换,并不是将静态数据加载到方法区) ,最后在堆中生成一个类的Class对象用来访问方法区数据。其中class文件的表现形式就是字节数组,所以class文件的来源可以是本地文件、网络、jar包等等。另外加载过程中需要有类加载器参与,在java中类ClassLoader就是类加载器。
  2. 链接
    1.验证: 验证加载进来的class文件各种格式是否符合JVM的要求
    2.准备:为静态变量分配内存,并赋予初始值。这个阶段开发者定义的值不会赋予静态变量并且也不会执行静态代码块。但如果为final修饰的变量会直接赋予开发者定义的值。
    3.解析:将常量池中的符号引用转换为直接引用

符号引用:符号引用是以一组符号来描述所引用的目标,符号可以是任何的字面形式的字面量。
直接引用:是指向目标的指针,偏移量或者能够直接定位的句柄。该引用是和内存中的而布局有关的,并且一定加载进来的。

  1. 初始化
    初始化为类加载过程的最后一个阶段,这个阶段会为静态变量进行赋值,并且顺序执行静态代码块

Java类执行过程

Java类执行过程

类初始化的4种情况

  1. 遇到new,getstatic,putstatic,invokestatic这失调字节码指令时,如果类没有进行过初始化,则需要先触发其初始化。生成这4条指令的最常见的Java代码场景是:使用new关键字实例化对象的时候、读取或设置一个类的静态字段(被final修饰、已在编译器把结果放入常量池的静态字段除外)的时候,以及调用一个类的静态方法的时候。
  2. 使用java.lang.reflect包的方法对类进行反射调用的时候,如果类没有进行过初始化,则需要先触发其初始化。
  3. 当初始化一个类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化。
  4. 当虚拟机启动时,用户需要指定一个要执行的主类(包含main()方法的那个类),虚拟机会先初始化这个主类。

Java引用及GC垃圾回收机制

Java引用及GC垃圾回收机制

Java Exception和Error 异常

JAVA基础——异常详解

Java I/O

Java IO面试题

Java泛型

Java泛型

Java反射

Java反射

Jni介绍

Java多线程

Android

Android 系统架构

android_platform_architecture.png

Android系统架构分为五层

  1. 应用层
  2. 应用框架层(Java API Framework),向应用层提供api,例如:ActivityManager,LocationManager,PackageManager,ContentProvider等
  3. 系统运行库层(Native)
    1.C++程序库 SQLite轻型关系型数据库引擎
    2.Android运行时库 包括核心库和ART虚拟机(Android5.0后,Dalvik虚拟机被ART取代)。核心库提供了Java语言核心库大多数功能,这样开发者才可以使用Java语言编写Android应用。Dalvik虚拟机(DVM)中应用每次运行,字节码都需要通过即时编译器(Just In Time,JIT)转化为机器码,这会使应用运行效率降低。而ART,系统在安装应用时会进行一次预编译(Ahead Of Time,AOT),将字节码预先编译为机器码并存储在本地,这样应用每次运行时就无需编译了,运行效率大大提高。
  4. 硬件抽象层(HAL) 隐藏了特定平台的硬件接口细节,为操作系统提供虚拟硬件平台。
  5. Linux 内核层(Linux Kernel) 系统的内存管理,驱动模型都依赖于该内核。

JVM&DVM&ART区别

Android系统启动过程

Android系统启动流程
  1. 启动电源以及系统启动
    当电源按下时引导芯片代码开始从预定义的地方(固化在ROM)开始执行。加载引导程序Bootloader到RAM,然后执行。
  2. 引导程序Bootloader
    引导程序是在Android操作系统开始运行前的一个小程序,它的主要作用是把系统OS拉起来并运行。
  3. linux内核启动 (Linux 内核层)
    内核启动时,设置缓存、被保护存储器、计划列表,加载驱动。当内核完成系统设置,它首先在系统文件中寻找”init”文件,然后启动root进程或者系统的第一个进程。
  4. init进程启动(Native层)
    init进程解析init.rc文件孵化出Zygote进程,Zygote进程是Android系统第一个Java进程(虚拟机进程),zygote进程是所有Java进程的父进程。
  5. Zygote进程启动(Framework层)
    创建Java虚拟机并为Java虚拟机注册JNI方法,创建服务端Socket,启动SystemServer进程
  6. SystemServer进程启动
    启动各种系统服务,如ActivityManagerService,PackageManagerService,WindowManagerService等。
  7. Launcher启动
    AMS会启动Launcher。

Apk打包流程

myapp.apk
|------assets/   // 原封不动打包
    ---lib/      // 原封不动打包
        --armeabi-v7a
            - libconversation.so
    ---META-INF/    
        --CERT.RSA  // 这个文件保存了签名和公钥证书。
        --CERT.SF
        --MANIFEST.MF
    ---res/   // 所有XML文件都是二进制
        --anim/
        --color/
        --drawable/
        --layout/
        --menu/
        --raw/
        --xml/
        ...
    ---AndroidManifest.xml  // 二进制
    ---classes.dex  //  *.java(通过javac)--->*.class(通过dx工具)--->classes.dex
    ---resources.arsc // 记录了所有的应用程序资源目录的信息,将其想象成是一个资源索引表,这个资源索引表在给定资源ID和设备配置信息(例如设备分辨率)的情况下,能够在应用程序的资源目录中快速地找到最匹配的资源
  1. 通过aapt工具编译所有资源文件(res目录但不包括res/raw)生成R.java(但res/raw资源也会在R.java)供代码引用资源。
    assets和res/raw不参与编译,会原封不动打包到apk,res目录下的其他xml资源和AndroidManifest.xml都会编译为二进制。
    打包工具aapt负责编译和打包资源,编译完成之后,会生成一个resources.arsc文件和一个R.java,前者保存的是一个资源索引表,后者定义了各个资源ID常量。res/values会被直接保存在resource.arsc中。
    那为什么要将文本格式的xml转为二进制格式的xml呢?
    (1) 二进制格式的XML文件占用空间更小。
    (2) 二进制格式的XML文件解析速度更快。
  2. 通过aidl工具将aidl代码转化为java代码
  3. 所有的java文件通过javac编译为class字节码文件(包括R.java和aidl生成的java类)
  4. 将生成的class文件和第三方jar包通过dx工具生成classes.dex
  5. 通过apkbuilder(最新的sdk没有该脚本,是通过sdklib.jar完成)将so文件,classes.dex、resources.arsc、res文件夹(res/raw资源被原装不动地打包进APK之外,其它的资源都会被编译或者处理)、Other Resources(assets文件夹)、AndroidManifest.xml打包成apk文件
    1.res/raw和assets的相同点:
    两者目录下的文件在打包后会原封不动的保存在apk包中,不会被编译成二进制。
    2.res/raw和assets的不同点:
    res/raw中的文件会被映射到R.java文件中,访问的时候直接使用资源ID即R.id.filename;assets文件夹下的文件不会被映射到R.java中,访问的时候需要AssetManager类。
    res/raw不可以有目录结构,而assets则可以有目录结构,也就是assets目录下可以再建立文件夹
  6. jarsigner对apk进行签名,可以进行Debug和Release 签名。
  7. 通过zipalign工具对apk中的未压缩资源(图片、视频)进行“对齐操作”,让资源按4字节的边界进行对齐,使得资源访问速度更快。(优化思想类似结构体变量分配时采用内存对齐)

Activity相关

1. 谈谈onSaveInstanceState(Bundle outState)

onSaveInstanceState一般在onStop之间调用,与onpause没有调用时序关系

  1. 用户行为要Activity关闭,不会执行onSaveInstanceState方法.当用户按下HOME键时或启动其他程序时都会调用onSaveInstanceState
  2. onSaveInstanceStateonRestoreInstanceState不是成对执行,onRestoreInstanceState转屏时会执行

2. onRestoreInstanceState(Bundle savedInstanceState)

onRestoreInstanceState在onResume之前调用,onCreate(Bundle)可能没有数据,但onRestoreInstanceState调用时一定会有.

  1. 其并不是和onSaveInstanceState成对回调,如果Activity并没有销毁也不需要恢复,这种情况下不会调用。
  2. 回调onRestoreInstanceState方法也会将保存的bundle传给onCreate()方法中的bundle

3. onPause和onSaveInstanceState区别

作用不同,onPause适用做数据持久化存储,onSaveInstanceState适用于临时状态数据

4. 4种启动模式介绍

IntentFilter匹配规则

Fragment相关

1. Fragment生命周期

onAttach()->onCreate()->onCreateView()->onActivityCreated()->onStart()->onResume()->onPause()->onStop()->onDestroyView()->onDestroy()->onDetach()

2. Fragment和Activity异同

3. 合适采用Fragment

4. DialogFragment与AlertDialog的优点

5. 同一个Activity不同Fragment的传值

  1. Activity向Fragment传值
    通过Activity中在添加Fragment之前调用setArgument传入Bundle值,Fragment通过getArgument即可得到Bundle
  2. Fragment向Activity传值
    采用接口回调,Fragment中定义接口,Activity实现该接口,需要传值时Fragment调用接口方法即可。Fragment中该接口的具体实现可以通过getActivity直接获得,也可通过set方法传入实现了该接口的对象。
  3. Fragment向Fragment传值
    1. 先由Fragment传值到Activity,再Activity传至Fragment
    2. 得到另一个Fragment的对象,调用其方法即可,eg:左右两个Fragment,左Fragment向右Fragment传值
      LeftFragment
    // 或者调用findFramgentByTag方法
    RightFragment rightFragment =(RightFragment) getFragmentManager().findFragmentById(R.id.right_fragment);
    
    1. 同属一个Activity,可以直接得到目标Fragment的控件进行操作

Service相关

1. Service生命周期

Service两种启动方式生命周期

2. startService和bindService

  1. 启动Service

    context.startService()->onCreate()->onStartCommand()->Service运行->(如果调用context.stopService)->onDestroy
    
    • 多次调用startService()只会走一次onCreate(),每次都会执行onStartCommand()方法
    • 多次调用stopService之后的stopService不会有任何反应
  2. 绑定Service

    context.bindService()->onCreate()->onBind()->Service绑定->(如果调用context.unbindService())->onUnbind()->onDestory();
    
    • 多次调用bindService(),只有第一次会走onCreate()和onBind(),之后的不会走任何方法
    • 多次调用unbindService(),之后的unbind会报错Caused by: java.lang.IllegalArgumentException: Service not registered: com.breeze.b.MainActivity$1@bdbf7e8
    • onBind返回null,onServiceConnected不会被回调
    • Service已经被启动,这时绑定Service解绑后又重新绑定,在onRebind方法返回值为true的情况,这时会执行onRebind方法而不是调用bind
  3. Service可以同时处于绑定状态和运行状态,只要Service存在则再start或bind就不会回调onCreate方法,当Service被start并且bind,需要同时stop和unbind才会回调onDestory()

  4. 启动方式和绑定方式区别

    1. 启动方式一旦启动Service,Service就一直运行在后台,除非调用自身stopSelf()方法,或者context.stopService();绑定方式绑定Service后,如果客户端调用unbindservice或者客户端退出后Service都会销毁,其与客户端声明周期有关
    2. 启动方式,调用方式无法与Service进行交互,只能启动或关闭Service;绑定方式可以通过返回的IBinder接口调用服务端的代码,从而实现交互。

3. Service如何和Activity进行通信

通过bindService可以实现Activity调用Service的方法.通过广播实现Service向Activity发送消息

4. IntentService

用于后台执行耗时任务,执行完成并可自动停止.比线程优先级高. 实现原理是通过HandlerThread消息机制.

5. Android8.0不允许启动后台Service

切换为前台Service,但必须有通知.或者通过JobIntentService实现.

6. 如何保证Service不被杀死

Broadcast相关

广播的类型主要分为5类:

1. 普通广播(Normal Broadcast)

普通广播接收没有先后顺序.最常用,分为动态注册和静态注册。但静态注册在Android8.0上做了限制,只能接收部分系统广播.

2. 系统广播(System Broadcast)

系统操作 action
监听网络变化 android.net.conn.CONNECTIVITY_CHANGE
关闭或打开飞行模式 Intent.ACTION_AIRPLANE_MODE_CHANGED
充电时或电量发生变化 Intent.ACTION_BATTERY_CHANGED
系统启动完成后(仅广播一次) Intent.ACTION_BOOT_COMPLETED
屏幕被关闭 Intent.ACTION_SCREEN_OFF
屏幕被打开 Intent.ACTION_SCREEN_ON

3. 有序广播(Ordered Broadcast)

4. 粘性广播(Sticky Broadcast)

粘性消息在发送后就一直存在于系统的消息容器里面,等待对应的处理器去处理,如果暂时没有处理器处理这个消息则一直在消息容器里面处于等待状态,粘性广播的Receiver如果被销毁,那么下次重建时会自动接收到消息数据。(在 android 5.0/api 21中deprecated,不再推荐使用)

耳机插拔广播Intent.ACTION_HEADSET_PLUG就是粘性广播

5. App应用内广播(Local Broadcast)

Android中的广播可以跨App直接通信(exported对于有intent-filter情况下默认值为true).

存在的问题

App应用内广播可理解为一种局部广播,广播的发送者和接收者都同属于一个App。相比于全局广播(普通广播),App应用内广播优势体现在:安全性高 & 效率高。通常采用如下两种方式实现本地广播

  1. 增设相应权限permission或者通过intent.setPackage(packageName)指定包名接收
  2. v4包下LocalBroadcastManager实现localBroadcastManager = LocalBroadcastManager.getInstance(this);获取其实例,通过其发送广播和接收广播。

ContentProvider相关

ContentProvider四大组件之一,严格意义不属于数据存储,它实现存储方式是通过其他存储方式实现(文件存储,SharedPrefernces,SQLite数据).ContentProvider主要是用于不同应用之间数据共享.

数据存储方式有哪些?

  1. File文件存储: 写入和读取文件的方法和Java中实现I/O的程序一样
  2. SharedPreferences: 轻型数据存储方式,常用与存储一些简单配置信息,本质是通过XML文件存储键值对数据
  3. SQLite数据存储:轻型关系型数据库,速度快,占用资源少,适合存储大量复杂关系型数据
  4. ContentProvider: 实现数据存储与共享
  5. 网络存储 使用较少,暂不记录
    Android数据存储的5种方式

1. SharedPreferences适用情形?适用中需要注意什么?

适用情形上面已说明.SharedPreferences底层通过读/写XML文件来实现,并发容易出问题.从而可靠性下降

Android进程和线程区别

IPC

1. 为什么要进行IPC,多进程可能出现什么问题

由于每个应用都运行在独立的虚拟机,不同虚拟机内存地址空间不同,从而不同虚拟机访问同一个对象会产生多个副本,故涉及到内存共享数据,都会共享失败,一般会造成如下几个问题。

  1. 静态成员和单例模式完全失效
  2. 线程同步完全失效
    不同进程的锁永远不会是同一个对象
  3. SharedPreference可靠性下降
    SharedPreference不支持两个进程同时执行写操作,否则会引起一定概率的数据丢失
  4. Application多次创建

2. Serializable接口和Parcelable接口的区别

3. Android中为何新增Binder来作为主要的IPC方式?

  1. 传输效率高、可操作性强:传输效率主要影响因素是内存拷贝的次数,拷贝次数越少,传输速率越高。从Android进程架构角度分析:对于消息队列、Socket和管道来说,数据先从发送方的缓存区拷贝到内核开辟的缓存区中,再从内核缓存区拷贝到接收方的缓存区,一共两次拷贝,共享内存虽然无需拷贝,但控制复杂,难以使用.
  2. 安全性 传统Linux IPC的接收方无法获得对方进程可靠的UID/PID,从而无法鉴别对方身份;而Binder机制为每个进程分配了UID/PID且在Binder通信时会根据UID/PID进行有效性检测。

4. Binder机制?

以AIDL运行机制介绍


  1. 服务端中的Service给与其绑定的客户端提供Binder对象
  2. 客户端通过AIDL接口中的asInterface()将这个Binder对象转换为代理Proxy,通过它发起RPC请求。客户端发起请求时会挂起当前线程,并将参数写入data然后调用transact()方法,RPC请求会通过系统底层封装后由服务端的onTransact()处理,并将结果写入reply,这个过程运行在服务端中的Binder线程池,最后返回调用结果并唤醒客户端线程。


    image.png

5. 是否了解AIDL?原理是什么?如何优化多模块都使用AIDL的情况

当有多个业务模块都需要AIDL来进行IPC,此时需要为每个模块创建特定的aidl文件,那么相应的Service就会很多。必然会出现系统资源耗费严重、应用过度重量级的问题。解决办法是建立Binder连接池,即将每个业务模块的Binder请求统一转发到一个远程Service中去执行,从而避免重复创建Service

每个业务模块创建自己的AIDL接口并实现此接口,然后向服务端提供自己的唯一标识和其对应的Binder对象。服务端只需要一个Service,服务器提供一个queryBinder接口,它会根据业务模块的特征来返回相应的Binder对像,不同的业务模块拿到所需的Binder对象后就可进行远程方法的调用了

6. IPC有哪些,各自优缺点适用场景

  1. Intent数据传递
    使用Bundle进行传递数据在Activity,Service,BroadcastReceiver中,数据支持类型有
    1.8种基本类型及数组
    2.String(实现了Serializable),CharSequence及对应数组
  2. 是实现了Serializable或者Parcelable的对象或数组
    Bundle支持传输的最大数据为1M,甚至更小,因为Binder会话缓冲区大小限制为1M,它是被所有处于Binder会话的进程锁共享的。
  3. 文件共享 (适合对并发要求不高的进程之间使用)
    • ObjectOutputStream/ObjectInputStrem实现读写对象到文件 文件可以是各种格式,只要双方约定好即可
    • 并发读写容易引起问题
  4. SharedPreference (不建议使用)
    • 本质上也是文件共享的一种
    • 系统对其有缓存策略,即内存中存在其缓存,多进程对其读写不可靠
    • 高并发容易引起数据丢失
  5. 基于Binder的Messager和AIDL
  6. Socket
  7. LocalSocket
    LocalSocket解决的是同一台主机上不同进程间互相通信的问题。其相对于网络通信使用的socket不需要经过网络协议栈,不需要打包拆包、计算校验,自然的执行效率也高。与大名鼎鼎的binder机制作用一样,都在Android系统中作为IPC通信手段被广泛使用

android下使用localsocket可以实现C与C,C与JAVA,JAVA与JAVA进程间通信

IPC优缺点对比

1. MotionEvent是什么?包含几种事件?什么条件下会产生?

MotionEvent是手指触摸屏幕锁产生的一系列事件。包含的事件有:
ACTION_DOWN:手指刚接触屏幕
ACTION_MOVE:手指在屏幕上滑动
ACTION_UP:手指在屏幕上松开的一瞬间
ACTION_CANCEL:手指保持按下操作,并从当前控件转移到外层控件时会触发

2. View的事件分发机制?

触摸事件传递顺序是Activity,ViewGroup,View
而在事件分发过程中,涉及到三个最重要的方法:

public boolean dispatchTouchEvent(MotionEvent ev) {
    //consume 表示事件是否在该View中消耗
    boolean consume = false;
    //是否当前View拦截事件,如果拦截,则调用本View的onTouchEvent方法,不会再下发事件序列中的其他事件
    if (onInterceptTouchEvent(ev)) {
        consume = onTouchEvent(ev);  
    } else {
        //如果没有拦截事件,则会遍历手指按下时接触的所有子View(不是容器的所有子View,而是该容器中down事件所接触的子View)进行遍历派发事件,事件如果有消耗则退出循环,如果所有字View均没有消耗,则此时consume为false
        for(int i=0; i<childs.length && !consume;i++) {
            consume = childs[i].dispatchTouchEvent(ev);
        }
        
    }
    如果派发给所有字view事件依然没有消耗,则有本View的onTouchEvent来进行处理,如果依然返回false,则当前View没有消耗事件,进一步上发事件给父View,如果顶层View依然没有处理,则Activity的onTouchEvent会被调用.
    if(!consume) {
        consume = onTouchEvent(ev);       
    }
    return consume;
}

Android 子线程访问主线线程更新UI && Handler原理

1. 为什么Android无法在子线程更新UI

实质上是为了避免多线程并发问题,容易引起界面更新错乱。如果通过加锁的机制实现同步,则非常消耗性能。

2. 其他线程访问UI线程的5种方式:

  1. Activity.runOnUiThread(Runnable);
  2. View.post(Runnable);
  3. View.postDelayed(Runnable,long);
  4. Handler;
  5. AsyncTask。
    上述5种方式包括AsyncTask本质都是通过Handler来实现的

3. Handler原理

Handler原理如下:

一个线程通过Looper.prepare会创建唯一的Looper对象,创建Looper中就会创建一个消息队列MessageQueue(数据结构是单链表队列)。创建Handler时会与当前线程的Looper及MessageQueue绑定,调用sendMessage方法发送消息Message,该消息Message会入队到MessageQueue。线程调用Looper.loop方法就会循环从MessageQueue中取消息并将该消息通过Handler的dispatchMessage方法将Message交由Handler处理。Looper负责一直从消息队列拿消息交由Handler来处理消息。

一个线程只能有一个Looper对象,并且有一个消息队列,但可以有多个Handler对象。

4. 主线程中的Looper.loop()一直无限循环为什么不会造成ANR?

造成ANR的原因一般有两种:

  1. 当前的事件没有机会得到处理(即主线程正在处理前一个事件,没有及时的完成或者looper被某种原因阻塞住了)
  2. 当前的事件正在处理,但没有及时完成

当只在UI线程延时很长时间,是不会引起ANR,如果按钮点击后延时,按钮则弹起后无法恢复,直到延时结束,如果按钮弹起,这时还要处理其他事件则会引起ANR

因为Android 的是由事件驱动的,looper.loop() 不断地接收事件、处理事件,每一个点击触摸或者说Activity的生命周期都是运行在 Looper.loop() 的控制之下,如果它停止了,应用也就停止了。只能是某一个消息或者说对消息的处理阻塞了 Looper.loop(),而不是 Looper.loop() 阻塞它。也就说我们的代码其实就是在这个循环里面去执行的,当然不会阻塞了。

5. HandlerThread

HandlerThread继承自Thread,它就是一个线程,不过做了对looper的封装。

HandlerThread的run方法中开始执行了Looper.prepare方法,在run方法结尾调用了Looper.loop方法,Looper.loop之前回调onLooperPrepared方法供子类可以进行一些设置。当在其他线程创建Handler时,需要传入HandlerThread的getLooper方法,这样该Handler才与该线程的Looper绑定了,从而Handler发送的消息才会进入该线程的MessageQueue中,Looper.loop取得消息才可以交由该Handler处理。

当线程无需处理消息时,可通过HandlerThread的quit或者quitSafely方法退出消息循环,从而run方法执行完成,该线程也就终止了。quit和quitSafely区别在于quit消息队列中的全部消息,而quitSafely只清除消息队列中的延迟消息。

6. AsyncTask相比Handler有什么优点?不足呢?

7. 使用AsyncTask需要注意什么?

一个异步对象只能调用一次execute()方法

线程池和阻塞队列

上一篇下一篇

猜你喜欢

热点阅读