Android关键散列算法不配置问题,怎么解决

危险!在HashMap中将可变对象用作Key - ImportNew
本文中我们将会讨论在Java HashMap中将可变对象用作Key。所有的Java程序员可能都在自己的编程经历中多次用过HashMap。那什么是HashMap呢?
HashMap是一种用哈希值来存储和查找键值对(key-value pair,也称作entry)的一种数据结构。
为了正确使用HashMap,选择恰当的Key是非常重要的。Key在HashMap里是不可重复的。
1、什么是可变对象
可变对象是指创建后自身状态能改变的对象。换句话说,可变对象是该对象在创建后它的哈希值可能被改变。
在下面的代码中,对象MutableKey的键在创建时变量 i=10 j=20,哈希值是1291。
然后我们改变实例的变量值,该对象的键 i 和 j 从10和20分别改变成30和40。现在Key的哈希值已经变成1931。
显然,这个对象的键在创建后发生了改变。所以类MutableKey是可变的。
让我们看看下面的示例代码:
注意:调用hashCode()时,equals()方法也同时被执行。
public class MutableKey {
public MutableKey(int i, int j) {
public final int getI() {
public final void setI(int i) {
public final int getJ() {
public final void setJ(int j) {
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result +
result = prime * result +
public boolean equals(Object obj) {
if (this == obj) {
if (obj == null) {
if (!(obj instanceof MutableKey)) {
MutableKey other = (MutableKey)
if (i != other.i) {
if (j != other.j) {
public class MutableDemo {
public static void main(String[] args) {
// Object created
MutableKey key = new MutableKey(10, 20);
System.out.println(&Hash code: & + key.hashCode());
// Object State is changed after object creation.
key.setI(30);
key.setJ(40);
System.out.println(&Hash code: & + key.hashCode());
Hash code: 1291
Hash code: 1931
2、HashMap如何存储键值对
HashMap用Key的哈希值来存储和查找键值对。
当插入一个Entry时,HashMap会计算Entry Key的哈希值。Map会根据这个哈希值把Entry插入到相应的位置。
查找时,HashMap通过计算Key的哈希值到特定位置查找这个Entry。
3、在HashMap中使用可变对象作为Key带来的问题
如果HashMap Key的哈希值在存储键值对后发生改变,Map可能再也查找不到这个Entry了。
如果Key对象是可变的,那么Key的哈希值就可能改变。在HashMap中可变对象作为Key会造成数据丢失。
下面的例子将会向你展示HashMap中有可变对象作为Key带来的问题。
import java.util.HashM
import java.util.M
public class MutableDemo1 {
public static void main(String[] args) {
// HashMap
Map&MutableKey, String& map = new HashMap&&();
// Object created
MutableKey key = new MutableKey(10, 20);
// Insert entry.
map.put(key, &Robin&);
// This line will print 'Robin'
System.out.println(map.get(key));
// Object State is changed after object creation.
// i.e. Object hash code will be changed.
key.setI(30);
// This line will print null as Map would be unable to retrieve the
System.out.println(map.get(key));
4、如何解决
在HashMap中使用不可变对象。在HashMap中,使用String、Integer等不可变类型用作Key是非常明智的。
我们也能定义属于自己的不可变类。
如果可变对象在HashMap中被用作键,那就要小心在改变对象状态的时候,不要改变它的哈希值了。
在下面的Employee示例类中,哈希值是用实例变量id来计算的。一旦Employee的对象被创建,id的值就能再改变。只有name可以改变,但name不能用来计算哈希值。所以,一旦Employee对象被创建,它的哈希值不会改变。所以Employee在HashMap中用作Key是安全的。
示例代码:
import java.util.HashM
import java.util.M
public class MutableSafeKeyDemo {
public static void main(String[] args) {
Employee emp = new Employee(2);
emp.setName(&Robin&);
// Put object in HashMap.
Map&Employee, String& map = new HashMap&&();
map.put(emp, &Showbasky&);
System.out.println(map.get(emp));
// Change Employee name. Change in 'name' has no effect
// on hash code.
emp.setName(&Lily&);
System.out.println(map.get(emp));
class Employee {
// It is specified while object creation.
// Cannot be changed once object is created. No setter for this field.
public Employee(final int id) {
public final String getName() {
public final void setName(final String name) {
this.name =
public int getId() {
// Hash code depends only on 'id' which cannot be
// changed once object is created. So hash code will not change
// on object's state change
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result +
public boolean equals(Object obj) {
if (this == obj)
if (obj == null)
if (getClass() != obj.getClass())
Employee other = (Employee)
if (id != other.id)
现在你可能已经理解在HashMap中用可变对象作为Key的危险了。关于本文欢迎提出的任何建议和意见。
原文链接:
- 译文链接: [ 转载请保留原文出处、译者和译文链接。]
关于作者:
(新浪微博:)
所以真实情况是怎样
tony.chenjy
关于ImportNew
ImportNew 专注于 Java 技术分享。于日 11:11正式上线。是的,这是一个很特别的时刻 :)
ImportNew 由两个 Java 关键字 import 和 new 组成,意指:Java 开发者学习新知识的网站。 import 可认为是学习和吸收, new 则可认为是新知识、新技术圈子和新朋友……
新浪微博:
推荐微信号
反馈建议:ImportNew.
广告与商务合作QQ:
– 好的话题、有启发的回复、值得信赖的圈子
– 写了文章?看干货?去头条!
– 为IT单身男女服务的征婚传播平台
– 优秀的工具资源导航
– 活跃 & 专业的翻译小组
– 国内外的精选博客文章
– UI,网页,交互和用户体验
– JavaScript, HTML5, CSS
– 专注Android技术分享
– 专注iOS技术分享
– 专注Java技术分享
– 专注Python技术分享
& 2018 ImportNewAndroid Coder-new Object
Android基础-Android中的HashMap浅析
以下源码基于Android中改造过后的HashMap
0.HashMap中的关键变量
MINIMUN_CAPACITY = 4 (最小容量)
MAXIMUN_CAPACITY = 1 && 30 ; (最大容量)
private static final Entry[] EMPTY_TABLE= new HashMapEntry[MINIMUM_CAPACITY &&& 1]; 这里的这个就是hash表,是一种数组链表结构(和字典一样),默认的容量大小为4&&1,也就是2
DEFAULT_LOAD_FACTOR 负载因子,默认是0.75F
modCount 修改次数
threshold 阀值
1、HashMapEntry
看HashMapEntry的构造函数。
HashMapEntry(K key, V value, int hash, HashMapEntry&K, V& next) {
this.key =
this.value = value;
this.hash =
this.next =
从中可以看出,这是一个单链表的数据结构,存有key、value、hash值以及下一个节点。
2、HashMap的的初始化
HashMap(int capacity)
HashMap(int capacity,float loadFactor)
第三个构造方法,直接调用的是第一个构造方法,并对loadFactor进行判断(然而,这并没有什么吊用)
那么。我们就来看HashMap的代码吧。
public HashMap() {
table = (HashMapEntry&K, V&[]) EMPTY_TABLE;
threshold = -1;
这里的这个table是什么呢?因为这是个数组,而数组中每个元素都是单链表,所有,就构成table的样式了。
threshold = -1,看注释是说,首次调用替换掉EMPTY_TABLE.
3、添加数据
put(K key, V value)
putAll(Map
3.1、put(K key,V value)
@Override public V put(K key, V value) {
if (key == null) {
return putValueForNullKey(value);
int hash = Collections.secondaryHash(key);
HashMapEntry&K, V&[] tab =
int index = hash & (tab.length - 1);
for (HashMapEntry&K, V& e = tab[index]; e != null; e = e.next) {
if (e.hash == hash && key.equals(e.key)) {
preModify(e);
V oldValue = e.value;
e.value = value;
return oldV
modCount++;
if (size++ & threshold) {
tab = doubleCapacity();
index = hash & (tab.length - 1);
addNewEntry(key, value, hash, index);
return null;
若key为null,则将值存放在entryForNullKey(当然会做一些处理)
算出key对应的hash值
根据hash值计算出index值取到对应的链表,如果存在hash值相等并且key值相等的的Entry,就修改value值,并返回旧的value值
如果size++大于了阀值,对进行扩容并从新计算index值
插入一个新的Entry,并返回null
下面来对上面的1,4,5进行说明
3.1.1 putValueForNullKey操作
相对应的源码如下。
private V putValueForNullKey(V value) {
HashMapEntry&K, V& entry = entryForNullK
if (entry == null) {
addNewEntryForNullKey(value);
modCount++;
return null;
preModify(entry);
V oldValue = entry.value;
entry.value = value;
return oldV
这里对应的操作也很简单,如果当前entryForNullKey为null的话,就添加一个,不为null,就修改值
3.1.2 doubleCapacity() 扩容
扩容部分源代码较长,咱们分段来看。
HashMapEntry&K, V&[] oldTable =
int oldCapacity = oldTable.length;
if (oldCapacity == MAXIMUM_CAPACITY) {
return oldT
int newCapacity = oldCapacity * 2;
HashMapEntry&K, V&[] newTable = makeTable(newCapacity);
if (size == 0) {
return newT
如果到最大容量了,直接返回
将容量设置为原来的2倍
制造一个table,(ps:制造的时候会将阀值设置为3/4,(容量&&1) + (容量&&2),&&1 相当于/2,&&2 相当于/4)
如果size(原先存储的数目)为0,直接返回
for (int j = 0; j & oldC j++) {
* Rehash the bucket using the minimum number of field writes.
* This is the most subtle and delicate code in the class.
HashMapEntry&K, V& e = oldTable[j];
if (e == null) {
int highBit = e.hash & oldC
HashMapEntry&K, V& broken = null;
newTable[j | highBit] =
for (HashMapEntry&K, V& n = e.next; n != null; e = n, n = n.next) {
int nextHighBit = n.hash & oldC
if (nextHighBit != highBit) {
if (broken == null)
newTable[j | nextHighBit] =
broken.next =
highBit = nextHighB
if (broken != null)
broken.next = null;
return newT
上面代码的就是将原table中每一处对应的链表取出来,并且从新散列
3.1 addNewEntry添加新的Entry
table[index] = new HashMapEntry&K, V&(key, value, hash, table[index]);
其中table[index]就是一个单链表,这里就是生成一个HashMapEntry并将其插入到index处的,当然,我们还需要看一下生成的构造方法。
HashMapEntry(K key, V value, int hash, HashMapEntry&K, V& next) {
this.key =
this.value = value;
this.hash =
this.next =
结合逻辑可以知道,我们可以看的出,每次是在链表的头部进行数据插入的。
3.2、putAll
@Override public void putAll(Map&? extends K, ? extends V& map) {
ensureCapacity(map.size());
super.putAll(map);
ensureCapacity,确保容量(这里就是进行容量检查,不够扩容,具体的细节就不说了)
调用父类去put数据
在这里我们就需要明白父类的实现了。
public void putAll(Map&? extends K, ? extends V& map) {
for (Map.Entry&? extends K, ? extends V& entry : map.entrySet()) {
put(entry.getKey(), entry.getValue());
从中可以看出,如果穿进来的是另一个HashMap的话,就会将这个HashMap中的Entry挨个加入到运来的HashMap中。
4、get取值
取值的过程因为在散列与散列码中,有提到过,所以这里就不多说了。
HashMap查询快速的原因就在于hashtable的思想。就像字典一样。
本博文中只是简单的介绍了下HashMap。当然HaspMap中还有许多值得我们去思考的问题,诸如:
为什么是0.75?
初始容量为什么在Java8中改成2了
散列时index的算法
为什么从新散列是那样求index的
等等,这些问题,每一个都值得我们去好好地研究。
没有更多推荐了,
加入CSDN,享受更精准的内容推荐,与500万程序员共同成长!推荐这篇日记的豆列
&&&&&&&&&&&&
&(1人关注)新人专享好礼凡未购买过小册的用户,均可领取三张 5 折新人专享券,购买小册时自动使用专享券,最高可节省 45 元。小册新人 5 折券最高可省 15 元小册新人 5 折券最高可省 15 元小册新人 5 折券最高可省 15 元注:专享券的使用期限在领券的七天内。一键领取购买小册时自动使用专享券前往小册首页本活动仅适用于小册新用户知道了几乎是史上最全最实用的Android性能全面分析与优化方案研究写在前面,如果面对复杂的动画效果你一筹莫展,不妨看看这篇文章:
该文章是结合我司产品手机迅雷做的一个全面的性能分析及优化方案。本文篇幅较长,几乎涵盖了所有的性能方面问题,以及给出了如何查找和解决问题的方案,几乎是史上最全最实用的Android性能分析和优化文章。
另外,由于简书对MarkDown的支持问题导致图片格式看着有点乱,还请多多谅解,细心阅读。
性能问题分类
性能优化原则和方法
借助性能优化工具分析解决问题
性能优化指标
1、渲染问题: 过度绘制、布局冗杂
2、内存问题: 内存浪费(内存管理)、内存泄漏
3、功耗问题: 耗电
1、性能优化原则
坚持性能测试(开发和测试同学的测试方法略有不同):不要凭感觉去检测性能问题、评估性能优化的效果,应该保持足够多的测量,用数据说话(主要针对测试同学)。使用各种性能工具测试及快速定位问题(主要针对开发同学)。
使用低配置的设备:同样的程序,在低端配置的设备中,相同的问题会暴露得更为明显。
权衡利弊:在能够保证产品稳定、按时完成需求的前提下去做优化。
2、优化方法
了解问题(分为可感知和不可感知的性能问题):对于性能问题来讲,这个步骤只适用于某些明显的性能问题,很多无法感知的性能问题需要通过工具定位。例如:内存泄漏、层级冗杂、过度绘制等无法感知。滑动卡顿是可以感知到的。
定位问题:通过工具检测、分析数据,定位在什么地方存在性能问题。
分析问题:找到问题后,分析针对这个问题该如何解决,确定解决方案。
解决问题:根据分析结果寻找解决方案。
验证问题:保证优化有效,没有产生新的问题,以及产品稳定性。
以下优化工具在下面文章中具体介绍使用方法。
1、手机开发者选项:调试GPU过度绘制、启用严格模式、显示CPU使用情况、GPU呈现模式分析、显示所有"应用程序无响应"。(小米手机开发开发者选项中名字)
2、IDE中:Android Studio,比如静态代码检测工具、Memory Monitor、CPU Monitor、NetWork Monitor、GPU Monitor、Layout Inspector、Analyze APK等。
3、SDK中:sdk\tools,比如DDMS、HierarchyViewer、TraceView等。
4、第三方工具:MAT、LeakCanary、GT等。
滑动流畅度:FPS,即Frame per Second,一秒内的刷新帧数,越接近60帧越好;
过度绘制:单页面的3X(粉红色区域) Overdraw小于25%
启动时间:这里主要说的是Activity界面启动时间,一般低于300ms,需要用高频摄像机计算时间。
内存大小:峰值越低越好,需要优化前后做对比
内存泄漏:需要用工具检查对比优化前后
单位时间内的掉电量,掉电量越少越好,业内没有固定标准。华为有专门测试功耗的机器,以及自己的标准。
先来看看造成应用UI卡顿的常见原因都有哪些?
1、人为在UI线程中做轻微耗时操作,导致UI线程卡顿;
2、布局Layout过于复杂,无法在16ms内完成渲染;
3、同一时间动画执行的次数过多,导致CPU或GPU负载过重;
4、View过度绘制,导致某些像素在同一帧时间内被绘制多次,从而使CPU或GPU负载过重;
5、View频繁的触发measure、layout,导致measure、layout累计耗时过多及整个View频繁的重新渲染;
6、内存频繁触发GC过多(同一帧中频繁创建内存),导致暂时阻塞渲染操作;
7、冗余资源及逻辑等导致加载和执行缓慢;
8、臭名昭著的ANR;
大多数用户感知到的卡顿等性能问题的最主要根源都是因为渲染性能。(Google官方说的)
Android系统每隔16ms发出VSYNC信号(vertical synchronization --场扫描同步,场同步,垂直同步),触发对UI进行渲染,如果每次渲染都成功,这样就能够达到流畅的画面所需要的60fps,为了能够实现60fps,这意味着程序的大多数操作都必须在16ms(.67ms)内完成。
如果你的某个操作花费时间是24ms,系统在得到VSYNC信号的时候就无法进行正常渲染,这样就发生了丢帧现象。那么用户在32ms内看到的会是同一帧画面。
Overdraw(过度绘制)描述的是屏幕上的某个像素在同一帧的时间内被绘制了多次。在多层次的UI结构里面,如果不可见的UI也在做绘制的操作,这就会导致某些像素区域被绘制了多次。这就浪费大量的CPU以及GPU资源,找出界面滑动不流畅、界面启动速度慢、手机发热。
如何查看过度绘制?
设置 — 开发中选项 — 调试GPU过度绘制
来看看手雷里的过度绘制和优化效果(目前手雷还存在很多待优化的页面)
上图中的各种颜色都代表什么意思?
每个颜色的说明如下:
原色:没有过度绘制
紫色:1 次过度绘制
绿色:2 次过度绘制
粉色:3 次过度绘制
红色:4 次及以上过度绘制
造成过度优化的关键是什么?多余的背景(Background)
接下来举例说明:
1、MainTabActivity
在MainTabActivity的Theme中修改背景
去除布局(main_activity_linerlayout.xml)中的background
如果不给当前Activity设置主题,默认主题是什么,默认主题背景是什么?
可以在默认主题中添加通用主题背景
&item name="android:windowBackground"&@drawable/common_layout_content_bkg&/item&
&item name="android:windowBackground"&null&/item&
2、除了布局中多余背景,还有可能在代码里添加了多余的背景。
查看分享弹窗的布局代码发现只有一个background,但为什么会过度绘制呢?
代码修改(SharePlatformsDialog.java)
3、弹窗底部布局不会导致弹窗本身过度绘制
弹窗的绘制是属于剪切式绘制不是覆盖绘制,蒙层是透明度亮度的调节不是绘制一层灰色。
如果我们不想用系统dialog而是自定义一个弹窗view,就需要考虑过度绘制问题。
4、自定义view时,通过Canvas的clipRect方法控制每个视图每次刷新的区域,这样可以避免刷新不必要的区域,从而规避过渡绘制的问题。还可以使用canvas.quickreject()来判断是否和某个矩形相交,从而跳过那些非矩形区域内的绘制操作。参考:
优化方法和步骤关键总结
总结一下,优化步骤如下:
1、移除或修改Window默认的Background
2、移除XML布局文件中非必需的Background
3、按需显示占位背景图片
4、控制绘制区域
布局太过复杂,层级嵌套太深导致绘制操作耗时,且增加内存的消耗。
我们的目标就是,层级扁平化。
布局优化的建议:
第一个建议:可以使用相对布局减少层级的就使用相对布局,否则使用线性布局。Android中RelativeLayout和LinearLayout性能分析,参考:
第二个建议:用merge标签来合并布局,这可以减少布局层次。
第三个建议:用include标签来重用布局,抽取通用的布局可以让布局的逻辑更清晰明了,但要避免include乱用。
第四个建议:避免创建不必要的布局层级。(最容易发生的!)
第五个建议:使用惰性控件ViewStub实现布局动态加载
如何借助工具查看代码布局?
Android SDK 工具箱中有一个叫做 Hierarchy Viewer 的工具,能够在程序运行时分析 Layout。
可以用这个工具找到 Layout 的性能瓶颈
该工具的使用条件:模拟器或者Root版真机。
如何开启该功能:AndroidStudio中,Tools — Android — Android Devices Monitor
该工具的缺点:使用起来麻烦。
看看项目中遇到的问题(MainTabAvtivity)。
merge标签的使用
未使用merge,例如:XLTabLayout.java
使用merge,例如:账号信息页的条目UserAccountItem
include标签的使用导致的问题
避免创建不必要的层级(MainTabActivity)
ViewStub的使用
这个标签最大的优点是当你需要时才会加载,使用他并不会影响UI初始化时的性能。
通常情况下我们需要在某个条件下使用某个布局的时候会通过gone或者invisible来隐藏,其实这样的方式虽然隐藏了布局,但是当显示该界面的时候还是将该布局实例化的。使用ViewStub可以避免内存的浪费,加快渲染速度。
其实ViewStub就是一个宽高都为0的一个View,它默认是不可见的,只有通过调用setVisibility函数或者Inflate函数才会将其要装载的目标布局给加载出来,从而达到延迟加载的效果,这个要被加载的布局通过android:layout属性来设置。
当准备inflate ViewStub时,调用inflate()方法即可。还可以设定ViewStub的Visibility为VISIBLE或INVISIBLE,也会触发inflate。注意的是,使用inflate()方法能返回布局文件的根View。
((ViewStub) findViewById(R.id.stub_import)).setVisibility(View.VISIBLE);
View importPanel = ((ViewStub) findViewById(R.id.stub_import)).inflate();
setVisibility的时候会触发了inflate
注意:使用ViewStub加载的布局中不能使用merge标签。
看看Space标签(不常用)
space标签可以只在布局文件中占位,不绘制,Space标签有对应的java类Space.java,通过阅读源码可以发现,它继承至View.java,并且复写了draw方法,该方法为空,既没有调用父类的draw方法,也没有执行自己的代码,表示该类是没有绘制操作的,但onMeasure方法正常调用,说明是有宽高的。
主要功能用来设置间距,这个标签不常用,常使用margin或padding。
GPU呈现模式分析(大致定位问题)
开发者选项 — GPU呈现模式分析 — 选择“在屏幕上显示为条形图”
Android开发者选项——Gpu呈现模式分析,参考:
自动播放的视频停止的时候会有两条很长的柱线,下个视频播放的时候还会有一条。这里有一个明显的卡顿。
播放器操作(DefaultPlayerView.java - doPlay,player_auto_control_layout.xml):
上图的E total time = 68 是播放器停止播放的时候耗费的时间。
total time = 29 是播放器开始播放的时候耗费的时间。
其中,大部分时间耗费在了 5total time = 18 上面,这个是inflate播放器界面的时候耗费的时间。
是不是所有的inflate都很耗费时间,看一下账号信息页:
GPU Monitor
启用严格模式(不止渲染性能)
应用在主线程上执行长时间操作时会闪烁屏幕。
通过代码进行严格模式(StrictMode)调试,参考:
程序内存的管理是否合理高效对应用的性能有着很大的影响。
推荐阅读Android性能优化典范-第3季,参考:
ArrayMap(我们项目中没有用到,Android源码中很多使用)
Android为移动操作系统特意编写了一些更加高效的容器,例如ArrayMap、SparseArray。
为了解决HashMap更占内存的弊端,Android提供了内存效率更高的ArrayMap。
先来看看HashMap的原理
HashMap的整体结构如下:
存储位置的确定流程:
!()[http://img.hb.aicdn.com/16bbbfdb9a09-UTrZIe]
再看来看看ArrayMap是如何优化内存的
它内部使用两个数组进行工作,其中一个数组记录key hash过后的顺序列表,另外一个数组按key的顺序记录Key-Value值,如下图所示:
当你想获取某个value的时候,ArrayMap会计算输入key转换过后的hash值,然后对hash数组使用二分查找法寻找到对应的index,然后我们可以通过这个index在另外一个数组中直接访问到需要的键值对。
既然ArrayMap中的内存占用是连续不间断的,那么它是如何处理插入与删除操作的呢?它跟HashMap有什么区别?二者之间的删除插入效率有什么差异?请看下图所示,演示了Array的特性:
HashMap与ArrayMap之间的内存占用效率对比图如下:
与HashMap相比,ArrayMap在循环遍历的时候更加高效。
什么时候使用ArrayMap呢?
1、对象个数的数量级最好是千以内,没有频繁的插入删除操作
2、数据组织形式包含Map结构
Autoboxing(避免自动装箱)
Autoboxing的行为还经常发生在类似HashMap这样的容器里面,对HashMap的增删改查操作都会发生了大量的autoboxing的行为。当key是int类型的时候,HashMap和ArrayMap都有Autoboxing行为。
SparseArray(项目中用到较多 -- 后面再说如何利用工具查找该用SparseArray而没有用到的地方)
为了避免Autoboxing行为Android提供了SparseArray,此容器使用于key为int类型。
SparseBooleanMap,SparseIntMap,SparseLongMap等容器,是key为int,value类型相应为boolean、int、long等。
Enum(枚举,项目中较多使用,应尽量避免)
Enums often require more than twice as much memory as static constants. You should strictly avoid using enums on Android.
Android官方强烈建议不要在Android程序里面使用到enum。
关于enum的效率,请看下面的讨论。假设我们有这样一份代码,编译之后的dex大小是2556 bytes,在此基础之上,添加一些如下代码,这些代码使用普通static常量相关作为判断值:
增加上面那段代码之后,编译成dex的大小是2680 bytes,相比起之前的2556 bytes只增加124 bytes。假如换做使用enum,情况如下:
使用enum之后的dex大小是4188 bytes,相比起2556增加了1632 bytes,增长量是使用static int的13倍。不仅仅如此,使用enum,运行时还会产生额外的内存占用,如下图所示:
推荐一些文章:
HashMap,ArrayMap,SparseArray源码分析及性能对比,参考:
Android性能优化--小心自动装箱:
Android性能优化篇:Android中如何避免创建不必要的对象:
HashMap、ArrayMap、SparseArray分析比较:
Android性能优化之String篇:
SharedPreferences的commit和apply分析:
什么是内存泄漏?
一些不用的对象被长期持有,导致内存无法被释放。
可能发生内存泄漏的地方有哪些?
内部类引用导致Activity的泄漏
在Java中,非静态(匿名)内部类会默认隐性引用外部类对象。而静态内部类不会引用外部类对象。
最典型的场景是Handler导致的Activity泄漏,如果Handler中有延迟的任务或者是等待执行的任务队列过长,都有可能因为Handler继续执行而导致Activity发生泄漏。
为了解决这个问题,可以在UI退出之前,执行remove Handler消息队列中的消息与runnable对象。或者是使用Static + WeakReference的方式来达到断开Handler与Activity之间存在引用关系的目的。
举例,MainTabActivity - MainTabHandler:
如何修复?
Android Weak Handler:可以避免内存泄漏的Handler库,参考:
Activity Context被传递到其他实例中,这可能导致自身被引用而发生泄漏。
考虑使用Application Context而不是Activity Context。
例如:全局Dialog或者Context被单例持有。
静态造成的内存泄漏
还有静态变量持有View,例如:
private static V
void setStaticView() {
view = findViewById(R.id.sv_button);
注意监听器的注销(稍后利用工具分析一个例子)
regist就要unregist
注意Cursor对象是否及时关闭(项目中也存在,不再列举)
WebView的引起的泄漏(暂时没有研究)
使用工具分析定位解决内存泄漏
Memory monitor
通过MemoryMonitor可以看到,启动手雷进入手雷的内存情况如下(为什么没有做任何操作内存一直在增加?):
通过实例分析一处内存泄漏,操作步骤如下:
启动手雷 - 进入首页 - 切换底部tab到我的tab - 点击登录弹窗弹窗 - 登录成功返回首页
MAT(Memory Analyzer Tool)
需要下载MAT独立版,可到这里下载解压使用:\\192.168.8.188\上传2\ltsoft
分析刚才的操作内存情况
进入首页并没有进行任何活动操作,为什么会有那么多bitmap对象呢?
LeakCanary
LeakCanary 中文使用说明,参考:
LeakCanary:让内存泄露无所遁形,参考:
TraceView(不做详细分析)
GT(应该更适合测试同学测试APP性能)
利用GT,仅凭一部手机,无需连接电脑,您即可对APP进行快速的性能测试(CPU、内存、流量、电量、帧率/流畅度等等)、开发日志的查看、Crash日志查看、网络数据包的抓取、APP内部参数的调试、真机代码耗时统计等。
内存使用策略优化
看看下载一个视频加上浏览一下精选页,然后将应用切到后台,内存使用情况
有什么优化内存的策略
onLowMemory():Android系统提供了一些回调来通知当前应用的内存使用情况,通常来说,当所有的background应用都被kill掉的时候,forground应用会收到onLowMemory()的回调。在这种情况下,需要尽快释放当前应用的非必须的内存资源,从而确保系统能够继续稳定运行。
onTrimMemory(int):Android系统从4.0开始还提供了onTrimMemory()的回调,当系统内存达到某些条件的时候,所有正在运行的应用都会收到这个回调,同时在这个回调里面会传递参数,代表不同的内存使用情况,收到onTrimMemory()回调的时候,需要根据传递的参数类型进行判断,合理的选择释放自身的一些内存占用,一方面可以提高系统的整体运行流畅度,另外也可以避免自己被系统判断为优先需要杀掉的应用。
文章推荐:
Android内存优化之OOM:
内存泄露从入门到精通三部曲之基础知识篇:
内存泄露从入门到精通三部曲之排查方法篇:
内存泄露从入门到精通三部曲之常见原因与用户实践:
胡凯Android性能典范系列
上面分析的一些项目中的问题,怎么找到的呢?
Lint:静态代码分析工具
如何通过Lint查找项目中的问题,如何使用?
如果只想分析某个文件夹的代码
设置代码分析选项
例如:关闭屏幕时关闭掉登录ping,屏幕亮时再打开。
以上的很多问题都会导致耗电量增加。
如何优化后台下载时的耗电手机发烫问题?
需要具体的硬件工具测试应用耗电量,需要一套耗电量测试标准。
加入掘金和开发者一起成长。发送简历到 hr@xitu.io,期待你的加入!分享

我要回帖

更多关于 散列表查找 的文章

 

随机推荐