3 个月 8 个 Offer!2020 字节跳动 + 京东,移动应用开发框架
架构的定义
有关软件整体结构与组件的抽象描述,用于指导大型软件系统各个方面的设计。总结一下,就是一整个软件工程项目中的骨架,是一种宏观的规划。
Volley 相关
Volley 的磁盘缓存
在面试的时候,聊到 Volley 请求到网络的数据缓存。当时说到是 Volley 会将每次通过网络请求到的数据,采用FileOutputStream
,写入到本地的文件中。那么问题来了:这个缓存文件,是声明在一个 SD 卡文件夹中的(也可以是 getCacheFile())。如果不停的请求网络数据,这个缓存文件夹将无限制的增大,最终达到 SD 卡容量时,会发生无法写入的异常(因为存储空间满了)。这个问题的确以前没有想到,当时也没说出怎么回事。回家了赶紧又看了看代码才知道,原来 Volley 考虑过这个问题(汗!想想也是)翻看代码DiskBasedCache#pruneIfNeeded()
private void pruneIfNeeded(int neededSpace) {if ((mTotalSize + neededSpace) < mMaxCacheSizeInBytes) {return;}
long before = mTotalSize;int prunedFiles = 0;long startTime = SystemClock.elapsedRealtime();
Iterator<Map.Entry<String, CacheHeader>> iterator = mEntries.entrySet().iterator();while (iterator.hasNext()) {Map.Entry<String, CacheHeader> entry = iterator.next();CacheHeader e = entry.getValue();boolean deleted = getFileForKey(e.key).delete();if (deleted) {mTotalSize -= e.size;} else {//print log}iterator.remove();prunedFiles++;if ((mTotalSize + neededSpace) < mMaxCacheSizeInBytes * HYSTERESIS_FACTOR) {break;}}}
其中mMaxCacheSizeInBytes
是构造方法传入的一个缓存文件夹的大小,如果不传默认是 5M 的大小。通过这个方法可以发现,每当被调用时会传入一个neededSpace
,也就是需要申请的磁盘大小(即要新缓存的那个文件所需大小)。首先会判断如果这个neededSpace
申请成功以后是否会超过最大可用容量,如果会超过,则通过遍历本地已经保存的缓存文件的 header(header 中包含了缓存文件的缓存有效期、占用大小等信息)去删除文件,直到可用容量不大于声明的缓存文件夹的大小。其中HYSTERESIS_FACTOR
是一个值为 0.9 的常量,应该是为了防止误差的存在吧(我猜的)。
Volley 缓存命中率的优化
如果让你去设计 Volley 的缓存功能,你要如何增大它的命中率。可惜了,如果上面的缓存功能是昨天看的,今天的面试这个问题就能说出来了。还是上面的代码,在缓存内容可能超过缓存文件夹的大小时,删除的逻辑是直接遍历 header 删除。这个时候删除的文件有可能是我们上一次请求时刚刚保存下来的,屁股都还没坐稳呢,现在立即删掉,有点舍不得啊。如果遍历的时候,判断一下,首先删除超过缓存有效期的(过期缓存),其次按照 LRU 算法,删除最久未使用的,岂不是更合适?
Volley 缓存文件名的计算
这个是我一直没弄懂的问题。如下代码:
private String getFilenameForKey(String key) {int firstHalfLength = key.length() / 2;String localFilename = String.valueOf(key.substring(0, firstHalfLength).hashCode());localFilename += String.valueOf(key.substring(firstHalfLength).hashCode());return localFilename;}
为什么会要把一个 key 分成两部分,分别求 hashCode,最后又做拼接。这个问题之前在 stackoverflow 上问过?#链接原谅我,别人的回答我最初并没有看懂。直到最近被问到,如果让你设计一个 HashMap,如何避免 value 被覆盖,我才想到原因。先来看一下?String#hashCode()
?的实现:
@Override public int hashCode() {int hash = hashCode;if (hash == 0) {if (count == 0) {return 0;}final int end = count + offset;final char[] chars = value;for (int i = offset; i < end; ++i) {hash = 31*hash + chars[i];}hashCode = hash;}return hash;}
从上面的实现可以看到,String 的 hashcode 是根据字符数组中每个位置的字母的 int 值再加上上次 hash 值乘以 31,这种算法求出来的,至于为什么是 31,我也不清楚。但是可以肯定一点,hashcode 并不是唯一的。不信你运行下面这两个输出:
System.out.print("======" + "vFrKiaNHfF7t[9::E[XsX?L7xPp3DZSteIZvdRT8CX:w6d;v<KZnhsM^dqoppe".hashCode());System.out.print("======" + "hI4pFxGOfS@suhVUd:mTo_begImJPB@Fl[6WJ?ai=RXfIx^=Aix@9M;;?Vdj_Zsi".hashCode());
这两个字符串是根据 hashcode 的算法逆向出来的,他们的 hashcode 都是 12345。逆向算法请见这里再回到我们的问题,为什么会要把一个 key 分成两部分。现在可以肯定的答出,目的是为了尽可能避免 hashcode 重复造成的文件名重复(求两次 hash 两次都与另一个 url 重复的概率总要比一次重复的概率小吧)。顺带再提一点,就像上面说的,概率小并不代表不存在。但是 Java 计算 hashcode 的速度是很快的,应该是在效率和安全性上取舍的结果吧。
推送心跳包是 TCP 包还是 UDP 包或者 HTTP 包
其实聊起这个问题是因为最近看到?@睡不着起不来的万宵?同学写的一篇文章《Android推送技术研究》结果就产生了这个没回答出来的问题(妈蛋,自己给自己挖坑 - -)最后看了这篇文章(好像是转的,没找到原地址)android 心跳的分析原来心跳包的实现是调用了socket.sendUrgentData(0xFF)
这句代码实现的,所以,当然是 TCP 包。
ARGB_8888 占用内存大小
首先说说本题的答案,是 4byte,即 ARGB 各占用 8 个比特来描述。当时回答错了,详细解答看这里你的 Bitmap 究竟占多大内存但是——这个问题引出了一个大大的闹剧,请听我慢慢道来。??????不知道怎么就聊到 Bitmap 压缩上了,他说他们的 Bitmap 居然都是不压缩的??????还是直接甩代码吧。。。。
public static Bitmap create(byte[] bytes, int maxWidth, int maxHeight) {//上面的省略了 option.inJustDecodeBounds = true;BitmapFactory.decodeByteArray(bytes, 0, bytes.length, option);int actualWidth = option.outWidth;int actualHeight = option.outHeight;
// 计算出图片应该显示的宽高 int desiredWidth = getResizedDimension(maxWidth, maxHeight, actualWidth, actualHeight);int desiredHeight = getResizedDimension(maxHeight, maxWidth, actualHeight, actualWidth);
option.inJustDecodeBounds = false;option.inSampleSize = findBestSampleSize(actualWidth, actualHeight,desiredWidth, desiredHeight);Bitmap tempBitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length, option);
// 做缩放 if (tempBitmap != null&& (tempBitmap.getWidth() > desiredWidth || tempBitmap.getHeight() > desiredHeight)) {bitmap = Bitmap.createScaledBitmap(tempBitmap, desiredWidth,desiredHei
ght, true);tempBitmap.recycle();} else {bitmap = tempBitmap;}}
return bitmap;}
你这么做,decodeByteArray 两次不是更占内存吗???????
评论