Android开发探索android面试录好好找job

阿里百度腾讯常见java和安卓面试题汇总

2018-02-26  本文已影响839人  编程小石头666

安卓部分

一,view的事件分发机制

dispatchTouchEvent 分发事件
onInterceptTouchEvent 拦截事件只有viewgroup才有,view和activity没
onTouchEvent 处理点击事件

image image

二,Handler原理机制

Handler主要负责发送和接受消息,Looper负责不断轮询MessageQueue,有新的消息就交给Handler处理,如果轮询不到新的消息,那就自身就处于阻塞状态。
Handler简单图解

image

Handler铁三角

详解单个handler原理

image

图解多个handler原理

image

Handler的创建流程

四,mvc,mvp,mvvm

三个架构模式:

Mvc和mvp的最主要区别:
Mvc中model可以直接和view交互
mvp中model   与view   的交互由presenter完成
image image

一,LRUCache原理和部分源码解析

图片的三级缓存:也就是加载图片的时候首先从内存缓存中取,如果没有再从文件缓存中取, 如果文件缓存没有取到,就从网络下载图片并且加入内存和文件缓存
LRU:Least Recently Used,最近最少使用算法。当内存缓存达到设定的最大值 时将内存缓存中近期最少使用的对象移除,有效的避免了OOM的出现。

LruCache中Lru算法:通过LinkedHashMap来实现的。LinkedHashMap继承于HashMap,它使用了一个双向链表来存储Map中的Entry顺序关系,这种顺序有两种,
一种是LRU顺序,
一种是插入顺序,
这可以由其构造函数

public LinkedHashMap(int initialCapacity,float loadFactor, boolean accessOrder)指定

所以,对于get、put、remove等操作,LinkedHashMap除了要做HashMap做的事情,还做些调整Entry顺序链表的工作。
LruCache中将LinkedHashMap的顺序设置为LRU顺序来实现LRU缓存,每次调用get(也就是从内存缓存中取图片),则将该对象移到链表的尾端。调用put插入新的对象也是存储在链表尾端,这样当内存缓存达到设定的最大值时,将链表头部的对象(近期最少用到的)移除

部分源码解析:LruCache的初始化构造方法

public LruCache(int maxSize) {
    if (maxSize <= 0) {
        throw new IllegalArgumentException("maxSize <= 0");
    }
    this.maxSize = maxSize;
    this.map = new LinkedHashMap<K, V>(0, 0.75f, true);
    //初始容量为零,0.75是加载因子,
    表示容量达到最大容量的75%的时候会
    把内存增加一半。最后这个参数至关重要
    表示访问元素的排序方式,true表示按照
    访问顺序排序,false表示按照插入的顺序排序
}

二,LinkedHashMap详解

LinkedHashMap底层数据结构由链表和哈希表组成。由链表保证元素有序。由哈希表保证元素唯一

LinkedHashMap的两种排序

1,按插入顺序排序(默认)
2,访问顺序排序(lru是这种排序)
LinkedHashMap重写了父类HashMap的get方法,实际在调用父类getEntry()方法取得查找的元素后,再判断当排序模式accessOrder为true时,记录访问顺序,将最新访问的元素添加到双向链表的表头,并从原来的位置删除。由于的链表的增加、删除操作是常量级的,故并不会带来性能的损失

三,HashMap和ConcurrentHashMap的原理

1,原理
HashMap的原理:hashmap本质数组加链表。根据key取得hash值,然后计算出数组下标,如果多个key对应到同一个下标,就用链表串起来,新插入的在前面
ConcurrentHashMap原理:在hashMap的基础上,ConcurrentHashMap将数据分为多个segment(类似HashTable),默认16个(concurrency level),然后每次操作对一个segment加锁,避免多线程锁得几率,提高并发效率

2,Hashmap的源码:
构造函数,空参或者单参时都会调用两个参数的

public HashMap(int initialCapacity, float loadFactor) {  
    int capacity = 1;  
    while (capacity < initialCapacity)  
        capacity <<= 1;  
  
    this.loadFactor = loadFactor;  
    threshold = (int)(capacity * loadFactor);  
    table = new Entry[capacity];  
    init();  
}  

有3个关键参数:
capacity:容量,就是数组大小
loadFactor:比例,用于扩容
threshold:=capacity*loadFactor 最多容纳的Entry数,如果当前元素个数多于这个就要 扩容(capacity扩大为原来的2倍)

Get方法:根据key算hash值,再根据hash值取得数组下标,通过数组下标取出链表,遍历链表用equals取出对应key的value

public V get(Object key) {  
    if (key == null)  
        return getForNullKey();  
    int hash = hash(key.hashCode());  
    for (Entry<K,V> e = table[indexFor(hash, table.length)];  
         e != null;  
         e = e.next) {  
        Object k;  
        if (e.hash == hash && ((k = e.key) == key || key.equals(k)))  
            return e.value;  
    }  
    return null;  
}  

3,HashMap和ConcurrentHashMap,hashtable的区别

HashMap:线程不安全,效率高
ConcurrentHashMap:线程安全,效率高,默认提升16倍
Hashtable:线程安全,效率低

四,SparseArray原理
SparseArray是android里为<Interger,Object>这样的Hashmap而专门写的类,目的是提高内存效率,其核心是折半查找函数(binarySearch)。注意内存二字很重要,因为它仅仅提高内存效率,而不是提高执行效率
它要比 HashMap 节省内存,结构比HashMap简单(SparseArray内部主要使用两个一维数组来保存数据,一个用来存key,一个用来存value)不需要额外的数据结构(主要是针对HashMap中的HashMapEntry而言的)。

五,系统启动流程

image

Zygote进程 –> SystemServer进程 –> 各种系统服务 –> 应用进程

当所有的服务都启动完毕后,SystemServer会打印出“Making services ready”,然后通过ActivityManager启动Home界面,并发送“ACTION_BOOT_COMPLETED”广播消息

六,App启动、Application生命周期

app的两种启动方式

1,冷启动

系统会重新创建一个新的进程分配给它,所以会先创建和初始化Application类,再创建和初始化MainActivity类(包括一系列的测量、布局、绘制),最后显示在界面上

2,热启动

进程在后台运行,所以热启动就不会走Application这步了,而是直接走MainActivity(包括一系列的测量、布局、绘制),所以热启动的过程只需要创建和初始化一个MainActivity就行了,而不必创建和初始化Application,因为一个应用从新进程的创建到进程的销毁,Application只会初始化一次。

App启动流程

用户点击app后会通知 ActivityManagerService 启动应用的入口 Activity, ActivityManagerService 发现这个应用还未启动,则会通知 Zygote 进程孵化出应用进程,然后在这个应用进程里执行 ActivityThread 的 main 方法。应用进程接下来通知 ActivityManagerService 应用进程已启动,ActivityManagerService 保存应用进程的一个代理对象,这样 ActivityManagerService 可以通过这个代理对象控制应用进程,然后 ActivityManagerService 通知应用进程创建入口 Activity 的实例,并执行它的生命周期函数。

上面的启动流程是 Android 提供的机制,我们只能在创建入口 Activity 的实例这里做文章,正常Main Activity 的启动流程:

-> Application 构造函数
-> Application.attachBaseContext()
-> Application.onCreate()
-> Activity 构造函数
-> Activity.setTheme()
-> Activity.onCreate()
-> Activity.onStart
-> Activity.onResume
-> Activity.onAttachedToWindow
-> Activity.onWindowFocusChanged

统计app启动时长的两种方式

1,本地通过adb命令行

adb shell am start -w packagename/activity 输入adb shell am start -W

com.qcl/com.qcl.MainActivity得到下面结果
Activity: com.qcl/.MainActivity
ThisTime: 83
TotalTime: 83
WaitTime: 94
TotalTime是我们真正的启动时间
2,通过收集log(可以用来获取线上启动时间)

开始时间:
冷启动在Application的attachBaseContext
热启动在入口activity的onRestart中
结束时间:在入口activity的onWindowFocusChanged

app的启动优化:

基于上面的启动流程我们尽量做到如下几点

app启动遇见黑屏或者白屏问题
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar"> 
    <item name="android:screenOrientation">portrait</item> 
    <item name="android:windowBackground">>@mipmap/splash</item> 
  
    <item name="android:windowIsTranslucent">true</item> 
    <item name="android:windowNoTitle">true</item> 
</style>

设置透明Theme
通过把样式设置为透明,程序启动后不会黑屏而是整个透明了,等到界面初始化完才一次性显示出来

<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar"> 
    <item name="android:windowNoTitle">true</item> 
    <item name="android:windowBackground">@android:color/transparent</item> 
    <item name="android:windowIsTranslucent">true</item> 
    <item name="android:screenOrientation">portrait</item> 
  </style>
两者对比:

Theme1 程序启动快,界面先显示背景图,然后再刷新其他界面控件。给人刷新不同步感觉。
Theme2 给人程序启动慢感觉,界面一次性刷出来,刷新同步

Application和Process进程

当application在Linux平台开启时,系统会给这个application创建一个进程(process)来运行同时分配内存资源给该application,当程序结束运行时,该进程结束系统回收内存资源。
一个Application就是一个应用,在应用启动时,android会启动一个linux进程和一个主线程,这个进程以应用的包名命名,在默认的情况下,这个Application下的Activity、service、provider、receiver等组件都在这个进程中运行

Application的生命周期

七,listview优化

1,convertView复用
2,viewholder使用
3,图片优化
4,getView()中不要写过多的逻辑代码,不要创建很多的对象,逻辑代码放在别的地方,
5,item的布局减少层级
6,通过getItemViewType实现复杂布局的复用
7,简单布局可以将listview的scrollingCache和animateCache属性设置false。
如果设置为true会提高显示效果,但是需要消耗更多内存和更长的初始化时间

八,RecycleView

基本解决了ListView的一些缺陷,比如:需要手动重写ViewHolder,对于item的动画操作也需要很复杂的逻辑去实现等等
但是也有些缺点
不能简单的加头和尾

  1. 不能简单的设置子item的点击事件
    所以对于一些简单的列表还是可以用listview的。Recylerview可以用于实现一些复杂的列表。

优化
1,简化item布局
2,图片优化

九,JVM内存模型

1,什么是jvm

JVM是Java Virtual Machine(Java虚拟机)的缩写,JVM是一种用于计算设备的规范,它是一个虚构出来的计算机。
JVM在执行字节码时,实际上最终还是把字节码解释成具体平台上的机器指令执行

2,JDK,JRE,JVM的作用及关系

(1)作用
    JVM:保证Java语言跨平台
    JRE:Java程序的运行环境
    JDK:Java程序的开发环境
(2)关系
    JDK:JRE+工具
    JRE:JVM+类库
简单而言:使用JDK开发完成的Java程序,交给JRE去运行,由JVM来保证跨平台。

3,java代码具体执行过程如下图

image

4,jvm内存结构图(即运行时数据区)

image image
方法区有以下特点 
1,方法区是线程安全的。
2、方法区的大小不必是固定的,JVM可根据应用需要动态调整。
3、方法区也可被垃圾收集,当某个类不在被使用(不可触及)时,JVM将卸载这个类,进行垃圾收集

堆和栈的主要区别:

栈--主要存放引用和基本数据类型。

堆--用来存放 new 出来的对象实例和数组。

线程和jvm

每个方法被执行的时候都会同时创建一个栈帧(Stack Frame)用于存储局部变量表、操作栈、动态链接、方法出口等信息。
每一个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过

image

5,JVM执行程序的过程

  1. 加载.class文件
  2. 管理并分配内存
  3. 执行垃圾收集

6,jvm的生命周期

十,GC的回收策略(垃圾回收策略机制)

垃圾回收:主要是对gc堆即Young Generation(年轻代)块和Old Generation(年老代)块内存进行回收,YG用来放新产生的对象,经过几次回收还没回收掉的对象往OG中移动,对YG进行垃圾回收又叫做MinorGC,对 OG垃圾回收又叫MajorGC,两块内存回收互不干涉

更细的划分为Eden和2个survivor space(即幸存区)

image

十一,画出 Android 的大体架构图

image

十二,Dalvik,ART和jvm

Dalvik是Google公司自己设计用于Android平台的Java虚拟机

Dalvik和jvm的区别
Dalvik是基于寄存器的,运行dex文件
JVM是基于栈的,运行java字节码

Art:空间换时间
art模式需要在程序安装时进行预编译,将apk编译解析成机器码,运行速度相比dalvik模式快。
缺点:安装时间稍长,由于进行了预编译,所以会产生机器码,会占用存储空间。

十三,阿里面试手写单例模式

一般情况下直接使用饿汉式就好了,如果明确要求要懒加载(lazy initialization)会倾向于使用静态内部类,如果涉及到反序列化创建对象时会试着使用枚举的方式来实现单例。

Public class Singleton{
Private static final Singleton instance=new Singleton();
Private Singleton(){}
Public static Singleton getInstance(){
Return instance;
}
}
Public class Singleton{
Private static class SingletonHolder{
Private static final Singleton INSTANCE=new Singleton();
}
Private Singleton(){}
Public static final Singleton getInstance(){
Return SingletonHolder.INSTANCE;
}
}
Public enum Singleton{
INSTANCE;
}

十四,安卓源码中用到的设计模式

十五,多线程相关

多进程的意义? 提高CPU的使用率
多线程的意义? 提高应用程序的使用率

1,继承Thread类,
2,实现Runnable接口(推荐,方便的实现资源的共享)
3,通过Callable和Future创建线程
代码演示    //创建一个线程池对象,控制要创建几个线程对象
        ExecutorService pool=Executors.newFixedThreadPool(2);
        //可以执行Runnable对象或者Callable对象的线程
        pool.submit(new MyRunnable());
        pool.submit(new MyRunnable());
        //结束线程池
        pool.shutdown();
1)Lock是一个接口,jdk5后出现,而synchronized是Java中的关键字,synchronized是内置的语言实现;
2)synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁;
3)Lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断;
4)通过Lock可以知道有没有成功获取锁,而synchronized却无法办到。
5)Lock可以提高多个线程进行读操作的效率。

在性能上来说,如果竞争资源不激烈,两者的性能是差不多的,而当竞争资源非常激烈时(即有大量线程同时竞争),此时Lock的性能要远远优于synchronized。所以说,在具体使用时要根据适当情况选择

        嵌套的代码体现
        if(x>20){
            synchronized(a){
                synchronized(b){...}
            }
        }else{
            synchronized(b){
                synchronized(a){...}
            }
        }

十六,AsyncTask的底层实现

AsyncTask是对Handler与线程池的封装,AsyncTask的本质是一个线程池,所有提交的异步任务都会在这个线程池中的工作线程内执行,当工作线程需要跟UI线程交互时,工作线程会通过向在UI线程创建的Handler传递消息的方式,调用相关的回调函数,从而实现UI界面的更新

十七,进程间通信和同步

1,SP是进程同步的吗?有什么方法做到同步?
Android本身的SP可以支持多进程,但是SP不能保证多进程间同步。因为当多个进程同时而又高频的调用SP的commit方法时,就会导致文件被反复覆盖写入,而并没有被及时读取,所以造成进程间数据的不同步
Android的ContentProvider是支持进程同步的,可以利用ContentProvider进行进程的sp同步
进程间通信的四种方式

十八,即时通讯和推送的3种实现方案

十九,Bitmap相关

1,android 色彩模式说明:

ALPHA_8:   每个像素占用1byte内存。
ARGB_4444:  每个像素占用2byte内存
ARGB_8888:  每个像素占用4byte内存
RGB_565:    每个像素占用2byte内存

假设一张10241024,模式为ARGB_8888的图片,那么它占有的内存就是:10241024*4 = 4MB
Android默认的色彩模式为ARGB_8888,通常我们优化Bitmap时,当需要做性能优化或者防止OOM(Out Of Memory),我们通常会使用Bitmap.Config.RGB_565这个配置,因为Bitmap.Config.ALPHA_8只有透明度,显示一般图片没有意义,Bitmap.Config.ARGB_4444显示图片不清楚,Bitmap.Config.ARGB_8888占用内存最多

if (!bmp.isRecycle()) {
    bmp.recycle();   //回收图片所占的内存
    bitmap = null;
    system.gc();  //提醒系统及时回收
}

虽然调用recycle()并不能保证立即释放占用的内存,但是可以加速Bitmap的内存的释放。
释放内存以后,就不能再使用该Bitmap对象了,如果再次使用,就会抛出异常。所以一定要保证不再使用的时候释放。比如,如果是在某个Activity中使用Bitmap,就可以在Activity的onStop()或者onDestroy()方法中进行回收

上一篇下一篇

猜你喜欢

热点阅读