Android面试题

一、图片压缩处理
图片的存在形式有三种:
1、文件形式(以二进制形式存在于硬盘上)
2、流的形式(以二进制形式存在于内存中)
3、Bitmap形式(位图图像,是由称作像素的单个点组成的)
这三种形式的区别:文件形式和流的形式对图片大小没有影响,当以Bitmap形式存在时,其占用内存会瞬间变大。

一张图片(Bitmap)占用的内存大小=图片长度x图片宽度x单位像素占用的字节数

图片常用的压缩格式:
ARGB A:透明度 R:红色 G:绿色 B:蓝色

ALPHA_8:表示8位ALPHA位图,即A=8,一个像素占用一个字节,没有其他颜色,只有透明度。
ARGB_4444:表示16位ARGB位图,即A=4,R=4,G=4,B=4,一个像素占16位,两个字节。
ARGB_8888:表示32位ARGB位图,即A=8,R=8,G=8,B=8,一个像素占32位,四个字节。
RGB_565:表示16位RGB位图,即R=5,G=6,B=5,一个像素占16位,他没有透明度,两个字节。
我们在做压缩处理的时候,可以先通过改变Bitmap的图片编码格式,来达到压缩的效果,其实压缩最主要就是要么改变其宽高,要么就通过减少其单个像素占用的内存。

常用的压缩方法:
1、质量压缩:质量压缩不会减少图片的像素,它是在保持像素的前提下改变图片的位深及透明度,来达到压缩图片的目的,图片的长,宽,像素都不会改变,那么bitmap所占内存大小是不会变的。
  我们可以看到有个参数:quality,可以调节你压缩的比例,但是还要注意一点就是,质量压缩对png格式这种图片没有作用,因为png是无损压缩。
2、采样率压缩:采样率压缩其原理是缩放bitamp的尺寸,通过调节其inSampleSize参数,比如调节为2,宽高会为原来的1/2,内存变回原来的1/4。
3、缩放法压缩:放缩法压缩使用的是通过矩阵对图片进行裁剪,也是通过缩放图片尺寸,来达到压缩图片的效果,和采样率的原理一样。
4、RGB_565压缩:RGB_565压缩是通过改用内存占用更小的编码格式来达到压缩的效果。Android默认的颜色模式为ARGB_8888,这个颜色模式色彩最细腻,显示质量最高。一般不建议使用ARGB_4444,因为画质实在是辣鸡,如果对透明度没有要求,建议可以改成RGB_565,相比ARGB_8888将节省一半的内存开销。
5、createScaledBitmap:这里是将图片压缩成用户所期望的长度和宽度,但是这里要说,如果用户期望的长度和宽度和原图长度宽度相差太多的话,图片会很不清晰。

以上就是5种图片压缩的方法,这里需要强调,他们的压缩仅仅只是对Android中的Bitmap来说的。如果将这些压缩后的Bitmap另存为sd中,他们的内存大小并不一样。

二、RecyclerView缓存机制:
四级缓存:
Scrap:当前屏幕内的缓存数据
Cache:刚刚移出屏幕的缓存数据,默认大小是2个,当其容量被充满同时又有新的数据添加的时候,会根据FIFO原则,把优先进入的缓存数据移出并放到下一级缓存中,然后再把新的数据添加进来。Cache里面的数据是干净的,也就是携带了原来的ViewHolder的所有数据信息,数据可以直接来拿来复用。需要注意的是,cache是根据position来寻找数据的,这个postion是根据第一个或者最后一个可见的item的position以及用户操作行为(上拉还是下拉)。
ViewCacheExtension:是google留给开发者自己来自定义缓存的,这个ViewCacheExtension我个人建议还是要慎用,没有找到具体的应用场景。
RecycledViewPool:刚才说了Cache默认的缓存数量是2个,当Cache缓存满了以后会根据FIFO(先进先出)的规则把Cache先缓存进去的ViewHolder移出并缓存到RecycledViewPool中,RecycledViewPool默认的缓存数量是5个。RecycledViewPool与Cache相比不同的是,从Cache里面移出的ViewHolder再存入RecycledViewPool之前ViewHolder的数据会被全部重置,相当于一个新的ViewHolder,而且Cache是根据position来获取ViewHolder,而RecycledViewPool是根据itemType获取的,如果没有重写getItemType()方法,itemType就是默认的。因为RecycledViewPool缓存的ViewHolder是全新的,所以取出来的时候需要走onBindViewHolder()方法。

三、Handler机制
由五部分组成:Handler、MessageQueue、Message、Looper、ThreadLocal。
MessageQueue:消息队列,主要包含两个操作:enqueueMessage(插入)和next(读取并删除),他是通过一个单链表的数据结构来维护消息列表,在插入和删除上比较有优势。
next方法是一个无限循环的方法,如果消息队列中没有消息,则方法处于阻塞状态。当有新消息到来时,next方法会返回这条消息并将其从单链表中移除。
Looper:消息循环,它会不停地从MessageQueue中查看是否有新消息,如果有就立马处理,否则就一直阻塞在那里。在构造方法中会创建一个MessageQueue,然后将当前线程对象保存起来,代码如下:
private Looper(boolean quitAllowed){
mQueue=new MessageQueue(quitAllowed);
mThread=Thread.currentThread();
}
Handler工作需要Looper,创建Looper方式如下:
Looper.prepare();
Handler handler = new Handler();
Looper.loop();
Looper最重要的方法就是loop方法,只有调用了loop后,消息循环系统才会真正的作用起来。
下面是Handler发构造方法:
public Handler(Callback callback, boolean async) {

        ...// 仅贴出关键代码

        // 1. 指定Looper对象
            mLooper = Looper.myLooper();
            if (mLooper == null) {
                throw new RuntimeException(
                    "Can't create handler inside thread that has not called Looper.prepare()");
            }
            // Looper.myLooper()作用:获取当前线程的Looper对象;若线程无Looper对象则抛出异常
            // 即 :若线程中无创建Looper对象,则也无法创建Handler对象
            // 故 若需在子线程中创建Handler对象,则需先创建Looper对象
            // 注:可通过Loop.getMainLooper()可以获得当前进程的主线程的Looper对象

        // 2. 绑定消息队列对象(MessageQueue)
            mQueue = mLooper.mQueue;
            // 获取该Looper对象中保存的消息队列对象(MessageQueue)
            // 至此,保证了handler对象 关联上 Looper对象中MessageQueue
}

由上述代码可知Handler的创建,必须建立在当前线程存在Looper的基础上才可以。
loop方法是一个死循环,唯一跳出循环的方式是MessageQueue的next方法返回了null。当Looper的quit方法被调用时,Looper就会调用MessageQueue的quit或者quitSafely方法来通知消息队列退出,当消息队列被标记为退出状态时,它的next方法就会返回null。loop方法会调用MessageQueue的next方法来获取新消息,而next是一个阻塞操作,当没有消息时,next方法会一直阻塞在那里,这也就导致loop方法一直阻塞在那里。如果MessageQueue的next方法返回了新消息,Looper就会处理这条消息。
msg.target.dispatchMessage(msg),这里的msg.target是发送这条消息的Handler对象,这样Handler发送的消息最终又交给它的dispatchMessage方法来处理了。

四、ThreadLocal
ThreadLocal创建有下面三种方式:
// 1. 直接创建对象
private ThreadLocal myThreadLocal = new ThreadLocal()

// 2. 创建泛型对象
private ThreadLocal myThreadLocal = new ThreadLocal();

// 3. 创建泛型对象 & 初始化值
// 指定泛型的好处:不需要每次对使用get()方法返回的值作强制类型转换
private ThreadLocal myThreadLocal = new ThreadLocal() {
@Override
protected String initialValue() {
return "This is the initial value";
}
};

// 特别注意:
// 1. ThreadLocal实例 = 类中的private、static字段
// 2. 只需实例化对象一次 & 不需知道它是被哪个线程实例化
// 3. 每个线程都保持 对其线程局部变量副本 的隐式引用
// 4. 线程消失后,其线程局部实例的所有副本都会被垃圾回收(除非存在对这些副本的其他引用)
// 5. 虽然所有的线程都能访问到这个ThreadLocal实例,但是每个线程只能访问到自己通过调用ThreadLocal的set()设置的值
// 即 哪怕2个不同的线程在同一个ThreadLocal对象上设置了不同的值,他们仍然无法访问到对方的值。

访问ThreadLocal变量:
// 1. 设置值:set()
// 需要传入一个Object类型的参数
myThreadLocal.set("初始值”);

// 2. 读取ThreadLocal变量中的值:get()
// 返回一个Object对象
String threadLocalValue = (String) myThreadLocal.get();

核心原理:
ThreadLocal类中有一个Map(称:ThreadLocalMap):用于存储每个线程&该线程设置的存储在ThreadLocal变量的值。
1、ThreadLocalMap的key = 当前ThreadLocal的实例、值value = 该线程设置的存储在ThreadLocal变量的值。
2、该key是ThreadLocal对象的弱引用;当要抛弃掉ThreadLocal对象时,垃圾收集器会忽略该key的引用而清理掉ThreadLocal对象。

关于如何设置ThreadLocal的值请看原码:
// ThreadLocal的源码

public class ThreadLocal {

...

/**
* 设置ThreadLocal变量引用的值
* ThreadLocal变量引用 指向 ThreadLocalMap对象,即设置ThreadLocalMap的值 = 该线程设置的存储在ThreadLocal变量的值
* ThreadLocalMap的键Key = 当前ThreadLocal实例
* ThreadLocalMap的值 = 该线程设置的存储在ThreadLocal变量的值
**/
public void set(T value) {

    // 1. 获得当前线程
    Thread t = Thread.currentThread();

    // 2. 获取该线程的ThreadLocalMap对象 ->>分析1
    ThreadLocalMap map = getMap(t);

    // 3. 若该线程的ThreadLocalMap对象已存在,则替换该Map里的值;否则创建1个ThreadLocalMap对象
    if (map != null)
        map.set(this, value);// 替换
    else
        createMap(t, value);// 创建->>分析2
}

/**
* 获取ThreadLocal变量里的值
* 由于ThreadLocal变量引用 指向 ThreadLocalMap对象,即获取ThreadLocalMap对象的值 = 该线程设置的存储在ThreadLocal变量的值
**/
public T get() {

    // 1. 获得当前线程
    Thread t = Thread.currentThread();

    // 2. 获取该线程的ThreadLocalMap对象
    ThreadLocalMap map = getMap(t);

    // 3. 若该线程的ThreadLocalMap对象已存在,则直接获取该Map里的值;否则则通过初始化函数创建1个ThreadLocalMap对象
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null)
            return (T)e.value; // 直接获取值
    }
    return setInitialValue(); // 初始化
}

/**
* 初始化ThreadLocal的值
**/
private T setInitialValue() {

    T value = initialValue();

    // 1. 获得当前线程
    Thread t = Thread.currentThread();

    // 2. 获取该线程的ThreadLocalMap对象
    ThreadLocalMap map = getMap(t);

     // 3. 若该线程的ThreadLocalMap对象已存在,则直接替换该值;否则则创建
    if (map != null)
        map.set(this, value); // 替换
    else
        createMap(t, value); // 创建->>分析2
    return value;
}

/**
* 分析1:获取当前线程的threadLocals变量引用
**/
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}

/**
* 分析2:创建当前线程的ThreadLocalMap对象
**/
void createMap(Thread t, T firstValue) {
// 新创建1个ThreadLocalMap对象 放入到 Thread类的threadLocals变量引用中:
// a. ThreadLocalMap的键Key = 当前ThreadLocal实例
// b. ThreadLocalMap的值 = 该线程设置的存储在ThreadLocal变量的值
t.threadLocals = new ThreadLocalMap(this, firstValue);
// 即 threadLocals变量 属于 Thread类中 ->> 分析3
}

...

}

/**
* 分析3:Thread类 源码分析
**/

public class Thread implements Runnable {
   ...

   ThreadLocal.ThreadLocalMap threadLocals = null;
   // 即 Thread类持有threadLocals变量
   // 线程类实例化后,每个线程对象拥有独立的threadLocals变量变量
   // threadLocals变量在 ThreadLocal对象中 通过set() 或 get()进行操作

   ...

}

五、HashMap原理:
HashMap 采用的数据结构 = 数组(主) + 单链表(副)
数组下标 = key键的哈希值。
数组元素 = 一个键值对(Entry) = 一个链表的头结点。

你可能感兴趣的:(Android面试题)