1. android arraymap与hashmap相比真的占用内存小么
不一定,还是要看数据量。
相同情况下,一个定值,一个一次一次的取内存。
2. android simplearraymap 属于哪个jar包
因为有的jar包已经封装好了要写的工具了,直接掉就可以。jar里面确实不能包含res的资源文件,调用的时候会报错“找不到资源”或“空指针”。现在一般的解决办法在把项目打包成jar的时候不要勾选res资源文件打包。并且在自己的jar工程里写一个类用来读取res资源文件,然后将工程里的所有带有R.id,R.layout之类的都用这个类去找资源。最后在要引用这个jar文件的工程中把属于jar的资源文件复制到工程里面。
3. 在Android开发中,有哪些好的内存优化方式
1. 使用更加轻量的数据结构
例如,我们可以考虑使用ArrayMap/SparseArray而不是HashMap等传统数据结构。通常的HashMap的实现方式更加消耗内存,因为它需要一个额外的实例对象来记录Mapping操作。另外,SparseArray更加高效,在于他们避免了对key与value的自动装箱(autoboxing),并且避免了装箱后的解箱。
2. 避免在Android里面使用Enum
Android官方培训课程提到过“Enums often require more than twice as much memory as static constants. You should strictly avoid using enums on Android.”,具体原理请参考《Android性能优化典范(三)》,所以请避免在Android里面使用到枚举。
3. 减小Bitmap对象的内存占用
Bitmap是一个极容易消耗内存的大胖子,减小创建出来的Bitmap的内存占用可谓是重中之重,,通常来说有以下2个措施:
inSampleSize:缩放比例,在把图片载入内存之前,我们需要先计算出一个合适的缩放比例,避免不必要的大图载入。
decode format:解码格式,选择ARGB_8888/RBG_565/ARGB_4444/ALPHA_8,存在很大差异
4.Bitmap对象的复用
缩小Bitmap的同时,也需要提高BitMap对象的复用率,避免频繁创建BitMap对象,复用的方法有以下2个措施
LRUCache : “最近最少使用算法”在Android中有极其普遍的应用。ListView与GridView等显示大量图片的控件里,就是使用LRU的机制来缓存处理好的Bitmap,把近期最少使用的数据从缓存中移除,保留使用最频繁的数据,
inBitMap高级特性:利用inBitmap的高级特性提高Android系统在Bitmap分配与释放执行效率。使用inBitmap属性可以告知Bitmap解码器去尝试使用已经存在的内存区域,新解码的Bitmap会尝试去使用之前那张Bitmap在Heap中所占据的pixel data内存区域,而不是去问内存重新申请一块区域来存放Bitmap。利用这种特性,即使是上千张的图片,也只会仅仅只需要占用屏幕所能够显示的图片数量的内存大小
4. 使用更小的图片
在涉及给到资源图片时,我们需要特别留意这张图片是否存在可以压缩的空间,是否可以使用更小的图片。尽量使用更小的图片不仅可以减少内存的使用,还能避免出现大量的InflationException。假设有一张很大的图片被XML文件直接引用,很有可能在初始化视图时会因为内存不足而发生InflationException,这个问题的根本原因其实是发生了OOM。
5.StringBuilder
在有些时候,代码中会需要使用到大量的字符串拼接的操作,这种时候有必要考虑使用StringBuilder来替代频繁的“+”。
6.避免在onDraw方法里面执行对象的创建
类似onDraw等频繁调用的方法,一定需要注意避免在这里做创建对象的操作,因为他会迅速增加内存的使用,而且很容易引起频繁的gc,甚至是内存抖动。
7. 避免对象的内存泄露
类的静态变量持有大数据对象
静态变量长期维持到大数据对象的引用,阻止垃圾回收。
非静态内部类存在静态实例
非静态内部类会维持一个到外部类实例的引用,如果非静态内部类的实例是静态的,就会间接长期维持着外部类的引用,阻止被回收掉。
资源对象未关闭
资源性对象比如(Cursor,File文件等)往往都用了一些缓冲,我们在不使用的时候,应该及时关闭它们, 以便它们的缓冲及时回收内存。它们的缓冲不仅存在于java虚拟机内,还存在于java虚拟机外。 如果我们仅仅是把它的引用设置为null,而不关闭它们,往往会造成内存泄露。
解决办法: 比如SQLiteCursor(在析构函数finalize(),如果我们没有关闭它,它自己会调close()关闭), 如果我们没有关闭它,系统在回收它时也会关闭它,但是这样的效率太低了。 因此对于资源性对象在不使用的时候,应该调用它的close()函数,将其关闭掉,然后才置为null. 在我们的程序退出时一定要确保我们的资源性对象已经关闭。 程序中经常会进行查询数据库的操作,但是经常会有使用完毕Cursor后没有关闭的情况。如果我们的查询结果集比较小, 对内存的消耗不容易被发现,只有在常时间大量操作的情况下才会复现内存问题,这样就会给以后的测试和问题排查带来困难和风险,记得try catch后,在finally方法中关闭连接
Handler内存泄漏
Handler作为内部类存在于Activity中,但是Handler生命周期与Activity生命周期往往并不是相同的,比如当Handler对象有Message在排队,则无法释放,进而导致本该释放的Acitivity也没有办法进行回收。
4. android arraymap与hashmap相比真的占用内存小么
HashMap简介 综述:HashMap 是一个散列表,它存储的内容是键值对(key-value)映射。 1.HashMap 继承于AbstractMap,实现了Map、Cloneable、java.io.Serializable接口。 2.HashMap 的实现不是同步的,这意味着它不是线程安全的。它的key、value都可以为null。此外,HashMap中的映射不是有序的。 3.HashMap 的实例有两个参数影响其性能:“初始容量” 和 “加载因子”。容量 是哈希表中桶的数量,初始容量 只是哈希表在创建时的容量。加载因子 是哈希表在其容量自动增加之前可以达到多满的一种尺度。当哈希表中的条目数超出了加载因子与当前容量的乘积时,则要对该哈希表进行 rehash 操作(即重建内部数据结构),从而哈希表将具有大约两倍的桶数。 通常,默认加载因子是 0.75, 这是在时间和空间成本上寻求一种折衷。加载因子过高虽然减少了空间开销,但同时也增加了查询成本(在大多数 HashMap 类的操作中,包括 get 和 put 操作,都反映了这一点)。在设置初始容量时应该考虑到映射中所需的条目数及其加载因子,以便最大限度地减少 rehash 操作次数。如果初始容量大于最大条目数除以加载因子,则不会发生 rehash 操作。
5. Android开发中ArrayMap与HashMap有哪些不同
1、存储方式不同
HashMap内部有一个HashMapEntry<K, V>[]对象,每一个键值对都存储在这个对象里,当使用put方法添加键值对时,就会new一个HashMapEntry对象,具体实现如下:
[java] view plain
@Override public V put(K key, V value) {
if (key == null) {
return putValueForNullKey(value);
}
int hash = secondaryHash(key);
HashMapEntry<K, V>[] tab = table;
int index = hash & (tab.length - 1);
//先查找有没有对应的key值,如果有,就改写value,并返回改写前的value值:oldValue
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 oldValue;
}
}
// No entry for (non-null) key is present; create one
modCount++;
if (size++ > threshold) {
//扩容,双倍
tab = doubleCapacity();
index = hash & (tab.length - 1);
}
addNewEntry(key, value, hash, index);
return null;
}
//创建对象存储键值对
void addNewEntry(K key, V value, int hash, int index) {
table[index] = new HashMapEntry<K, V>(key, value, hash, table[index]);
}
ArrayMap的存储中没有Entry这个东西,他是由两个数组来维护的
[java] view plain
int[] mHashes;
Object[] mArray;
mHashes数组中保存的是每一项的HashCode值,mArray中就是键值对,每两个元素代表一个键值对,前面保存key,后面的保存value,我们看看下面代码的结果:
[java] view plain
arraymap = new HashMap<String, String>();
a.put("a", "a_value");
a.put("b", "b_value");
执行上面代码后,arraymap中的存储是这样的
是不是能清楚地看到ArrayMap的存储了,这种存储在put代码中如下:
[java] view plain
mHashes[index] = hash;
mArray[index<<1] = key;
mArray[(index<<1)+1] = value;
2、添加数据时扩容时的处理不一样
先来看看HashMap
[java] view plain
if (size++ > threshold) {
tab = doubleCapacity();
index = hash & (tab.length - 1);
}
doubleCapacity进行双倍扩容,它的代码中有这么一句话
[java] view plain
HashMapEntry<K, V>[] newTable = makeTable(newCapacity);
最终,这个newTable将作为扩容后的新对象返回,那么makeTable做了什么呢,如下:
[java] view plain
private HashMapEntry<K, V>[] makeTable(int newCapacity) {
@SuppressWarnings("unchecked") HashMapEntry<K, V>[] newTable
= (HashMapEntry<K, V>[]) new HashMapEntry[newCapacity];
table = newTable;
threshold = (newCapacity >> 1) + (newCapacity >> 2); // 3/4 capacity
return newTable;
}
我们清楚地看到,这里进行了new操作,重新创建对象,开销很大。
那么ArrayMap呢,看看:
[java] view plain
//如果容量不够
ize >= mHashes.length) {
final int n = mSize >= (BASE_SIZE*2) ? (mSize+(mSize>>1))
: (mSize >= BASE_SIZE ? (BASE_SIZE*2) : BASE_SIZE);
if (DEBUG) Log.d(TAG, "put: grow from " + mHashes.length + " to " + n);
final int[] ohashes = mHashes;
final Object[] oarray = mArray;
//分配数组
allocArrays(n);
if (mHashes.length > 0) {
if (DEBUG) Log.d(TAG, "put: 0-" + mSize + " to 0");
//特别注意这,是,而不是new,效率提升
System.array(ohashes, 0, mHashes, 0, ohashes.length);
System.array(oarray, 0, mArray, 0, oarray.length);
}
//释放无用空间,收缩数组
freeArrays(ohashes, oarray, mSize);
}
ArrayMap用的是数据,所以效率相对要高。
3、ArrayMap提供了数组收缩的功能,在clear或remove后,会重新收缩数组,是否空间
4、ArrayMap采用二分法查找;
以上就是android开发中,HashMap与ArrayMap的区别,大家在涉及到内存方面的实现,可根据实际情况选择这两种不同的方式。
6. 22 AndroidBroadcast广播机制
广播(Broadcast)机制用于进程/线程间通信,广播分为广播发送和广播接收两个过程,其中广播接收者BroadcastReceiver便是Android四大组件之一。
BroadcastReceiver分为两类:
从广播发送方式可分为三类:
广播在系统中以BroadcastRecord对象来记录, 该对象有几个时间相关的成员变量.
广播注册,对于应用开发来说,往往是在Activity/Service中调用 registerReceiver() 方法,而Activity或Service都间接继承于Context抽象类,真正干活是交给ContextImpl类。另外调用getOuterContext()可获取最外层的调用者Activity或Service。
[ContextImpl.java]
其中broadcastPermission拥有广播的权限控制,scheler用于指定接收到广播时onRecive执行线程,当scheler=null则默认代表在主线程中执行,这也是最常见的用法
[ContextImpl.java]
ActivityManagerNative.getDefault()返回的是ActivityManagerProxy对象,简称AMP.
该方法中参数有mMainThread.getApplicationThread()返回的是ApplicationThread,这是Binder的Bn端,用于system_server进程与该进程的通信。
[-> LoadedApk.java]
不妨令 以BroadcastReceiver(广播接收者)为key,LoadedApk.ReceiverDispatcher(分发者)为value的ArrayMap 记为 A 。此处 mReceivers 是一个以 Context 为key,以 A 为value的ArrayMap。对于ReceiverDispatcher(广播分发者),当不存在时则创建一个。
此处mActivityThread便是前面传递过来的当前主线程的Handler.
ReceiverDispatcher(广播分发者)有一个内部类 InnerReceiver ,该类继承于 IIntentReceiver.Stub 。显然,这是一个Binder服务端,广播分发者通过rd.getIIntentReceiver()可获取该Binder服务端对象 InnerReceiver ,用于Binder IPC通信。
[-> ActivityManagerNative.java]
这里有两个Binder服务端对象 caller 和 receiver ,都代表执行注册广播动作所在的进程. AMP通过Binder驱动将这些信息发送给system_server进程中的AMS对象,接下来进入AMS.registerReceiver。
[-> ActivityManagerService.java]
其中 mRegisteredReceivers 记录着所有已注册的广播,以receiver IBinder为key, ReceiverList为value为HashMap。
在BroadcastQueue中有两个广播队列mParallelBroadcasts,mOrderedBroadcasts,数据类型都为ArrayList<broadcastrecord style="box-sizing: border-box;">:</broadcastrecord>
mLruProcesses数据类型为 ArrayList<ProcessRecord> ,而ProcessRecord对象有一个IApplicationThread字段,根据该字段查找出满足条件的ProcessRecord对象。
该方法用于匹配发起的Intent数据是否匹配成功,匹配项共有4项action, type, data, category,任何一项匹配不成功都会失败。
broadcastQueueForIntent(Intent intent)通过判断intent.getFlags()是否包含FLAG_RECEIVER_FOREGROUND 来决定是前台或后台广播,进而返回相应的广播队列mFgBroadcastQueue或者mBgBroadcastQueue。
注册广播:
另外,当注册的是Sticky广播:
广播注册完, 另一个操作便是在广播发送过程.
发送广播是在Activity或Service中调用 sendBroadcast() 方法,而Activity或Service都间接继承于Context抽象类,真正干活是交给ContextImpl类。
[ContextImpl.java]
[-> ActivityManagerNative.java]
[-> ActivityManagerService.java]
broadcastIntent()方法有两个布尔参数serialized和sticky来共同决定是普通广播,有序广播,还是Sticky广播,参数如下:
broadcastIntentLocked方法比较长,这里划分为8个部分来分别说明。
这个过程最重要的工作是:
BroadcastReceiver还有其他flag,位于Intent.java常量:
主要功能:
这个过主要处于系统相关的10类广播,这里不就展开讲解了.
这个过程主要是将sticky广播增加到list,并放入mStickyBroadcasts里面。
其他说明:
AMS.collectReceiverComponents :
广播队列中有一个成员变量 mParallelBroadcasts ,类型为ArrayList<broadcastrecord style="box-sizing: border-box;">,记录着所有的并行广播。</broadcastrecord>
动态注册的registeredReceivers,全部合并都receivers,再统一按串行方式处理。
广播队列中有一个成员变量 mOrderedBroadcasts ,类型为ArrayList<broadcastrecord style="box-sizing: border-box;">,记录着所有的有序广播。</broadcastrecord>
发送广播过程:
处理方式:
可见不管哪种广播方式,都是通过broadcastQueueForIntent()来根据intent的flag来判断前台队列或者后台队列,然后再调用对应广播队列的scheleBroadcastsLocked方法来处理广播;
在发送广播过程中会执行 scheleBroadcastsLocked 方法来处理相关的广播
[-> BroadcastQueue.java]
在BroadcastQueue对象创建时,mHandler=new BroadcastHandler(handler.getLooper());那么此处交由mHandler的handleMessage来处理:
由此可见BroadcastHandler采用的是”ActivityManager”线程的Looper
[-> BroadcastQueue.java]
此处mService为AMS,整个流程还是比较长的,全程持有AMS锁,所以广播效率低的情况下,直接会严重影响这个手机的性能与流畅度,这里应该考虑细化同步锁的粒度。
7. android arraymap与hashmap相比真的占用内存小么
在认识hashmap中要先认识Map。在数组中我们是通过数组下标来对其内容索引的,而在Map中我们通过对象来对对象进行索引,用来索引的对象叫做key,其对应的对象叫做value。 HashMap的初始过程 :在并发环境下使用HashMap 而没有做同步,可能会引起死循环,关于这一点,sun的官方网站上已有阐述,这并非是bug。 HashMap的数据结构 :HashMap主要是用数组来存储数据的,我们都知道它会对key进行哈希运算,哈系运算会有重复的哈希值,对于哈希值的冲突,HashMap采用链表来解决的。在HashMap里有这样的一句属性声明: transient Entry[] table; 因此hashmap可以在andriod中用来存储数据。
8. 内存优化之ArrayMap、SparseArray、SparseIntArray
这几个数据结构呢,都是Android这家伙搞出来;为啥呢,打的旗号是节约内存,系统的很多地方都有他们的身影;为了追随google的脚步,也为了扩展自己对于数据结构的理解,我们有必要学习它;
这里先大致说下,ArrayMap是一个map,是为了解决HashMap存储数据浪费空间情况;SparseArray是数组,为稀疏数据准备的;SparseXXXArray,也是数组,也为稀疏数据准备,xxx是基本数据类型,这样使用时不存在自动拆装箱操作(就是基本类型和相对应类之间的自动转换);SparseXXXArray的原理实现一致,这里只介绍SparseIntArray
ArrayMap结构
两个数组来存储;key的hash数据,key-value组成的数组;通过index来映射,2倍位置为key, 2倍位置+1 为value;mHashes数据,是从小到大有序存储的
SparseArray结构
也是两个数组,存储数组索引的key,存储数据value,通过相等索引来映射;mKeys是从小到大有序存储的
从图来看,我觉得是结构思想整体是一致的;但他们的实现思路还是存在不同的地方
这里key是可以为null,null是hash值为0,不为空时,其计算方式与下面变量有关,这个可以仅可以在构造器中设置,默认为false
计算区别如下:
由于是以hash来排序,如果使用默认,那么我们的hashCode方法,要是每个对象固定,最好且是不同的
我们有必要先介绍以下,申请内存缓存机制,这涉及到下面两个数组,其原理是一样的,只是容量不同;
mBaseCache的容量大小为8, mTwiceBaseCache容量大小为16;两个缓存每种最多缓存10个;缓存结构如下图,入量按照8来绘制的:
这里的容量是申请的数组容量,数据存储key-value,所以其能存储的数据个数为其一半; 初始化、扩容都是根据存储个数来计算的
初始化
扩容
调用下面两个方法时,可能会扩容
数组的移动System.array方法处理,如果容器大小增加了,则废弃的数组;废弃的数组,进行回收,如果存储容量大小为4或者8,会放入缓存中
查找可以插入或者替换数据的位置;查找思想:
查找位置为正整数,说明此处有数据,可替换,负整数,表示无数据且取非表示可插入位置
增加数据时,可能会扩容;如果数据未找到且容器存储个数已满,这时会进行扩容
删除数据时,可能会进行容量缩减
如果你上面ArrayMap已经懂了,那么SparseIntArray其实已经很简单了;它存在下面的差异
SparseArray和SparseXXXArray那就更接近了;不过我们还是和ArrayMap做对比吧
和SparseXXXArray区别就是:无gc方法机制、存储value值是类而不是基本数据
gc方法:如果有效数据前存在删除数据标志值DELETED,则后面把有效数据往前移位
有没有小伙伴在想,为何SparseXXXArray,没有这种gc行为呢,因为基本类型,存什么都无法标志其是无效数据啊;
技术变化都很快,但基础技术、理论知识永远都是那些;作者希望在余后的生活中,对常用技术点进行基础知识分享;如果你觉得文章写的不错,请给与关注和点赞;如果文章存在错误,也请多多指教!