android 分享总是报"android 反射获取资源源ID失败"的错,为什么

安全检查中...
请打开浏览器的javascript,然后刷新浏览器
< 浏览器安全检查中...
还剩 5 秒&今天看啥 热点:
【解决方法】Error Domain=kCLErrorDomain Code=0 &The operation couldn’t be completed.,operationcode
环境:XCODE6.0.1 &#43; iPhone / iOS8
错误:使用CoreLocation获取地理位置信息,报错
Error Domain=kCLErrorDomain Code=0 &The operation couldn’t be completed. (kCLErrorDomain error 0.)&
解决方法:
1.确定模拟器(手机)已经联网并且允许程序获取地理位置
2.重置地理位置服务或者网络服务
PS:如果是模拟器就果断直接重置模拟器吧 &IOS Simulator - Reset Content and Settings..。
你试一下我的代码,这段代码可以工作。NSData *audioData = [NSData dataWithContentsOfURL:someURL];NSString *docDirPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];NSString *filePath = [NSString stringWithFormat:@&%@&#47;%@.mp3&, docDirPath , fileName];[audioData writeToFile:filePath atomically:YES];NSError *NSURL *fileURL = [NSURL fileURLWithPath:filePath];player = [[AVAudioPlayer alloc] initWithContentsOfURL:fileURL error:&error];if (player == nil) {
NSLog(@&AudioPlayer did not load properly: %@&, [error description]);} else {
[player play];}
看日志问题不少啊,建议你检查下安装镜像的md5,很可能是文件损坏了,另外,如果是8.4以后的版本,原版驱动不要直接删除,要保留kext内的info.plist文件
相关搜索:
相关阅读:
相关频道:
Android教程最近更新1573人阅读
&#65279;&#65279;
Caused by: java.lang.RuntimeException: 获取资源ID失败:(packageName=com.kinga.babylearn type=drawable name=umeng_socialize_sina_on
您好:请将/res/drawable中umeng_socialize_sina_on图片考入你的工程对应目录里
&&相关文章推荐
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:125741次
积分:2592
积分:2592
排名:第14137名
原创:141篇
转载:44篇
(5)(5)(1)(2)(2)(5)(1)(1)(23)(2)(16)(2)(7)(21)(21)(12)(3)(1)(6)(9)(3)(2)(7)(19)(9)(1)android|LOFTER(乐乎) - 让兴趣,更有趣
LOFTER for ipad —— 让兴趣,更有趣
让兴趣,更有趣
8084位喜爱 #android 的小伙伴邀你来玩
查看高清大图
喜欢并收藏内容
关注达人获取动态
评论私信与同好交流
10秒注册,查看更多优质内容
网易公司版权所有 & ICP备:浙B2-增值电信业务经营许可证:浙B2-
{if x.type==1}
{if !!x.title}${x.title}{/if}
{if !!x.digest}${x.digest}{/if}
{if x.type==2}
{if x.type==3}
{if x.type==4}
加入 LOFTER 开通功能特权
查看高清大图
喜欢并收藏内容
关注达人获取动态
评论私信与同好交流&img src=&/v2-fcbac35ff787_b.jpg& data-rawwidth=&750& data-rawheight=&340& class=&origin_image zh-lightbox-thumb& width=&750& data-original=&/v2-fcbac35ff787_r.jpg&&&blockquote&作者:Java团长&p&原文链接:&a href=&/?target=https%3A//mp./s/5OK2aNJ1fdnrvul8PT_ZiQ& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&40个Java多线程问题总结&i class=&icon-external&&&/i&&/a&&br&&/p&&/blockquote&&h2&前言&/h2&&p&这篇文章主要是对多线程的问题进行总结的,因此罗列了40个多线程的问题。&/p&&p&这些多线程的问题,有些来源于各大网站、有些来源于自己的思考。可能有些问题网上有、可能有些问题对应的答案也有、也可能有些各位网友也都看过,但是本文写作的重心就是所有的问题都会按照自己的理解回答一遍,不会去看网上的答案,因此可能有些问题讲的不对,能指正的希望大家不吝指教。&/p&&h1&40个问题汇总&/h1&&h2&1、多线程有什么用?&/h2&&p&一个可能在很多人看来很扯淡的一个问题:我会用多线程就好了,还管它有什么用?在我看来,这个回答更扯淡。所谓”知其然知其所以然”,”会用”只是”知其然”,”为什么用”才是”知其所以然”,只有达到”知其然知其所以然”的程度才可以说是把一个知识点运用自如。OK,下面说说我对这个问题的看法:&/p&&p&(1)发挥多核CPU的优势&/p&&p&随着工业的进步,现在的笔记本、台式机乃至商用的应用服务器至少也都是双核的,4核、8核甚至16核的也都不少见,如果是单线程的程序,那么在双核CPU上就浪费了50%,在4核CPU上就浪费了75%。单核CPU上所谓的”多线程”那是假的多线程,同一时间处理器只会处理一段逻辑,只不过线程之间切换得比较快,看着像多个线程”同时”运行罢了。多核CPU上的多线程才是真正的多线程,它能让你的多段逻辑同时工作,多线程,可以真正发挥出多核CPU的优势来,达到充分利用CPU的目的。&/p&&p&(2)防止阻塞&/p&&p&从程序运行效率的角度来看,单核CPU不但不会发挥出多线程的优势,反而会因为在单核CPU上运行多线程导致线程上下文的切换,而降低程序整体的效率。但是单核CPU我们还是要应用多线程,就是为了防止阻塞。试想,如果单核CPU使用单线程,那么只要这个线程阻塞了,比方说远程读取某个数据吧,对端迟迟未返回又没有设置超时时间,那么你的整个程序在数据返回回来之前就停止运行了。多线程可以防止这个问题,多条线程同时运行,哪怕一条线程的代码执行读取数据阻塞,也不会影响其它任务的执行。&/p&&p&(3)便于建模&/p&&p&这是另外一个没有这么明显的优点了。假设有一个大的任务A,单线程编程,那么就要考虑很多,建立整个程序模型比较麻烦。但是如果把这个大的任务A分解成几个小任务,任务B、任务C、任务D,分别建立程序模型,并通过多线程分别运行这几个任务,那就简单很多了。&/p&&h2&2、创建线程的方式&/h2&&p&比较常见的一个问题了,一般就是两种:&/p&&p&(1)继承Thread类&/p&&p&(2)实现Runnable接口&/p&&p&至于哪个好,不用说肯定是后者好,因为实现接口的方式比继承类的方式更灵活,也能减少程序之间的耦合度,面向接口编程也是设计模式6大原则的核心。&/p&&h2&3、start()方法和run()方法的区别&/h2&&p&只有调用了start()方法,才会表现出多线程的特性,不同线程的run()方法里面的代码交替执行。如果只是调用run()方法,那么代码还是同步执行的,必须等待一个线程的run()方法里面的代码全部执行完毕之后,另外一个线程才可以执行其run()方法里面的代码。&/p&&h2&4、Runnable接口和Callable接口的区别&/h2&&p&有点深的问题了,也看出一个Java程序员学习知识的广度。&/p&&p&Runnable接口中的run()方法的返回值是void,它做的事情只是纯粹地去执行run()方法中的代码而已;Callable接口中的call()方法是有返回值的,是一个泛型,和Future、FutureTask配合可以用来获取异步执行的结果。&/p&&p&这其实是很有用的一个特性,因为多线程相比单线程更难、更复杂的一个重要原因就是因为多线程充满着未知性,某条线程是否执行了?某条线程执行了多久?某条线程执行的时候我们期望的数据是否已经赋值完毕?无法得知,我们能做的只是等待这条多线程的任务执行完毕而已。而Callable+Future/FutureTask却可以获取多线程运行的结果,可以在等待时间太长没获取到需要的数据的情况下取消该线程的任务,真的是非常有用。&/p&&h2&5、CyclicBarrier和CountDownLatch的区别&/h2&&p&两个看上去有点像的类,都在java.util.concurrent下,都可以用来表示代码运行到某个点上,二者的区别在于:&/p&&p&(1)CyclicBarrier的某个线程运行到某个点上之后,该线程即停止运行,直到所有的线程都到达了这个点,所有线程才重新运行;CountDownLatch则不是,某线程运行到某个点上之后,只是给某个数值-1而已,该线程继续运行&/p&&p&(2)CyclicBarrier只能唤起一个任务,CountDownLatch可以唤起多个任务&/p&&p&(3)CyclicBarrier可重用,CountDownLatch不可重用,计数值为0该CountDownLatch就不可再用了&/p&&h2&6、Volatile关键字的作用&/h2&&p&一个非常重要的问题,是每个学习、应用多线程的Java程序员都必须掌握的。理解volatile关键字的作用的前提是要理解Java内存模型,这里就不讲Java内存模型了,可以参见第31点,volatile关键字的作用主要有两个:&/p&&p&(1)多线程主要围绕可见性和原子性两个特性而展开,使用volatile关键字修饰的变量,保证了其在多线程之间的可见性,即每次读取到volatile变量,一定是最新的数据&/p&&p&(2)代码底层执行不像我们看到的高级语言—-Java程序这么简单,它的执行是Java代码–&字节码–&根据字节码执行对应的C/C++代码–&C/C++代码被编译成汇编语言–&和硬件电路交互,现实中,为了获取更好的性能JVM可能会对指令进行重排序,多线程下可能会出现一些意想不到的问题。使用volatile则会对禁止语义重排序,当然这也一定程度上降低了代码执行效率&/p&&p&从实践角度而言,volatile的一个重要作用就是和CAS结合,保证了原子性,详细的可以参见java.util.concurrent.atomic包下的类,比如AtomicInteger。&/p&&h2&7、什么是线程安全&/h2&&p&又是一个理论的问题,各式各样的答案有很多,我给出一个个人认为解释地最好的:如果你的代码在多线程下执行和在单线程下执行永远都能获得一样的结果,那么你的代码就是线程安全的。&/p&&p&这个问题有值得一提的地方,就是线程安全也是有几个级别的:&/p&&p&(1)不可变&/p&&p&像String、Integer、Long这些,都是final类型的类,任何一个线程都改变不了它们的值,要改变除非新创建一个,因此这些不可变对象不需要任何同步手段就可以直接在多线程环境下使用&/p&&p&(2)绝对线程安全&/p&&p&不管运行时环境如何,调用者都不需要额外的同步措施。要做到这一点通常需要付出许多额外的代价,Java中标注自己是线程安全的类,实际上绝大多数都不是线程安全的,不过绝对线程安全的类,Java中也有,比方说CopyOnWriteArrayList、CopyOnWriteArraySet&/p&&p&(3)相对线程安全&/p&&p&相对线程安全也就是我们通常意义上所说的线程安全,像Vector这种,add、remove方法都是原子操作,不会被打断,但也仅限于此,如果有个线程在遍历某个Vector、有个线程同时在add这个Vector,99%的情况下都会出现ConcurrentModificationException,也就是fail-fast机制。&/p&&p&(4)线程非安全&/p&&p&这个就没什么好说的了,ArrayList、LinkedList、HashMap等都是线程非安全的类&/p&&h2&8、Java中如何获取到线程dump文件&/h2&&p&死循环、死锁、阻塞、页面打开慢等问题,打线程dump是最好的解决问题的途径。所谓线程dump也就是线程堆栈,获取到线程堆栈有两步:&/p&&p&(1)获取到线程的pid,可以通过使用jps命令,在Linux环境下还可以使用ps -ef | grep java&/p&&p&(2)打印线程堆栈,可以通过使用jstack pid命令,在Linux环境下还可以使用kill -3 pid&/p&&p&另外提一点,Thread类提供了一个getStackTrace()方法也可以用于获取线程堆栈。这是一个实例方法,因此此方法是和具体线程实例绑定的,每次获取获取到的是具体某个线程当前运行的堆栈,&/p&&h2&9、一个线程如果出现了运行时异常会怎么样&/h2&&p&如果这个异常没有被捕获的话,这个线程就停止执行了。另外重要的一点是:如果这个线程持有某个某个对象的监视器,那么这个对象监视器会被立即释放&/p&&h2&10、如何在两个线程之间共享数据&/h2&&p&通过在线程之间共享对象就可以了,然后通过wait/notify/notifyAll、await/signal/signalAll进行唤起和等待,比方说阻塞队列BlockingQueue就是为线程之间共享数据而设计的&/p&&h2&11、sleep方法和wait方法有什么区别&/h2&&p&这个问题常问,sleep方法和wait方法都可以用来放弃CPU一定的时间,不同点在于如果线程持有某个对象的监视器,sleep方法不会放弃这个对象的监视器,wait方法会放弃这个对象的监视器&/p&&h2&12、生产者消费者模型的作用是什么&/h2&&p&这个问题很理论,但是很重要:&/p&&p&(1)通过平衡生产者的生产能力和消费者的消费能力来提升整个系统的运行效率,这是生产者消费者模型最重要的作用&/p&&p&(2)解耦,这是生产者消费者模型附带的作用,解耦意味着生产者和消费者之间的联系少,联系越少越可以独自发展而不需要收到相互的制约&/p&&h2&13、ThreadLocal有什么用&/h2&&p&简单说ThreadLocal就是一种以空间换时间的做法,在每个Thread里面维护了一个以开地址法实现的ThreadLocal.ThreadLocalMap,把数据进行隔离,数据不共享,自然就没有线程安全方面的问题了&/p&&h2&14、为什么wait()方法和notify()/notifyAll()方法要在同步块中被调用&/h2&&p&这是JDK强制的,wait()方法和notify()/notifyAll()方法在调用前都必须先获得对象的锁&/p&&h2&15、wait()方法和notify()/notifyAll()方法在放弃对象监视器时有什么区别&/h2&&p&wait()方法和notify()/notifyAll()方法在放弃对象监视器的时候的区别在于:wait()方法立即释放对象监视器,notify()/notifyAll()方法则会等待线程剩余代码执行完毕才会放弃对象监视器。&/p&&h2&16、为什么要使用线程池&/h2&&p&避免频繁地创建和销毁线程,达到线程对象的重用。另外,使用线程池还可以根据项目灵活地控制并发的数目。&/p&&h2&17、怎么检测一个线程是否持有对象监视器&/h2&&p&我也是在网上看到一道多线程面试题才知道有方法可以判断某个线程是否持有对象监视器:Thread类提供了一个holdsLock(Object obj)方法,当且仅当对象obj的监视器被某条线程持有的时候才会返回true,注意这是一个static方法,这意味着“某条线程”指的是当前线程。&/p&&h2&18、synchronized和ReentrantLock的区别&/h2&&p&synchronized是和if、else、for、while一样的关键字,ReentrantLock是类,这是二者的本质区别。既然ReentrantLock是类,那么它就提供了比synchronized更多更灵活的特性,可以被继承、可以有方法、可以有各种各样的类变量,ReentrantLock比synchronized的扩展性体现在几点上:&/p&&p&(1)ReentrantLock可以对获取锁的等待时间进行设置,这样就避免了死锁&/p&&p&(2)ReentrantLock可以获取各种锁的信息&/p&&p&(3)ReentrantLock可以灵活地实现多路通知&/p&&p&另外,二者的锁机制其实也是不一样的。ReentrantLock底层调用的是Unsafe的park方法加锁,synchronized操作的应该是对象头中mark word,这点我不能确定。&/p&&h2&19、ConcurrentHashMap的并发度是什么&/h2&&p&ConcurrentHashMap的并发度就是segment的大小,默认为16,这意味着最多同时可以有16条线程操作ConcurrentHashMap,这也是ConcurrentHashMap对Hashtable的最大优势,任何情况下,Hashtable能同时有两条线程获取Hashtable中的数据吗?&/p&&h2&20、ReadWriteLock是什么&/h2&&p&首先明确一下,不是说ReentrantLock不好,只是ReentrantLock某些时候有局限。如果使用ReentrantLock,可能本身是为了防止线程A在写数据、线程B在读数据造成的数据不一致,但这样,如果线程C在读数据、线程D也在读数据,读数据是不会改变数据的,没有必要加锁,但是还是加锁了,降低了程序的性能。&/p&&p&因为这个,才诞生了读写锁ReadWriteLock。ReadWriteLock是一个读写锁接口,ReentrantReadWriteLock是ReadWriteLock接口的一个具体实现,实现了读写的分离,读锁是共享的,写锁是独占的,读和读之间不会互斥,读和写、写和读、写和写之间才会互斥,提升了读写的性能。&/p&&h2&21、FutureTask是什么&/h2&&p&这个其实前面有提到过,FutureTask表示一个异步运算的任务。FutureTask里面可以传入一个Callable的具体实现类,可以对这个异步运算的任务的结果进行等待获取、判断是否已经完成、取消任务等操作。当然,由于FutureTask也是Runnable接口的实现类,所以FutureTask也可以放入线程池中。&/p&&h2&22、Linux环境下如何查找哪个线程使用CPU最长&/h2&&p&这是一个比较偏实践的问题,这种问题我觉得挺有意义的。可以这么做:&/p&&p&(1)获取项目的pid,jps或者ps -ef | grep java,这个前面有讲过&/p&&p&(2)top -H -p pid,顺序不能改变&/p&&p&这样就可以打印出当前的项目,每条线程占用CPU时间的百分比。注意这里打出的是LWP,也就是操作系统原生线程的线程号,我笔记本山没有部署Linux环境下的Java工程,因此没有办法截图演示,网友朋友们如果公司是使用Linux环境部署项目的话,可以尝试一下。&/p&&p&使用”top -H -p pid”+”jps pid”可以很容易地找到某条占用CPU高的线程的线程堆栈,从而定位占用CPU高的原因,一般是因为不当的代码操作导致了死循环。&/p&&p&最后提一点,”top -H -p pid”打出来的LWP是十进制的,”jps pid”打出来的本地线程号是十六进制的,转换一下,就能定位到占用CPU高的线程的当前线程堆栈了。&/p&&h2&23、Java编程写一个会导致死锁的程序&/h2&&p&第一次看到这个题目,觉得这是一个非常好的问题。很多人都知道死锁是怎么一回事儿:线程A和线程B相互等待对方持有的锁导致程序无限死循环下去。当然也仅限于此了,问一下怎么写一个死锁的程序就不知道了,这种情况说白了就是不懂什么是死锁,懂一个理论就完事儿了,实践中碰到死锁的问题基本上是看不出来的。&/p&&p&真正理解什么是死锁,这个问题其实不难,几个步骤:&/p&&p&(1)两个线程里面分别持有两个Object对象:lock1和lock2。这两个lock作为同步代码块的锁;&/p&&p&(2)线程1的run()方法中同步代码块先获取lock1的对象锁,Thread.sleep(xxx),时间不需要太多,50毫秒差不多了,然后接着获取lock2的对象锁。这么做主要是为了防止线程1启动一下子就连续获得了lock1和lock2两个对象的对象锁&/p&&p&(3)线程2的run)(方法中同步代码块先获取lock2的对象锁,接着获取lock1的对象锁,当然这时lock1的对象锁已经被线程1锁持有,线程2肯定是要等待线程1释放lock1的对象锁的&/p&&p&这样,线程1″睡觉”睡完,线程2已经获取了lock2的对象锁了,线程1此时尝试获取lock2的对象锁,便被阻塞,此时一个死锁就形成了。代码就不写了,占的篇幅有点多,Java多线程7:死锁这篇文章里面有,就是上面步骤的代码实现。&/p&&h2&24、怎么唤醒一个阻塞的线程&/h2&&p&如果线程是因为调用了wait()、sleep()或者join()方法而导致的阻塞,可以中断线程,并且通过抛出InterruptedException来唤醒它;如果线程遇到了IO阻塞,无能为力,因为IO是操作系统实现的,Java代码并没有办法直接接触到操作系统。&/p&&h2&25、不可变对象对多线程有什么帮助&/h2&&p&前面有提到过的一个问题,不可变对象保证了对象的内存可见性,对不可变对象的读取不需要进行额外的同步手段,提升了代码执行效率。&/p&&h2&26、什么是多线程的上下文切换&/h2&&p&多线程的上下文切换是指CPU控制权由一个已经正在运行的线程切换到另外一个就绪并等待获取CPU执行权的线程的过程。&/p&&h2&27、如果你提交任务时,线程池队列已满,这时会发生什么&/h2&&p&如果你使用的LinkedBlockingQueue,也就是无界队列的话,没关系,继续添加任务到阻塞队列中等待执行,因为LinkedBlockingQueue可以近乎认为是一个无穷大的队列,可以无限存放任务;如果你使用的是有界队列比方说ArrayBlockingQueue的话,任务首先会被添加到ArrayBlockingQueue中,ArrayBlockingQueue满了,则会使用拒绝策略RejectedExecutionHandler处理满了的任务,默认是AbortPolicy。&/p&&h2&28、Java中用到的线程调度算法是什么&/h2&&p&抢占式。一个线程用完CPU之后,操作系统会根据线程优先级、线程饥饿情况等数据算出一个总的优先级并分配下一个时间片给某个线程执行。&/p&&h2&29、Thread.sleep(0)的作用是什么&/h2&&p&这个问题和上面那个问题是相关的,我就连在一起了。由于Java采用抢占式的线程调度算法,因此可能会出现某条线程常常获取到CPU控制权的情况,为了让某些优先级比较低的线程也能获取到CPU控制权,可以使用Thread.sleep(0)手动触发一次操作系统分配时间片的操作,这也是平衡CPU控制权的一种操作。&/p&&h2&30、什么是自旋&/h2&&p&很多synchronized里面的代码只是一些很简单的代码,执行时间非常快,此时等待的线程都加锁可能是一种不太值得的操作,因为线程阻塞涉及到用户态和内核态切换的问题。既然synchronized里面的代码执行地非常快,不妨让等待锁的线程不要被阻塞,而是在synchronized的边界做忙循环,这就是自旋。如果做了多次忙循环发现还没有获得锁,再阻塞,这样可能是一种更好的策略。&/p&&h2&31、什么是Java内存模型&/h2&&p&Java内存模型定义了一种多线程访问Java内存的规范。Java内存模型要完整讲不是这里几句话能说清楚的,我简单总结一下Java内存模型的几部分内容:&/p&&p&(1)Java内存模型将内存分为了主内存和工作内存。类的状态,也就是类之间共享的变量,是存储在主内存中的,每次Java线程用到这些主内存中的变量的时候,会读一次主内存中的变量,并让这些内存在自己的工作内存中有一份拷贝,运行自己线程代码的时候,用到这些变量,操作的都是自己工作内存中的那一份。在线程代码执行完毕之后,会将最新的值更新到主内存中去&/p&&p&(2)定义了几个原子操作,用于操作主内存和工作内存中的变量&/p&&p&(3)定义了volatile变量的使用规则&/p&&p&(4)happens-before,即先行发生原则,定义了操作A必然先行发生于操作B的一些规则,比如在同一个线程内控制流前面的代码一定先行发生于控制流后面的代码、一个释放锁unlock的动作一定先行发生于后面对于同一个锁进行锁定lock的动作等等,只要符合这些规则,则不需要额外做同步措施,如果某段代码不符合所有的happens-before规则,则这段代码一定是线程非安全的&/p&&h2&32、什么是CAS&/h2&&p&CAS,全称为Compare and Set,即比较-设置。假设有三个操作数:内存值V、旧的预期值A、要修改的值B,当且仅当预期值A和内存值V相同时,才会将内存值修改为B并返回true,否则什么都不做并返回false。当然CAS一定要volatile变量配合,这样才能保证每次拿到的变量是主内存中最新的那个值,否则旧的预期值A对某条线程来说,永远是一个不会变的值A,只要某次CAS操作失败,永远都不可能成功。&/p&&h2&33、什么是乐观锁和悲观锁&/h2&&p&(1)乐观锁:就像它的名字一样,对于并发间操作产生的线程安全问题持乐观状态,乐观锁认为竞争不总是会发生,因此它不需要持有锁,将比较-设置这两个动作作为一个原子操作尝试去修改内存中的变量,如果失败则表示发生冲突,那么就应该有相应的重试逻辑。&/p&&p&(2)悲观锁:还是像它的名字一样,对于并发间操作产生的线程安全问题持悲观状态,悲观锁认为竞争总是会发生,因此每次对某资源进行操作时,都会持有一个独占的锁,就像synchronized,不管三七二十一,直接上了锁就操作资源了。&/p&&h2&34、什么是AQS&/h2&&p&简单说一下AQS,AQS全称为AbstractQueuedSychronizer,翻译过来应该是抽象队列同步器。&/p&&p&如果说java.util.concurrent的基础是CAS的话,那么AQS就是整个Java并发包的核心了,ReentrantLock、CountDownLatch、Semaphore等等都用到了它。AQS实际上以双向队列的形式连接所有的Entry,比方说ReentrantLock,所有等待的线程都被放在一个Entry中并连成双向队列,前面一个线程使用ReentrantLock好了,则双向队列实际上的第一个Entry开始运行。&/p&&p&AQS定义了对双向队列所有的操作,而只开放了tryLock和tryRelease方法给开发者使用,开发者可以根据自己的实现重写tryLock和tryRelease方法,以实现自己的并发功能。&/p&&h2&35、单例模式的线程安全性&/h2&&p&老生常谈的问题了,首先要说的是单例模式的线程安全意味着:某个类的实例在多线程环境下只会被创建一次出来。单例模式有很多种的写法,我总结一下:&/p&&p&(1)饿汉式单例模式的写法:线程安全&/p&&p&(2)懒汉式单例模式的写法:非线程安全&/p&&p&(3)双检锁单例模式的写法:线程安全&/p&&h2&36、Semaphore有什么作用&/h2&&p&Semaphore就是一个信号量,它的作用是限制某段代码块的并发数。Semaphore有一个构造函数,可以传入一个int型整数n,表示某段代码最多只有n个线程可以访问,如果超出了n,那么请等待,等到某个线程执行完毕这段代码块,下一个线程再进入。由此可以看出如果Semaphore构造函数中传入的int型整数n=1,相当于变成了一个synchronized了。&/p&&h2&37、Hashtable的size()方法中明明只有一条语句”return count”,为什么还要做同步?&/h2&&p&这是我之前的一个困惑,不知道大家有没有想过这个问题。某个方法中如果有多条语句,并且都在操作同一个类变量,那么在多线程环境下不加锁,势必会引发线程安全问题,这很好理解,但是size()方法明明只有一条语句,为什么还要加锁?&/p&&p&关于这个问题,在慢慢地工作、学习中,有了理解,主要原因有两点:&/p&&p&(1)同一时间只能有一条线程执行固定类的同步方法,但是对于类的非同步方法,可以多条线程同时访问。所以,这样就有问题了,可能线程A在执行Hashtable的put方法添加数据,线程B则可以正常调用size()方法读取Hashtable中当前元素的个数,那读取到的值可能不是最新的,可能线程A添加了完了数据,但是没有对size++,线程B就已经读取size了,那么对于线程B来说读取到的size一定是不准确的。而给size()方法加了同步之后,意味着线程B调用size()方法只有在线程A调用put方法完毕之后才可以调用,这样就保证了线程安全性&/p&&p&(2)CPU执行代码,执行的不是Java代码,这点很关键,一定得记住。Java代码最终是被翻译成汇编代码执行的,汇编代码才是真正可以和硬件电路交互的代码。即使你看到Java代码只有一行,甚至你看到Java代码编译之后生成的字节码也只有一行,也不意味着对于底层来说这句语句的操作只有一个。一句”return count”假设被翻译成了三句汇编语句执行,完全可能执行完第一句,线程就切换了。&/p&&h2&38、线程类的构造方法、静态块是被哪个线程调用的&/h2&&p&这是一个非常刁钻和狡猾的问题。请记住:线程类的构造方法、静态块是被new这个线程类所在的线程所调用的,而run方法里面的代码才是被线程自身所调用的。&/p&&p&如果说上面的说法让你感到困惑,那么我举个例子,假设Thread2中new了Thread1,main函数中new了Thread2,那么:&/p&&p&(1)Thread2的构造方法、静态块是main线程调用的,Thread2的run()方法是Thread2自己调用的&/p&&p&(2)Thread1的构造方法、静态块是Thread2调用的,Thread1的run()方法是Thread1自己调用的&/p&&h2&39、同步方法和同步块,哪个是更好的选择&/h2&&p&同步块,这意味着同步块之外的代码是异步执行的,这比同步整个方法更提升代码的效率。请知道一条原则:同步的范围越少越好。&/p&&p&借着这一条,我额外提一点,虽说同步的范围越少越好,但是在Java虚拟机中还是存在着一种叫做锁粗化的优化方法,这种方法就是把同步范围变大。这是有用的,比方说StringBuffer,它是一个线程安全的类,自然最常用的append()方法是一个同步方法,我们写代码的时候会反复append字符串,这意味着要进行反复的加锁-&解锁,这对性能不利,因为这意味着Java虚拟机在这条线程上要反复地在内核态和用户态之间进行切换,因此Java虚拟机会将多次append方法调用的代码进行一个锁粗化的操作,将多次的append的操作扩展到append方法的头尾,变成一个大的同步块,这样就减少了加锁–&解锁的次数,有效地提升了代码执行的效率。&/p&&h2&40、高并发、任务执行时间短的业务怎样使用线程池?并发不高、任务执行时间长的业务怎样使用线程池?并发高、业务执行时间长的业务怎样使用线程池?&/h2&&p&这是我在并发编程网上看到的一个问题,把这个问题放在最后一个,希望每个人都能看到并且思考一下,因为这个问题非常好、非常实际、非常专业。关于这个问题,个人看法是:&/p&&p&(1)高并发、任务执行时间短的业务,线程池线程数可以设置为CPU核数+1,减少线程上下文的切换&/p&&p&(2)并发不高、任务执行时间长的业务要区分开看:&/p&&p&a)假如是业务时间长集中在IO操作上,也就是IO密集型的任务,因为IO操作并不占用CPU,所以不要让所有的CPU闲下来,可以加大线程池中的线程数目,让CPU处理更多的业务&/p&&p&b)假如是业务时间长集中在计算操作上,也就是计算密集型任务,这个就没办法了,和(1)一样吧,线程池中的线程数设置得少一些,减少线程上下文的切换&/p&&p&(3)并发高、业务执行时间长,解决这种类型任务的关键不在于线程池而在于整体架构的设计,看看这些业务里面某些数据是否能做缓存是第一步,增加服务器是第二步,至于线程池的设置,设置参考(2)。最后,业务执行时间长的问题,也可能需要分析一下,看看能不能使用中间件对任务进行拆分和解耦。&/p&&p&&a href=&/?target=https%3A//mp./s/5OK2aNJ1fdnrvul8PT_ZiQ& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&阅读原文&i class=&icon-external&&&/i&&/a&&/p&&img src=&/v2-9d70dd9d3a01aeb00034_b.jpg& data-rawwidth=&900& data-rawheight=&500& class=&origin_image zh-lightbox-thumb& width=&900& data-original=&/v2-9d70dd9d3a01aeb00034_r.jpg&&
作者:Java团长原文链接: 前言这篇文章主要是对多线程的问题进行总结的,因此罗列了40个多线程的问题。这些多线程的问题,有些来源于各大网站、有些来源于自己的思考。可能有些问题网上有、可能有些问题对应的答案也有、也可能有些…
要完全彻底理解这个问题,需要准备以下4方面的知识:Process/Thread,Android Binder IPC,Handler/Looper/MessageQueue消息机制,Linux pipe/epoll机制。&br&&br&&br&&b&总结一下楼主主要有3个疑惑:&/b&&br&&br&1.Android中为什么主线程不会因为Looper.loop()里的死循环卡死? &br&&br&2.没看见哪里有相关代码为这个死循环准备了一个新线程去运转? &br&&br&3.Activity的生命周期这些方法这些都是在主线程里执行的吧,那这些生命周期方法是怎么实现在死循环体外能够执行起来的?&br&&br&--------------------------------------------------------------------------------------------------------------------------------------&br&针对这些疑惑, &a data-hash=&50b28faaec5cd6734484& href=&///people/50b28faaec5cd6734484& class=&member_mention& data-editable=&true& data-title=&@hi大头鬼hi& data-hovercard=&p$b$50b28faaec5cd6734484&&@hi大头鬼hi&/a&&a data-hash=&bfd113af52& href=&///people/bfd113af52& class=&member_mention& data-editable=&true& data-title=&@Rocko& data-hovercard=&p$b$bfd113af52&&@Rocko&/a&&a data-hash=&5bf3beb935& href=&///people/5bf3beb935& class=&member_mention& data-editable=&true& data-title=&@陈昱全& data-hovercard=&p$b$5bf3beb935&&@陈昱全&/a& 大家回答都比较精炼,接下来我再更进一步详细地一一解答楼主的疑惑:&br&&br&&b&(1) Android中为什么主线程不会因为Looper.loop()里的死循环卡死? &/b&&br&&br&这里涉及线程,先说说说进程/线程,&b&进程:&/b&每个app运行时前首先创建一个进程,该进程是由Zygote fork出来的,用于承载App上运行的各种Activity/Service等组件。进程对于上层应用来说是完全透明的,这也是google有意为之,让App程序都是运行在Android Runtime。大多数情况一个App就运行在一个进程中,除非在AndroidManifest.xml中配置Android:process属性,或通过native代码fork进程。&br&&br&&b&线程:&/b&线程对应用来说非常常见,比如每次new Thread().start都会创建一个新的线程。该线程与App所在进程之间资源共享,从Linux角度来说进程与线程除了是否共享资源外,并没有本质的区别,都是一个task_struct结构体&b&,在CPU看来进程或线程无非就是一段可执行的代码,CPU采用CFS调度算法,保证每个task都尽可能公平的享有CPU时间片&/b&。&br&&br&有了这么准备,再说说死循环问题:&br&&br&对于线程既然是一段可执行的代码,当可执行代码执行完成后,线程生命周期便该终止了,线程退出。而对于主线程,我们是绝不希望会被运行一段时间,自己就退出,那么如何保证能一直存活呢?&b&简单做法就是可执行代码是能一直执行下去的,死循环便能保证不会被退出,&/b&例如,binder线程也是采用死循环的方法,通过循环方式不同与Binder驱动进行读写操作,当然并非简单地死循环,无消息时会休眠。但这里可能又引发了另一个问题,既然是死循环又如何去处理其他事务呢?通过创建新线程的方式。&br&&br&真正会卡死主线程的操作是在回调方法onCreate/onStart/onResume等操作时间过长,会导致掉帧,甚至发生ANR,looper.loop本身不会导致应用卡死。&br&&br&&br&&b&(2) 没看见哪里有相关代码为这个死循环准备了一个新线程去运转? &/b&&br&&br&事实上,会在进入死循环之前便创建了新binder线程,在代码ActivityThread.main()中:&br&&div class=&highlight&&&pre&&code class=&language-java&&&span class=&kd&&public&/span& &span class=&kd&&static&/span& &span class=&kt&&void&/span& &span class=&nf&&main&/span&&span class=&o&&(&/span&&span class=&n&&String&/span&&span class=&o&&[]&/span& &span class=&n&&args&/span&&span class=&o&&)&/span& &span class=&o&&{&/span&
&span class=&o&&....&/span&
&span class=&c1&&//创建Looper和MessageQueue对象,用于处理主线程的消息&/span&
&span class=&n&&Looper&/span&&span class=&o&&.&/span&&span class=&na&&prepareMainLooper&/span&&span class=&o&&();&/span&
&span class=&c1&&//创建ActivityThread对象&/span&
&span class=&n&&ActivityThread&/span& &span class=&n&&thread&/span& &span class=&o&&=&/span& &span class=&k&&new&/span& &span class=&n&&ActivityThread&/span&&span class=&o&&();&/span&
&span class=&c1&&//建立Binder通道 (创建新线程)&/span&
&span class=&n&&thread&/span&&span class=&o&&.&/span&&span class=&na&&attach&/span&&span class=&o&&(&/span&&span class=&kc&&false&/span&&span class=&o&&);&/span&
&span class=&n&&Looper&/span&&span class=&o&&.&/span&&span class=&na&&loop&/span&&span class=&o&&();&/span& &span class=&c1&&//消息循环运行&/span&
&span class=&k&&throw&/span& &span class=&k&&new&/span& &span class=&n&&RuntimeException&/span&&span class=&o&&(&/span&&span class=&s&&&Main thread loop unexpectedly exited&&/span&&span class=&o&&);&/span&
&span class=&o&&}&/span&
&/code&&/pre&&/div&&b&thread.attach(false);便会创建一个Binder线程(具体是指ApplicationThread,Binder的服务端,用于接收系统服务AMS发送来的事件),该Binder线程通过Handler将Message发送给主线程&/b&,具体过程可查看 &a href=&///?target=http%3A////start-service/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&startService流程分析&i class=&icon-external&&&/i&&/a&,这里不展开说,简单说Binder用于进程间通信,采用C/S架构。关于binder感兴趣的朋友,可查看我回答的另一个知乎问题:&br&&a href=&/question//answer/& class=&internal&&为什么Android要采用Binder作为IPC机制? - Gityuan的回答&/a&&br&&br&另外,&b&ActivityThread实际上并非线程&/b&,不像HandlerThread类,ActivityThread并没有真正继承Thread类,只是往往运行在主线程,该人以线程的感觉,其实承载ActivityThread的主线程就是由Zygote fork而创建的进程。&br&&br&&b&主线程的死循环一直运行是不是特别消耗CPU资源呢? &/b&其实不然,这里就涉及到&b&Linux pipe/e&/b&&b&poll机制&/b&,简单说就是在主线程的MessageQueue没有消息时,便阻塞在loop的queue.next()中的nativePollOnce()方法里,详情见&a href=&///?target=http%3A////handler-message-framework/%23next& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Android消息机制1-Handler(Java层)&i class=&icon-external&&&/i&&/a&,此时主线程会释放CPU资源进入休眠状态,直到下个消息到达或者有事务发生,通过往pipe管道写端写入数据来唤醒主线程工作。这里采用的epoll机制,是一种IO多路复用机制,可以同时监控多个描述符,当某个描述符就绪(读或写就绪),则立刻通知相应程序进行读或写操作,本质同步I/O,即读写是阻塞的。 &b&所以说,主线程大多数时候都是处于休眠状态,并不会消耗大量CPU资源。&/b&&br&&br&&br&&b&(3) Activity的生命周期是怎么实现在死循环体外能够执行起来的?&/b&&br&&br&ActivityThread的内部类H继承于Handler,通过handler消息机制,简单说Handler机制用于同一个进程的线程间通信。&br&&br&&b&Activity的生命周期都是依靠主线程的Looper.loop,当收到不同Message时则采用相应措施:&/b&&br&在H.handleMessage(msg)方法中,根据接收到不同的msg,执行相应的生命周期。&br&&br&
比如收到msg=H.LAUNCH_ACTIVITY,则调用ActivityThread.handleLaunchActivity()方法,最终会通过反射机制,创建Activity实例,然后再执行Activity.onCreate()等方法;&br&
再比如收到msg=H.PAUSE_ACTIVITY,则调用ActivityThread.handlePauseActivity()方法,最终会执行Activity.onPause()等方法。 上述过程,我只挑核心逻辑讲,真正该过程远比这复杂。&br&&br&&b&主线程的消息又是哪来的呢?&/b&当然是App进程中的其他线程通过Handler发送给主线程,请看接下来的内容:&br&&br&&br&--------------------------------------------------------------------------------------------------------------------------------------&br&&b&最后,从进程与线程间通信的角度,&/b&&b&通过一张图&/b&&b&加深大家对App运行过程的理解:&/b&&br&&img src=&/7fbac86a2b0b886de2b872_b.jpg& data-rawwidth=&890& data-rawheight=&535& class=&origin_image zh-lightbox-thumb& width=&890& data-original=&/7fbac86a2b0b886de2b872_r.jpg&&&br&&b&system_server进程是系统进程&/b&,java framework框架的核心载体,里面运行了大量的系统服务,比如这里提供ApplicationThreadProxy(简称ATP),ActivityManagerService(简称AMS),这个两个服务都运行在system_server进程的不同线程中,由于ATP和AMS都是基于IBinder接口,都是binder线程,binder线程的创建与销毁都是由binder驱动来决定的。&br&&br&&b&App进程则是我们常说的应用程序&/b&,主线程主要负责Activity/Service等组件的生命周期以及UI相关操作都运行在这个线程; 另外,每个App进程中至少会有两个binder线程 ApplicationThread(简称AT)和ActivityManagerProxy(简称AMP),除了图中画的线程,其中还有很多线程,比如signal catcher线程等,这里就不一一列举。&br&&br&Binder用于不同进程之间通信,由一个进程的Binder客户端向另一个进程的服务端发送事务,比如图中线程2向线程4发送事务;而handler用于同一个进程中不同线程的通信,比如图中线程4向主线程发送消息。&br&&br&&b&结合图说说Activity生命周期,比如暂停Activity,流程如下:&/b&&br&&ol&&li&线程1的AMS中调用线程2的ATP;(由于同一个进程的线程间资源共享,可以相互直接调用,但需要注意多线程并发问题)&br&&/li&&li&线程2通过binder传输到App进程的线程4;&br&&/li&&li&线程4通过handler消息机制,将暂停Activity的消息发送给主线程;&br&&/li&&li&主线程在looper.loop()中循环遍历消息,当收到暂停Activity的消息时,便将消息分发给ActivityThread.H.handleMessage()方法,再经过方法的调用,最后便会调用到Activity.onPause(),当onPause()处理完后,继续循环loop下去。&br&&/li&&/ol&&br&&br&--------------------------------- &b&欢迎关注我的微博:&a href=&///?target=http%3A///gityuan& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Gityuan&i class=&icon-external&&&/i&&/a&&/b&-----------------------------------------&br&&br&&b&如果大家觉得我回答得还行,还请大家随手 点赞、关注、收藏,如果觉得说得不好的,还往评论指正。&/b&
要完全彻底理解这个问题,需要准备以下4方面的知识:Process/Thread,Android Binder IPC,Handler/Looper/MessageQueue消息机制,Linux pipe/epoll机制。 总结一下楼主主要有3个疑惑: 1.Android中为什么主线程不会因为Looper.loop()里的死循环卡死? 2.没…
&p& 本文来自于&strong&腾讯Bugly&/strong&公众号(&strong&weixinBugly&/strong&),未经作者同意,请勿转载,原文地址:&a href=&/?target=https%3A//mp./s/2MsEAR9pQfMr1Sfs7cPdWQ& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Android 内存优化总结&实践&i class=&icon-external&&&/i&&/a&&/p&&h2&导语&/h2&&p& 智能手机发展到今天已经有十几个年头,手机的软硬件都已经发生了翻天覆地的变化,特别是Android阵营,从一开始的一两百M到今天动辄4G,6G内存。然而大部分的开发者观看下自己的异常上报系统,还是会发现各种内存问题仍然层出不穷,各种OOM为crash率贡献不少。Android开发发展到今天也是已经比较成熟,各种新框架,新技术也是层出不穷,而内存优化一直都是Android开发过程一个不可避免的话题。 恰好最近做了内存优化相关的工作,这里也对Android内存优化相关的知识做下总结。&/p&&p& 在开始文章之前推荐下公司同事翻译整理版本&a href=&/?target=https%3A//mp./s/uySEk1cwxRENneFsoReFyw& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&《Android性能优化典范 - 第6季》&i class=&icon-external&&&/i&&/a&,因为篇幅有限这里我对一些内容只做简单总结,同时如果有不正确内容也麻烦帮忙指正。&/p&&p& 本文将会对Android内存优化相关的知识进行总结以及最后案例分析(一二部分是理论知识总结,你也可以直接跳到第三部分看案例):&/p&&p& 一、 Android内存分配回收机制&br& 二 、Android常见内存问题和对应检测,解决方式。&br& 三、 JOOX内存优化案例&br& 四 、总结&/p&&p& 工欲善其事必先利其器,想要优化App的内存占用,那么还是需要先了解Android系统的内存分配和回收机制。&/p&&h2&一 ,Android内存分配回收机制&/h2&&p& 参考Android 操作系统的内存回收机制[1],这里简单做下总结:&/p&&p& 从宏观角度上来看Android系统可以分为三个层次&/p&&ol&&li&Application Framework, &/li&&li&Dalvik 虚拟机&/li&&li&Linux内核。&/li&&/ol&&p& 这三个层次都有各自内存相关工作:&/p&&h4&1. Application Framework&/h4&&p& Anroid基于进程中运行的组件及其状态规定了默认的五个回收优先级:&/p&&img src=&/v2-a243da0ffd25e4cefe9ee0e_b.png& data-rawwidth=&761& data-rawheight=&395& class=&origin_image zh-lightbox-thumb& width=&761& data-original=&/v2-a243da0ffd25e4cefe9ee0e_r.png&&&ul&&li&Empty process(空进程)&/li&&li&Background process(后台进程)&/li&&li&Service process(服务进程)&/li&&li&Visible process(可见进程) &/li&&li&Foreground process(前台进程)&/li&&/ul&&p& 系统需要进行内存回收时最先回收空进程,然后是后台进程,以此类推最后才会回收前台进程(一般情况下前台进程就是与用户交互的进程了,如果连前台进程都需要回收那么此时系统几乎不可用了)。&/p&&p& 由此也衍生了很多进程保活的方法(提高优先级,互相唤醒,native保活等等),出现了国内各种全家桶,甚至各种杀不死的进程。&/p&&p& Android中由ActivityManagerService 集中管理所有进程的内存资源分配。&/p&&h4&2. Linux内核&/h4&&img src=&/v2-51ef90d669fbe55edfbede_b.png& data-rawwidth=&1108& data-rawheight=&422& class=&origin_image zh-lightbox-thumb& width=&1108& data-original=&/v2-51ef90d669fbe55edfbede_r.png&&&p& 参考QCon大会上阿里巴巴的Android内存优化分享[2],这里最简单的理解就是ActivityManagerService会对所有进程进行评分(存放在变量adj中),然后再讲这个评分更新到内核,由内核去完成真正的内存回收(lowmemorykiller, Oom_killer)。这里只是大概的流程,中间过程还是很复杂的,有兴趣的同学可以一起研究,代码在系统源码ActivityManagerService.java中。&/p&&h4&3. Dalvik虚拟机&/h4&&p& Android进程的内存管理分析[3],对Android中进程内存的管理做了分析。&/p&&p& Android中有Native Heap和Dalvik Heap。Android的Native Heap言理论上可分配的空间取决了硬件RAM,而对于每个进程的Dalvik Heap都是有大小限制的,具体策略可以看看android dalvik heap 浅析[4]。&/p&&p&&strong&Android App为什么会OOM呢?&/strong&其实就是申请的内存超过了Dalvik Heap的最大值。这里也诞生了一些比较”黑科技”的内存优化方案,比如将耗内存的操作放到Native层,或者使用分进程的方式突破每个进程的Dalvik Heap内存限制。&/p&&p& Android Dalvik Heap与原生Java一样,将堆的内存空间分为三个区域,Young Generation,Old Generation, Permanent Generation。&/p&&img src=&/v2-4dfbde8fc6bb2_b.png& data-rawwidth=&428& data-rawheight=&322& class=&origin_image zh-lightbox-thumb& width=&428& data-original=&/v2-4dfbde8fc6bb2_r.png&&&p& 最近分配的对象会存放在Young Generation区域,当这个对象在这个区域停留的时间达到一定程度,它会被移动到Old Generation,最后累积一定时间再移动到Permanent Generation区域。系统会根据内存中不同的内存数据类型分别执行不同的gc操作。&/p&&p& GC发生的时候,所有的线程都是会被暂停的。执行GC所占用的时间和它发生在哪一个Generation也有关系,Young Generation中的每次GC操作时间是最短的,Old Generation其次,Permanent Generation最长。&/p&&p& GC时会导致线程暂停,导致卡顿,Google在新版本的Android中优化了这个问题, 在ART中对GC过程做了优化揭秘 ART 细节 —— Garbage collection[5],据说内存分配的效率提高了10倍,GC的效率提高了2-3倍(可见原来效率有多低),不过主要还是优化中断和阻塞的时间,频繁的GC还是会导致卡顿。&/p&&p& 上面就是Android系统内存分配和回收相关知识,回过头来看,现在各种手机厂商鼓吹人工智能手机,号称18个月不卡顿,越用越快,其实很大一部分Android系统的内存优化有关,无非就是利用一些比较成熟的基于统计,机器学习的算法定时清理数据,清理内存,甚至提前加载数据到内存。&/p&&h2&二 ,Android常见内存问题和对应检测,解决方式&/h2&&h4&1. 内存泄露&/h4&&p& 不止Android程序员,内存泄露应该是大部分程序员都遇到过的问题,可以说大部分的内存问题都是内存泄露导致的,Android里也有一些很常见的内存泄露问题[6],这里简单罗列下:&/p&&ul&&li&&strong&单例&/strong&(主要原因还是因为一般情况下单例都是全局的,有时候会引用一些实际生命周期比较短的变量,导致其无法释放)&/li&&li&&strong&静态变量&/strong&(同样也是因为生命周期比较长)&/li&&li&&strong&Handler内存泄露&/strong&[7]&/li&&li&&strong&匿名内部类&/strong&(匿名内部类会引用外部类,导致无法释放,比如各种回调)&/li&&li&&strong&资源使用完未关闭&/strong&(BraodcastReceiver,ContentObserver,File,Cursor,Stream,Bitmap)&/li&&/ul&&p& 对Android内存泄露业界已经有很多优秀的组件其中LeakCanary最为知名(Square出品,Square可谓Android开源界中的业界良心,开源的项目包括okhttp, retrofit,otto, picasso, Android开发大神Jake Wharton就在Square),其原理是监控每个activity,在activity ondestory后,在后台线程检测引用,然后过一段时间进行gc,gc后如果引用还在,那么dump出内存堆栈,并解析进行可视化显示。使用LeakCanary可以快速地检测出Android中的内存泄露。&/p&&p& 正常情况下,解决大部分内存泄露问题后,App稳定性应该会有很大提升,但是有时候App本身就是有一些比较耗内存的功能,比如直播,视频播放,音乐播放,那么我们还有什么能做的可以降低内存使用,减少OOM呢?&/p&&h4&2. 图片分辨率相关&/h4&&p&&strong&分辨率适配问题&/strong&。很多情况下图片所占的内存在整个App内存占用中会占大部分。我们知道可以通过将图片放到hdpi/xhdpi/xxhdpi等不同文件夹进行适配,通过xml android:background设置背景图片,或者通过BitmapFactory.decodeResource()方法,图片实际上默认情况下是会进行缩放的。在Java层实际调用的函数都是或者通过BitmapFactory里的decodeResourceStream函数&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span& public static Bitmap decodeResourceStream(Resources res, TypedValue value,
InputStream is, Rect pad, Options opts) {
if (opts == null) {
opts = new Options();
if (opts.inDensity == 0 && value != null) {
final int density = value.
if (density == TypedValue.DENSITY_DEFAULT) {
opts.inDensity = DisplayMetrics.DENSITY_DEFAULT;
} else if (density != TypedValue.DENSITY_NONE) {
opts.inDensity =
if (opts.inTargetDensity == 0 && res != null) {
opts.inTargetDensity = res.getDisplayMetrics().densityD
return decodeStream(is, pad, opts);
&/code&&/pre&&/div&&p& decodeResource在解析时会对Bitmap根据当前设备屏幕像素密度densityDpi的值进行缩放适配操作,使得解析出来的Bitmap与当前设备的分辨率匹配,达到一个最佳的显示效果,并且Bitmap的大小将比原始的大,可以参考下腾讯Bugly的详细分析Android 开发绕不过的坑:&a href=&/?target=https%3A//mp./s/GkPrmlNm8p3fkeh4vo3Htg& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&你的 Bitmap 究竟占多大内存?。&i class=&icon-external&&&/i&&/a&&/p&&p& 关于Density、分辨率、-hdpi等res目录之间的关系:&/p&&img src=&/v2-87e75e61dec0a3096fc84_b.png& data-rawwidth=&775& data-rawheight=&244& class=&origin_image zh-lightbox-thumb& width=&775& data-original=&/v2-87e75e61dec0a3096fc84_r.png&&&blockquote&&p&举个例子,对于一张的图片,如果放在xhdpi,那么xhdpi的设备拿到的大小还是而xxhpi的设备拿到的可能是,这两种情况在内存里的大小分别为:3.68M和8.29M,相差4.61M,在移动设备来说这几M的差距还是很大的。&/p&&/blockquote&&p& 尽管现在已经有比较先进的图片加载组件类似Glide,Facebook Freso, 或者老牌Universal-Image-Loader,但是有时就是需要手动拿到一个bitmap或者drawable,特别是在一些可能会频繁调用的场景(比如ListView的getView),怎样尽可能对bitmap进行复用呢?这里首先需要明确的是对同样的图片,要 尽可能复用,我们可以简单自己用WeakReference做一个bitmap缓存池,也可以用类似图片加载库写一个通用的bitmap缓存池,可以参考GlideBitmapPool[8]的实现。 &/p&&p& 我们也来看看系统是怎么做的,对于类似在xml里面直接通过android:background或者android:src设置的背景图片,以ImageView为例,最终会调用Resource.java里的loadDrawable:&/p&&div class=&highlight&&&pre&&code class=&language-java&&&span&&/span& &span class=&n&&Drawable&/span& &span class=&nf&&loadDrawable&/span&&span class=&o&&(&/span&&span class=&n&&TypedValue&/span& &span class=&n&&value&/span&&span class=&o&&,&/span& &span class=&kt&&int&/span& &span class=&n&&id&/span&&span class=&o&&,&/span& &span class=&n&&Theme&/span& &span class=&n&&theme&/span&&span class=&o&&)&/span& &span class=&kd&&throws&/span& &span class=&n&&NotFoundException&/span& &span class=&o&&{&/span&
&span class=&c1&&// Next, check preloaded drawables. These may contain unresolved theme&/span&
&span class=&c1&&// attributes.&/span&
&span class=&kd&&final&/span& &span class=&n&&ConstantState&/span& &span class=&n&&cs&/span&&span class=&o&&;&/span&
&span class=&k&&if&/span& &span class=&o&&(&/span&&span class=&n&&isColorDrawable&/span&&span class=&o&&)&/span& &span class=&o&&{&/span&
&span class=&n&&cs&/span& &span class=&o&&=&/span& &span class=&n&&sPreloadedColorDrawables&/span&&span class=&o&&.&/span&&span class=&na&&get&/span&&span class=&o&&(&/span&&span class=&n&&key&/span&&span class=&o&&);&/span&
&span class=&o&&}&/span& &span class=&k&&else&/span& &span class=&o&&{&/span&
&span class=&n&&cs&/span& &span class=&o&&=&/span& &span class=&n&&sPreloadedDrawables&/span&&span class=&o&&[&/span&&span class=&n&&mConfiguration&/span&&span class=&o&&.&/span&&span class=&na&&getLayoutDirection&/span&&span class=&o&&()].&/span&&span class=&na&&get&/span&&span class=&o&&(&/span&&span class=&n&&key&/span&&span class=&o&&);&/span&
&span class=&o&&}&/span&
&span class=&n&&Drawable&/span& &span class=&n&&dr&/span&&span class=&o&&;&/span&
&span class=&k&&if&/span& &span class=&o&&(&/span&&span class=&n&&cs&/span& &span class=&o&&!=&/span& &span class=&kc&&null&/span&&span class=&o&&)&/span& &span class=&o&&{&/span&
&span class=&n&&dr&/span& &span class=&o&&=&/span& &span class=&n&&cs&/span&&span class=&o&&.&/span&&span class=&na&&newDrawable&/span&&span class=&o&&(&/span&&span class=&k&&this&/span&&span class=&o&&);&/span&
&span class=&o&&}&/span& &span class=&k&&else&/span& &span class=&k&&if&/span& &span class=&o&&(&/span&&span class=&n&&isColorDrawable&/span&&span class=&o&&)&/span& &span class=&o&&{&/span&
&span class=&n&&dr&/span& &span class=&o&&=&/span& &span class=&k&&new&/span& &span class=&n&&ColorDrawable&/span&&span class=&o&&(&/span&&span class=&n&&value&/span&&span class=&o&&.&/span&&span class=&na&&data&/span&&span class=&o&&);&/span&
&span class=&o&&}&/span& &span class=&k&&else&/span& &span class=&o&&{&/span&
&span class=&n&&dr&/span& &span class=&o&&=&/span& &span class=&n&&loadDrawableForCookie&/span&&span class=&o&&(&/span&&span class=&n&&value&/span&&span class=&o&&,&/span& &span class=&n&&id&/span&&span class=&o&&,&/span& &span class=&kc&&null&/span&&span class=&o&&);&/span&
&span class=&o&&}&/span&
&span class=&o&&...&/span&
&span class=&k&&return&/span& &span class=&n&&dr&/span&&span class=&o&&;&/span&
&span class=&o&&}&/span&
&/code&&/pre&&/div&&p& 可以看到实际上系统也是有一份全局的缓存,sPreloadedDrawables, 对于不同的drawable,如果图片时一样的,那么最终只会有一份bitmap(享元模式),存放于BitmapState中,获取drawable时,系统会从缓存中取出这个bitmap然后构造drawable。而通过BitmapFactory.decodeResource()则每次都会重新解码返回bitmap。所以其实我们可以通过context.getResources().getDrawable再从drawable里获取bitmap,从而复用bitmap,然而这里也有一些坑,比如我们获取到的这份bitmap,假如我们执行了recycle之类的操作,但是假如在其他地方再使用它是那么就会有”Canvas: trying to use a recycled bitmap android.graphics.Bitmap”异常。&/p&&h4&3. 图片压缩&/h4&&p& BitmapFactory 在解码图片时,可以带一个Options,有一些比较有用的功能,比如:&/p&&ul&&li&&p&&strong&inTargetDensity&/strong& 表示要被画出来时的目标像素密度&/p&&/li&&li&&p&&strong&inSampleSize&/strong& 这个值是一个int,当它小于1的时候,将会被当做1处理,如果大于1,那么就会按照比例(1 / inSampleSize)缩小bitmap的宽和高、降低分辨率,大于1时这个值将会被处置为2的倍数。例如,width=100,height=100,inSampleSize=2,那么就会将bitmap处理为,width=50,height=50,宽高降为1 / 2,像素数降为1 / 4&/p&&/li&&li&&p&&strong&inJustDecodeBounds&/strong& 字面意思就可以理解就是只解析图片的边界,有时如果只是为了获取图片的大小就可以用这个,而不必直接加载整张图片。&/p&&/li&&li&&p&&strong&inPreferredConfig&/strong& 默认会使用ARGB_8888,在这个模式下一个像素点将会占用4个byte,而对一些没有透明度要求或者图片质量要求不高的图片,可以使用RGB_565,一个像素只会占用2个byte,一下可以省下50%内存。&/p&&/li&&li&&p&&strong&inPurgeable&/strong&和&strong&inInputShareable&/strong& 这两个需要一起使用,BitmapFactory.java的源码里面有注释,大致意思是表示在系统内存不足时是否可以回收这个bitmap,有点类似软引用,但是实际在5.0以后这两个属性已经被忽略,因为系统认为回收后再解码实际会反而可能导致性能问题&/p&&/li&&li&&p&&strong&inBitmap&/strong& 官方推荐使用的参数,表示重复利用图片内存,减少内存分配,在4.4以前只有相同大小的图片内存区域可以复用,4.4以后只要原有的图片比将要解码的图片大既可以复用了。&/p&&/li&&/ul&&h4&4. 缓存池大小&/h4&&p& 现在很多图片加载组件都不仅仅是使用软引用或者弱引用了,实际上类似Glide 默认使用的事LruCache,因为软引用 弱引用都比较难以控制,使用LruCache可以实现比较精细的控制,而默认缓存池设置太大了会导致浪费内存,设置小了又会导致图片经常被回收,所以需要根据每个App的情况,以及设备的分辨率,内存计算出一个比较合理的初始值,可以参考Glide的做法。&/p&&h4&5. 内存抖动&/h4&&p& 什么是内存抖动呢?Android里内存抖动是指内存频繁地分配和回收,而频繁的gc会导致卡顿,严重时还会导致OOM。&/p&&img src=&/v2-0d8e20fc13a0ae825bd929f1a7783ebe_b.png& data-rawwidth=&669& data-rawheight=&332& class=&origin_image zh-lightbox-thumb& width=&669& data-original=&/v2-0d8e20fc13a0ae825bd929f1a7783ebe_r.png&&&p& 一个很经典的案例是string拼接创建大量小的对象(比如在一些频繁调用的地方打字符串拼接的log的时候), 见Android优化之String篇[9]。&/p&&p&&strong&而内存抖动为什么会引起OOM呢?&/strong&&/p&&p& 主要原因还是有因为大量小的对象频繁创建,导致内存碎片,从而当需要分配内存时,虽然总体上还是有剩余内存可分配,而由于这些内存不连续,导致无法分配,系统直接就返回OOM了。&/p&&blockquote&&p&比如我们坐地铁的时候,假设你没带公交卡去坐地铁,地铁的售票机就只支持5元,10元,而哪怕你这个时候身上有1万张1块的都没用(是不是觉得很反人类..)。当然你可以去兑换5元,10元,而在Android系统里就没那么幸运了,系统会直接拒绝为你分配内存,并扔一个OOM给你(有人说Android系统并不会对Heap中空闲内存区域做碎片整理,待验证)。&/p&&/blockquote&&h4&其他&/h4&&p&&strong&常用数据结构优化&/strong&,ArrayMap及SparseArray是android的系统API,是专门为移动设备而定制的。用于在一定情况下取代HashMap而达到节省内存的目的,具体性能见HashMap,ArrayMap,SparseArray源码分析及性能对比[10],对于key为int的HashMap尽量使用SparceArray替代,大概可以省30%的内存,而对于其他类型,ArrayMap对内存的节省实际并不明显,10%左右,但是数据量在1000以上时,查找速度可能会变慢。&/p&&p&&strong&枚举&/strong&,Android平台上枚举是比较争议的,在较早的Android版本,使用枚举会导致包过大,在个例子里面,使用枚举甚至比直接使用int包的size大了10多倍 在stackoverflow上也有很多的讨论, 大致意思是随着虚拟机的优化,目前枚举变量在Android平台性能问题已经不大,而目前Android官方建议,使用枚举变量还是需要谨慎,因为枚举变量可能比直接用int多使用2倍的内存。&/p&&p&&strong&ListView复用&/strong&,这个大家都知道,getView里尽量复用convertView,同时因为getView会频繁调用,要避免频繁地生成对象&/p&&p&&strong&谨慎使用多进程&/strong&,现在很多App都不是单进程,为了保活,或者提高稳定性都会进行一些进程拆分,而实际上即使是空进程也会占用内存(1M左右),对于使用完的进程,服务都要及时进行回收。&/p&&p&&strong&尽量使用系统资源&/strong&,系统组件,图片甚至控件的id&/p&&p&&strong&减少view的层级&/strong&,对于可以 延迟初始化的页面,使用viewstub&/p&&p&&strong&数据相关&/strong&:序列化数据使用protobuf可以比xml省30%内存,慎用shareprefercnce,因为对于同一个sp,会将整个xml文件载入内存,有时候为了读一个配置,就会将几百k的数据读进内存,数据库字段尽量精简,只读取所需字段。&/p&&p&&strong&dex优化,代码优化,谨慎使用外部库&/strong&, 有人觉得代码多少于内存没有关系,实际会有那么点关系,现在稍微大一点的项目动辄就是百万行代码以上,多dex也是常态,不仅占用rom空间,实际上运行的时候需要加载dex也是会占用内存的(几M),有时候为了使用一些库里的某个功能函数就引入了整个庞大的库,此时可以考虑抽取必要部分,开启proguard优化代码,使用Facebook redex使用优化dex(好像有不少坑)。&/p&&h2&三 案例&/h2&&p& JOOX是IBG一个核心产品,2014年发布以来已经成为5个国家和地区排名第一的音乐App。东南亚是JOOX的主要发行地区,实际上这些地区还是有很多的低端机型,对App的进行内存优化势在必行。&/p&&p& 上面介绍了Android系统内存分配和回收机制,同时也列举了常见的内存问题,但是当我们接到一个内存优化的任务时,我们应该从何开始?下面是一次内存优化的分享。&/p&&h4&1. 首先是解决大部分内存泄露。&/h4&&p& 不管目前App内存占用怎样,理论上不需要的东西最好回收,避免浪费用户内存,减少OOM。实际上自JOOX接入LeakCanary后,每个版本都会做内存泄露检测,经过几个版本的迭代,JOOX已经修复了几十处内存泄露。&/p&&img src=&/v2-e7dd472d9e6cdc2f58cbdbeac380195a_b.png& data-rawwidth=&1446& data-rawheight=&695& class=&origin_image zh-lightbox-thumb& width=&1446& data-original=&/v2-e7dd472d9e6cdc2f58cbdbeac380195a_r.png&&&h4&2. 通过MAT查看内存占用,优化占用内存较大的地方。&/h4&&p& JOOX修复了一系列内存泄露后,内存占用还是居高不下,只能通过MAT查看到底是哪里占用了内存。关于MAT的使用,网上教程无数,简单推荐两篇MAT使用教程[11],MAT - Memory Analyzer Tool 使用进阶[12]。&/p&&p& 点击Android Studio这里可以dump当前的内存快照,因为直接通过Android Sutdio dump出来的hprof文件与标准hprof文件有些差异,我们需要手动进行转换,利用sdk目录/platform-tools/hprof-conv.exe可以直接进行转换,用法:hprof-conv 原文件.hprof 新文件.hprof。只需要输入原文件名还有目标文件名就可以进行转换,转换完就可以直接用MAT打开。&/p&&img src=&/v2-6bd9a7f3c6c35d7d11d122fa_b.png& data-rawwidth=&891& data-rawheight=&197& class=&origin_image zh-lightbox-thumb& width=&891& data-original=&/v2-6bd9a7f3c6c35d7d11d122fa_r.png&&&p& 下面就是JOOX打开App,手动进行多次gc的hprof文件。&/p&&p& 这里我们看的是Dominator Tree(即内存里占用内存最多的对象列表)。&/p&&img src=&/v2-ed75c04adccc00bf1517bf8_b.png& data-rawwidth=&850& data-rawheight=&736& class=&origin_image zh-lightbox-thumb& width=&850& data-original=&/v2-ed75c04adccc00bf1517bf8_r.png&&&ul&&li&&p&&strong&Shallo Heap&/strong&:对象本身占用内存的大小,不包含其引用的对象内存。&/p&&/li&&li&&p&&strong&Retained Heap&/strong&: Retained heap值的计算方式是将retained set中的所有对象大小叠加。或者说,由于X被释放,导致其它所有被释放对象(包括被递归释放的)所占的heap大小。&/p&&p&第一眼看去 居然有3个8M的对象,加起来就是24M啊 这到底是什么鬼?&/p&&img src=&/v2-ecf14d447e98_b.png& data-rawwidth=&840& data-rawheight=&215& class=&origin_image zh-lightbox-thumb& width=&840& data-original=&/v2-ecf14d447e98_r.png&&&p&我们通过List objects-&with incoming references查看(这里with incoming references表示查看谁引用了这个对象,with outgoing references表示这个对象引用了谁)&/p&&img src=&/v2-d41ba1abcb83ff806ee1d3_b.png& data-rawwidth=&1064& data-rawheight=&609& class=&origin_image zh-lightbox-thumb& width=&1064& data-original=&/v2-d41ba1abcb83ff806ee1d3_r.png&&&p&通过这个方式我们看到这三张图分别是闪屏,App主背景,App抽屉背景。&/p&&img src=&/v2-dda9c241ca8bca58c4bc2bc_b.png& data-rawwidth=&1210& data-rawheight=&662& class=&origin_image zh-lightbox-thumb& width=&1210& data-original=&/v2-dda9c241ca8bca58c4bc2bc_r.png&&&p&&strong&这里其实有两个问题:&/strong&&/p&&/li&&li&&p&这几张图原图实际都是,而在1080p手机上实测这几张图都缩放到了&/p&&/li&&li&&p&闪屏页面,其实这张图在闪屏显示过后应该可以回收,但是因为历史原因(和JOOX的退出机制有关),这张图被常驻在后台,导致无谓的内存占用。&/p&&/li&&/ul&&p&&strong&优化方式&/strong&:我们通过将这三张图从xhdpi挪动到xxhdpi(当然这里需要看下图片显示效果有没很大的影响),以及在闪屏显示过后回收闪屏图片。&br& 优化结果:&/p&&img src=&/v2-5f09fcf6abf6e61f3d11e_b.png& data-rawwidth=&849& data-rawheight=&305& class=&origin_image zh-lightbox-thumb& width=&849& data-original=&/v2-5f09fcf6abf6e61f3d11e_r.png&&&p& 从原来的8.29x3=24.87M 到 3.68x2=7.36M 优化了17M(有没一种万马奔腾的感觉。。可能有时费大力气优化很多代码也优化不了几百K,所以很多情况下内存优化时优化图片还是比较立竿见影的)。&/p&&p& 同样方式我们发现对于一些默认图,实际要求的显示要求并不高(图片相对简单,同时大部分情况下图片加载会成功),比如下面这张banner的背景图:&/p&&img src=&/v2-8585db3edc3c9b752e80_b.png& data-rawwidth=&477& data-rawheight=&360& class=&origin_image zh-lightbox-thumb& width=&477& data-original=&/v2-8585db3edc3c9b752e80_r.png&&&p& 优化前1.6M左右,优化后700K左右。&/p&&p& 同时我们也发现了默认图片一个其他问题,因为历史原因,我们使用的图片加载库,设置默认图片的接口是需要一个bitmap,导致我们原来几乎每个adapter都用BitmapFactory decode了一个bitmap,对同一张默认图片,不但没有复用,还保存了多份,不仅会造成内存浪费,而且导致滑动偶尔会卡顿。这里我们也对默认图片使用全局的bitmap缓存池,App全局只要使用同一张bitmap,都复用了同一份。&/p&&p& 另外对于从MAT里看到的图片,有时候因为看不到在项目里面对应的ID,会比较难确认到底是哪一张图,这里stackoverflow上有一种方法,直接用原始数据通过GIM还原这张图片。&/p&&p& 这里其实也看到JOOX比较吃亏一个地方,JOOX不少地方都是使用比较复杂的图片,同时有些地方还需要模糊,动画这些都是比较耗内存的操作,Material Design出来后,很多App都遵循MD设计进行改版,通常默认背景,默认图片一般都是纯色,不仅App看起来比较明亮轻快,实际上也省了很多的内存,对此,JOOX后面对低端机型做了对应的优化。&/p&&h4&3. 我们也对Bugly上的OOM进行了分析,发现其实有些OOM是可以避免的。&/h4&&p& 下面这个crash就是上面提到的在LsitView的adapter里不停创建bitmap,这个地方是我们的首页banner位,理论上App一打开就会缓存这张默认背景图片了,而实际在使用过一段时间后,才因为为了解码这张背景图而OOM, 改为用全局缓存解决。&/p&&img src=&/v2-f2e05c64f5c68eab3d1702f_b.png& data-rawwidth=&771& data-rawheight=&333& class=&origin_image zh-lightbox-thumb& width=&771& data-original=&/v2-f2e05c64f5c68eab3d1702f_r.png&&&p& 下面这个就是传说中的内存抖动&/p&&img src=&/v2-33b6afc1cf4_b.png& data-rawwidth=&740& data-rawheight=&220& class=&origin_image zh-lightbox-thumb& width=&740& data-original=&/v2-33b6afc1cf4_r.png&&&p& 实际代码如下,因为打Log而进行了字符串拼接,一旦这个函数被比较频繁地调用,那么就很有可能会发生内存抖动。这里我们新版本已经改为使用stringbuilder进行优化。&/p&&img src=&/v2-99b6eabe939000adeb8aa5c3_b.png& data-rawwidth=&1169& data-rawheight=&223& class=&origin_image zh-lightbox-thumb& width=&1169& data-original=&/v2-99b6eabe939000adeb8aa5c3_r.png&&&p& 还有一些比较奇怪的情况,这里是我们扫描歌曲文件头的时候发生的,有些文件头居然有几百M大,导致一次申请了过大的内存,直接OOM,这里暂时也无法修复,直接catch住out of memory error。&/p&&img src=&/v2-09bc7d3fac33a86d4decf0b_b.png& data-rawwidth=&749& data-rawheight=&374& class=&origin_image zh-lightbox-thumb& width=&749& data-original=&/v2-09bc7d3fac33a86d4decf0b_r.png&&&h4&4. 同时我们对一些逻辑代码进行调整,比如我们的App主页的第三个tab(Live tab)进行了数据延迟加载,和定时回收。&/h4&&img src=&/v2-d6b15fb61c86fe6dd12400_b.png& data-rawwidth=&450& data-rawheight=&420& class=&origin_image zh-lightbox-thumb& width=&450& data-original=&/v2-d6b15fb61c86fe6dd12400_r.png&&&p& 这里因为这个页面除了有大图还有轮播banner,实际强引用的图片会有多张,如果这个时候切到其他页面进行听歌等行为,这个页面一直在后台缓存,实际是很浪费耗内存的,同时为优化体验,我们又不能直接通过设置主页的viewpager的缓存页数,因为这样经常都会回收,导致影响体验,所以我们在页面不可见后过一段时间,清理掉adapter数据(只是清空adapter里的数据,实际从网络加载回来的数据还在,这里只是为了去掉界面对图片的引用),当页面再次显示时再用已经加载的数据显示,即减少了很多情况下图片的引用,也不影响体验。&/p&&h4&5. 最后我们也遇到一个比较奇葩的问题,在我们的Bugly上报上有这样一条上报&/h4&&img src=&/v2-10f8899cbfaada2d18a3_b.png& data-rawwidth=&637& data-rawheight=&158& class=&origin_image zh-lightbox-thumb& width=&637& data-original=&/v2-10f8899cbfaada2d18a3_r.png&&&p& 我们在stackoverflow上看到了相关的讨论,大致意思是有些情况下比如息屏,或者一些省电模式下,频繁地调System.gc()可能会因为内核状态切换超时的异常。这个问题貌似没有比较好的解决方法,只能是优化内存,尽量减少手动调用System.gc()&/p&&p&&strong&优化结果&/strong&&/p&&p& 我们通过启动App后,切换到我的音乐界面,停留1分钟,多次gc后,获取App内存占用&/p&&p&&strong&优化前:&/strong&&/p&&img src=&/v2-e727d6b2f20a40f894c4f_b.png& data-rawwidth=&1428& data-rawheight=&157& class=&origin_image zh-lightbox-thumb& width=&1428& data-original=&/v2-e727d6b2f20a40f894c4f_r.png&&&p&&strong&优化后:&/strong&&/p&&img src=&/v2-1d076fbcbea6e_b.png& data-rawwidth=&1820& data-rawheight=&179& class=&origin_image zh-lightbox-thumb& width=&1820& data-original=&/v2-1d076fbcbea6e_r.png&&&p& 多次试验结果都差不多,这里只截取了其中一次,有28M的优化效果。&br& 当然不同的场景内存占用不同,同时上面试验结果是通过多次手动触发gc稳定后的结果。对于使用其他第三方工具不手动gc的情况下,试验结果可能会差异比较大。&/p&&p& 对于上面提到的JOOX里各种图片背景等问题,我们做了动态的优化,对不同的机型进行优化,对特别低端的机型设置为纯色背景等方式,最终优化效果如下:&/p&&img src=&/v2-b6ffc53c77b83f438e85bf_b.png& data-rawwidth=&1328& data-rawheight=&409& class=&origin_image zh-lightbox-thumb& width=&1328& data-original=&/v2-b6ffc53c77b83f438e85bf_r.png&&&img src=&/v2-e490de04c6dce1ec819cdc_b.png& data-rawwidth=&556& data-rawheight=&152& class=&origin_image zh-lightbox-thumb& width=&556& data-original=&/v2-e490de04c6dce1ec819cdc_r.png&&&p& 平均内存降低41M。&/p&&p& 本次总结主要还是从图片方面下手,还有一点逻辑优化,已经基本达到优化目标。&/p&&h2&四 总结&/h2&&p& 上面写了很多,我们可以简单总结,目前Andorid内存优化还是比较重要一个话题,我们可以通过各种内存泄露检测组件,MAT查看内存占用,Memory Monitor跟踪整个App的内存变化情况, Heap Viewer查看当前内存快照, Allocation Tracker追踪内存对象的来源,以及利用崩溃上报平台从多个方面对App内存进行监控和优化。上面只是列举了一些常见的情况,当然每个App功能,逻辑,架构也都不一样,造成内存问题也是不尽相同,掌握好工具的使用,发现问题所在,才能对症下药。&/p&&h4&参考链接&/h4&&blockquote&&p&1.Android 操作系统的内存回收机制&br&&a href=&/?target=https%3A///developerworks/cn/opensource/os-cn-android-mmry-rcycl/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Android 操作系统的内存回收机制&i class=&icon-external&&&/i&&/a&&/p&&p&2.阿里巴巴的Android内存优化分享&br&&a href=&/?target=http%3A//q.com/cn/presentations/android-memory-optimization& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Android内存优化 &i class=&icon-external&&&/i&&/a&&/p&&p&3.Android进程的内存管理分析&br&&a href=&/?target=http%3A//blog.csdn.net/gemmem/article/details/8920039& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Android进程的内存管理分析 - yulongfei的专栏 - 博客频道 - CSDN.NET&i class=&icon-external&&&/i&&/a&&/p&&p&4.android dalvik heap 浅析&br&&a href=&/?target=http%3A//blog.csdn.net/cqupt_chen/article/details/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&android dalvik heap 浅析&i class=&icon-external&&&/i&&/a&&/p&&p&5.揭秘 ART 细节 —— Garbage collection&br&&a href=&/?target=http%3A///jinkeep/p/3818180.html& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&【原创】【Android】揭秘 ART 细节 —— Garbage collection&i class=&icon-external&&&/i&&/a&&/p&&p&6.Android性能优化之常见的内存泄漏&br&&a href=&/?target=http%3A//blog.csdn.net/u/article/details/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Android性能优化之常见的内存泄漏 - 【博客地址永久迁移到】:http://zhengxiaoyong.me - 博客频道 - CSDN.NET&i class=&icon-external&&&/i&&/a&&/p&&p&7.Android App 内存泄露之Handler&br&&a href=&/?target=http%3A//blog.csdn.net/zhuanglonghai/article/details/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Android App 内存泄露之Handler&i class=&icon-external&&&/i&&/a&&/p&&p&8.GlideBitmapPool&br&&a href=&/?target=https%3A///amitshekhariitbhu/GlideBitmapPool& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&amitshekhariitbhu/GlideBitmapPool&i class=&icon-external&&&/i&&/a&&/p&&p&9.Android 性能优化之String篇&br&&a href=&/?target=http%3A//blog.csdn.net/vfush/article/details/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Android 性能优化之String篇&i class=&icon-external&&&/i&&/a&&/p&&p&10.HashMap,ArrayMap,SparseArray源码分析及性能对比&br&&a href=&/?target=http%3A///p/7b9a1b386265& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&HashMap,ArrayMap,SparseArray源码分析及性能对比&i class=&icon-external&&&/i&&/a&&/p&&p&11.MAT使用教程&br&&a href=&/?target=http%3A//blog.csdn.net/itomge/article/details/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Talk is cheap. Show me the code&i class=&icon-external&&&/i&&/a&&/p&&p&12.MAT - Memory Analyzer Tool 使用进阶&br&&a href=&/?target=http%3A////mat_usage/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&MAT - Memory Analyzer Tool 使用进阶&i class=&icon-external&&&/i&&/a&&/p&&/blockquote&&p&更多精彩内容欢迎关注&a href=&/?target=https%3A///& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&腾讯 Bugly&i class=&icon-external&&&/i&&/a&的微信公众账号:&br&&img src=&/3f2c1b1ff77fcedf3fb54616_b.jpg& class=&content_image&&&/p&&p&&a href=&/?target=https%3A///& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&腾讯 Bugly&i class=&icon-external&&&/i&&/a&是一款专为移动开发者打造的质量监控工具,帮助开发者快速,便捷的定位线上应用崩溃的情况以及解决方案。智能合并功能帮助开发同学把每天上报的数千条 &a href=&/?target=https%3A///& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Crash&i class=&icon-external&&&/i&&/a& 根据根因合并分类,每日日报会列出影响用户数最多的崩溃,精准定位功能帮助开发同学定位到出问题的代码行,实时上报可以在发布后快速的了解应用的质量情况,适配最新的 iOS, Android 官方操作系统,鹅厂的工程师都在使用,快来加入我们吧!&/p&
本文来自于腾讯Bugly公众号(weixinBugly),未经作者同意,请勿转载,原文地址:导语 智能手机发展到今天已经有十几个年头,手机的软硬件都已经发生了翻天覆地的变化,特别是Android阵营,从一开始的一两百M到今天动辄4G,6G内存…
&img src=&/v2-bc9ebc46afb_b.jpg& data-rawwidth=&600& data-rawheight=&314& class=&origin_image zh-lightbox-thumb& width=&600& data-original=&/v2-bc9ebc46afb_r.jpg&&&p&当一个 App 发布之后,突然发现了一个严重 bug 需要进行紧急修复,这时候公司各方就会忙得焦头烂额:重新打包 App、测试、向各个应用市场和渠道换包、提示用户升级、用户下载、覆盖安装。有时候仅仅是为了修改了一行代码,也要付出巨大的成本进行换包和重新发布。这时候就提出一个问题:有没有办法以补丁的方式动态修复紧急Bug,不再需要重新发布App,不再需要用户重新下载,覆盖安装?答案当然是有的,那就是最近涌现出来得热补丁方案。&br&&/p&&p&截止目前国内包含支付宝、淘宝、微信、QQ空间、饿了么、美丽说蘑菇街、美团大众点评等团队都推出了自己的热修复方案。(以下图片来自美丽说蘑菇街热修复文章分析)&br&&/p&&img src=&/v2-3c1cbe45adf10055c11ec_b.png& data-rawwidth=&1260& data-rawheight=&816& class=&origin_image zh-lightbox-thumb& width=&1260& data-original=&/v2-3c1cbe45adf10055c11ec_r.png&&&p&1、&a href=&/?target=http%3A////HotPatchCompare/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&& Dexposed、AndFix、ClassLoader等各大热补丁方案分析和比较&i class=&icon-external&&&/i&&/a&&/p&&p&主要分析了 Dexposed、AndFix、ClassLoader 这三种方案的原理原理和各自的优缺点,感觉现在文章可以加入 Instant Run 和微信的 Tinker 热补丁方案了&/p&&p&2、&a href=&/?target=https%3A//mp./s%3F__biz%3DMzI1MTA1MzM2Nw%3D%3D%26mid%3Didx%3D1%26sn%3Db4fddeef12ad0d17f39d4a& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Android App 热补丁动态修复技术介绍及 QQ 空间热补丁动态修复技术方案原理&i class=&icon-external&&&/i&&/a&&/p&&p&Android QQ空间团队提出了独特的解决方法,该方案基于的是android dex分包方案的。具体大家直接点击原文查看&/p&&p&3、&a href=&/?target=https%3A///WeMobileDev/article/blob/master/%25E5%25BE%25AE%25E4%25BF%25A1Android%25E7%2583%25AD%25E8%25A1%25A5%25E4%25B8%%25AE%259E%25E8%25B7%25B5%25E6%25BC%%25BF%259B%25E4%25B9%258B%25E8%25B7%25AF.md%23rd& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&微信 Android 热补丁实践演进之路&i class=&icon-external&&&/i&&/a&&/p&&p&文章介绍了热补丁主要是让应用能够在无需重新安装的情况实现更新,帮助应用快速建立动态修复能力,同时对比了 Dexposed、AndFix、QQ 空间的 ClassLoader 等等的业务局限然后根据微信自身的需求推出了微信自己的热补丁方案 Tinker。&/p&&p&4、&a href=&/?target=http%3A//blog.csdn.net/qxs/article/category/5949113& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Alibaba 热修复方案系列文章(包含 AndFix、Dexposed 框架)&i class=&icon-external&&&/i&&/a&&/p&&p&Alibaba 出品的热修复方案,主要涉及到AndFix、Dexposed框架的使用

我要回帖

更多关于 android 获取res资源 的文章

 

随机推荐