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<String>();

// 3. 创建泛型对象 & 初始化值
// 指定泛型的好处:不需要每次对使用get()方法返回的值作强制类型转换
private ThreadLocal myThreadLocal = new ThreadLocal<String>() {
@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<T> {

...

/**
* 设置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) = 一个链表的头结点。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 210,978评论 6 490
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 89,954评论 2 384
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,623评论 0 345
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,324评论 1 282
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,390评论 5 384
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,741评论 1 289
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,892评论 3 405
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,655评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,104评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,451评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,569评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,254评论 4 328
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,834评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,725评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,950评论 1 264
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,260评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,446评论 2 348

推荐阅读更多精彩内容

  • 所有知识点已整理成app app下载地址 J2EE 部分: 1.Switch能否用string做参数? 在 Jav...
    侯蛋蛋_阅读 2,412评论 1 4
  • 1、Activity如何与Service通信? 可以通过bindService的方式,先在Activity里实现一...
    ZhouWG阅读 1,039评论 0 0
  • 【威哥说】虽然不能说所有的面试题都是必学的技术知识点,但是大家学习都是以找工作为目标,时刻了解用人单位的技术要求,...
    磨砺营阅读 1,855评论 6 92
  • 答: 方法的重载属于编译时多态,方法名相同参数列表不同,返回值必须相同或都没有返回值类型。方法的重写属于运行时多态...
    JA尐白阅读 869评论 1 19
  • Fragment 如何实现类似 Activity 栈的压栈和出栈效果的? Fragment 的事物管理器内部维持了...
    奔跑吧李博阅读 1,011评论 1 21