码仔,今天就给大家带来了《每日一道面试题》的第十二期:
01
ListView如何提高效率
1、使用分页加载,不要一次性加载所有数据。
2、复用convertView。在getItemView中,判断converView是否为空,如果不为空,可复用。
3、异步加载图片。Item中如果包含有webimage,那么最好异步加载。
4、快速滑动时,不显示图片。当快速滑动列表(SCROLL_STATE_FLING),item中的图片或获取需要消耗资源的view,可以不显示出来;而处于其他两种状态(SCROLL_STATE_IDLE和SCROLL_STATE_TOUCH_SCROLL),则将那些view显示出来
02
谈谈Android的安全机制
- Android 是基于Linux内核的,因此 Linux 对文件权限的控制同样适用于 Android。在 Android 中每个应用都有自己的/data/data/包名 文件夹,该文件夹只能该应用访问,而其他应用则无权访问。
- Android 的权限机制保护了用户的合法权益。如果我们的代码想拨打电话、发送短信、访问通信录、定位、访问、sdcard 等所有可能侵犯用于权益的行为都是必须要在 AndroidManifest.xml 中进行声明的,这样就给了用户一个知情权。
- Android 的代码混淆保护了开发者的劳动成果。
03
LruCache算法源码解析
我们先看下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);
}
从构造方法的源码我们可以看到,在这段代码中我们主要做了两件事。第一是判断下传递来的最大分配内存大小是否小于零,如果小于零则抛出异常,因为我们如果传入一个小于零的内存大小就没有意义了。之后在构造方法内存就new了一个LinkHashMap集合,从而得知LruCache内部实现原理果然是基于LinkHashMap来实现的。
之后我们再来看下存储缓存的put()方法。
public final V put(K key, V value) {
if (key == null || value == null) {
throw new NullPointerException("key == null || value == null");
}
V previous;
synchronized (this) {
putCount++;
size += safeSizeOf(key, value);
previous = map.put(key, value);
if (previous != null) {
size -= safeSizeOf(key, previous);
}
}
if (previous != null) {
entryRemoved(false, key, previous, value);
}
trimToSize(maxSize);
return previous;
}
从代码中我们可以看到,这个put方法内部其实没有做什么很特别的操作,就是对数据进行了一次插入操作。但是我们注意到最后的倒数第三行有一个trimToSize()方法,那么这个方法是做什么用的呐?我们点进去看下。
public void trimToSize(int maxSize) {
while (true) {
K key;
V value;
synchronized (this) {
if (size < 0 || (map.isEmpty() && size != 0)) {
throw new IllegalStateException(getClass().getName()
+ ".sizeOf() is reporting inconsistent results!");
}
if (size <= maxSize) {
break;
}
Map.Entry<K, V> toEvict = map.eldest();
if (toEvict == null) {
break;
}
key = toEvict.getKey();
value = toEvict.getValue();
map.remove(key);
size -= safeSizeOf(key, value);
evictionCount++;
}
entryRemoved(true, key, value, null);
}
}
我们可以看到,这个方法原来就是对内存做了一次判断,如果发现内存已经满了,那么就调用map.eldest()方法获取到最后的数据,之后调用map.remove(key)方法,将这个最近最少使用的数据给剔除掉,从而达到我们内存不炸掉的目的。
我们再来看看get()方法。
public final V get(K key) {
//key为空抛出异常
if (key == null) {
throw new NullPointerException("key == null");
}
V mapValue;
synchronized (this) {
//获取对应的缓存对象
//get()方法会实现将访问的元素更新到队列头部的功能
mapValue = map.get(key);
if (mapValue != null) {
hitCount++;
return mapValue;
}
missCount++;
}
get方法看起来就是很常规的操作了,就是通过key来查找value的操作,我们再来看看LinkHashMap的中get方法。
public V get(Object key) {
LinkedHashMapEntry<K,V> e = (LinkedHashMapEntry<K,V>)getEntry(key);
if (e == null)
return null;
//实现排序的关键方法
e.recordAccess(this);
return e.value;
}
调用recordAccess()方法如下:
void recordAccess(HashMap<K,V> m) {
LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;
if (lm.accessOrder) {
lm.modCount++;
remove();
addBefore(lm.header);
}
}
由此可见LruCache中维护了一个集合LinkedHashMap,该LinkedHashMap是以访问顺序排序的。当调用put()方法时,就会在结合中添加元素,并调用trimToSize()判断缓存是否已满,如果满了就用LinkedHashMap的迭代器删除队尾元素,即最近最少访问的元素。当调用get()方法访问缓存对象时,就会调用LinkedHashMap的get()方法获得对应集合元素,同时会更新该元素到队头。
04
Broadcast注册方式与区别
一、Brodcast注册方式:
1、静态注册:
2、动态注册:
二、静态注册:在清单文件manifest中注册,当程序退出之后还可以收到该广播。不能控制具体某个时间点接收和不接收广播。
三、动态注册:通过代码的方式注册context.registerReceiver(broadcastReceiver),注册的同时注意在不需要接受的时候进行反注册context.unregisterReceiver(broadcastReceiver);避免内存泄漏, 动态注册可以很好的控制广播接受。
四、从Android 8.0(API 26)开始,对于大部分隐式广播(广播的对象不是针对你开发的APP),不能在manifest中声明receiver,如果需要使用隐式广播,需要使用context.registerReceiver 的方法。
05
使用Xposed为什么需要Root
Xposed框架(Xposed Framework)是一套开源的、在Android高权限模式下运行的框架服务,可以在不修改APK文件的情况下影响程序运行(修改系统)的框架服务,基于它可以制作出许多功能强大的模块,且在功能不冲突的情况下同时运作。是通过加载插件的形式安装的,例如微信抢红包等
Xposed通过劫持Android系统的zygote进程来加载自定义功能,就像是半路截杀,在应用运行之前就已经将我们需要的自定义内容强加在了系统进程当中。
Magisk类似Xposed,但是他是通过挂载一个与系统文件相隔离的文件系统来加载自定义内容,为系统分区打开了一个通往平行世界的入口,所有改动在那个世界Magisk分区里发生,在必要的时候却又可以被认为是(从系统分区的角度而言)没有发生过
因为Xposed需要胁持Zygote进程,所以必须root,不然拿不到权限
06
结束语
如果你有好的答案可以提交至:
https://github.com/codeegginterviewgroup/CodeEggDailyInterview