相关文章:Android面试总结–Java篇
图中需要注意一下几点:
1.Activity实例是由系统自动创建,并在不同的状态期间回调相应的方法。一个最简单的完整的Activity生命周期会按照如下顺序回调:onCreate -> onStart -> onResume -> onPause -> onStop -> onDestroy。称之为entire lifetime。
2.当执行onStart回调方法时,Activity开始被用户所见(也就是说,onCreate时用户是看不到此Activity的,那用户看到的是哪个?当然是此Activity之前的那个Activity),一直到onStop之前,此阶段Activity都是被用户可见,称之为visible lifetime。
3.当执行到onResume回调方法时,Activity可以响应用户交互,一直到onPause方法之前,此阶段Activity称之为foreground lifetime。
在实际应用场景中,假设A Activity位于栈顶,此时用户操作,从A Activity跳转到B Activity。那么对AB来说,具体会回调哪些生命周期中的方法呢?回调方法的具体回调顺序又是怎么样的呢?
开始时,A被实例化,执行的回调有A:onCreate -> A:onStart -> A:onResume
当用户点击A中按钮来到B时,假设B全部遮挡住了A,将依次执行A:onPause -> B:onCreate -> B:onStart -> B:onResume -> A:onStop。
此时如果点击Back键,将依次执行B:onPause -> A:onRestart -> A:onStart -> A:onResume -> B:onStop -> B:onDestroy。
1、如果自己没有配置android:ConfigChanges,这时默认让系统处理,就会重建Activity,此时Activity的生命周期会走一遍,其中onSaveInstanceState() 与onRestoreIntanceState()
资源相关的系统配置发生改变或者资源不足:例如屏幕旋转、切换系统语言,当前Activity会销毁,并且在onStop之前回调onSaveInstanceState保存数据,在重新创建Activity的时候在onStart之后回调onRestoreInstanceState。其中Bundle数据会传到onCreate(不一定有数据)和onRestoreInstanceState(一定有数据)。 用户或者程序员主动去销毁一个Activity的时候不会回调,其他情况都会调用,来保存界面信息。如代码中finish()或用户按下back,不会回调。
2、如果设置android:configChanges=“orientation|keyboardHidden|screenSize”>,此时Activity的生命周期不会重走一遍,Activity不会重建,只会回调onConfigurationChanged方法。
任务栈是一种后进先出的结构。位于栈顶的Activity处于焦点状态,当按下back按钮的时候,栈内的Activity会一个一个的出栈,并且调用其onDestory()方法。如果栈内没有Activity,那么系统就会回收这个栈,每个APP默认只有一个栈,以APP的包名来命名.
启动模式可在AndroidManifest.xml中,通过标签的android:launchMode属性设置
standard模式
singleTop模式
singleTask模式
singleInstance模式
Intent Flags:
Flags有很多,比如:
Intent.FLAG_ACTIVITY_NEW_TASK 相当于singleTask
Intent. FLAG_ACTIVITY_CLEAR_TOP 相当于singleTop
被启动的服务的生命周期:如果一个Service被某个Activity 调用 Context.startService 方法启动,那么不管是否有Activity使用bindService绑定或unbindService解除绑定到该Service,该Service都在后台运行。如果一个Service被startService 方法多次启动,那么onCreate方法只会调用一次,onStart将会被调用多次(对应调用startService的次数),并且系统只会创建Service的一个实例(因此你应该知道只需要一次stopService调用)。该Service将会一直在后台运行,而不管对应程序的Activity是否在运行,直到被调用stopService,或自身的stopSelf方法。当然如果系统资源不足,android系统也可能结束服务。
被绑定的服务的生命周期:如果一个Service被某个Activity 调用 Context.bindService 方法绑定启动,不管调用 bindService 调用几次,onCreate方法都只会调用一次,同时onStart方法始终不会被调用。当连接建立之后,Service将会一直运行,除非调用Context.unbindService 断开连接或者之前调用bindService 的 Context 不存在了(如Activity被finish的时候),系统将会自动停止Service,对应onDestroy将被调用。
被启动又被绑定的服务的生命周期:如果一个Service又被启动又被绑定,则该Service将会一直在后台运行。并且不管如何调用,onCreate始终只会调用一次,对应startService调用多少次,Service的onStart便会调用多少次。调用unbindService将不会停止Service,而必须调用 stopService 或 Service的 stopSelf 来停止服务。
当服务被停止时清除服务:当一个Service被终止(1、调用stopService;2、调用stopSelf;3、不再有绑定的连接(没有被启动))时,onDestroy方法将会被调用,在这里你应当做一些清除工作,如停止在Service中创建并运行的线程。
生命周期方法简单介绍
startService()
作用:启动Service服务
手动调用startService()后,自动调用内部方法:onCreate()、onStartCommand()
如果一个service被startService多次启动,onCreate()只会调用一次
onStartCommand()调用次数=startService()次数
stopService()
作用:关闭Service服务
手动调用stopService()后,自动调用内部方法:onDestory()
如果一个service被启动且被绑定,如果没有在绑定的前提下stopService()是无法停止服务的。
bindService()
作用:绑定Service服务
手动调用bindService()后,自动调用内部方法:onCreate()、onBind()
unbindService()
作用:解绑Service服务
手动调用unbindService()后,自动调用内部方法:onCreate()、onBind()、onDestory()
注意:
startService()和stopService()只能开启和关闭Service,无法操作Service;
bindService()和unbindService()可以操作Service
startService开启的Service,调用者退出后Service仍然存在;
BindService开启的Service,调用者退出后,Service随着调用者销毁。
Service一般分为两种:
本地服务, Local Service 用于应用程序内部。在Service可以调用Context.startService()启动,调用Context.stopService()结束。 在内部可以调用Service.stopSelf() 或 Service.stopSelfResult()来自己停止。无论调用了多少次startService(),都只需调用一次 stopService()来停止。
远程服务, Remote Service 用于android系统内部的应用程序之间。可以定义接口并把接口暴露出来,以便其他应用进行操作。客户端建立到服务对象的连接,并通过那个连接来调用服 务。调用Context.bindService()方法建立连接,并启动,以调用 Context.unbindService()关闭连接。多个客户端可以绑定至同一个服务。如果服务此时还没有加载,bindService()会先加 载它。
提供给可被其他应用复用,比如定义一个天气预报服务,提供与其他应用调用即可。
我们知道线程是CPU调度的最小单位。在Android中主线程是不能够做耗时操作的,子线程是不能够更新UI的。而线程间通信的方式有很多,比如广播,Eventbus,接口回掉,在Android中主要是使用handler。handler通过调用sendmessage方法,将保存消息的Message发送到Messagequeue中,而looper对象不断的调用loop方法,从messageueue中取出message,交给handler处理,从而完成线程间通信。
Android中常见的线程池有四种,FixedThreadPool、CachedThreadPool、ScheduledThreadPool、SingleThreadExecutor。
线程池是通过Executors的new FixedThreadPool方法来创建。它的特点是该线程池中的线程数量是固定的。即使线程处于闲置的状态,它们也不会被回收,除非线程池被关闭。当所有的线程都处于活跃状态的时候,新任务就处于队列中等待线程来处理。注意,FixedThreadPool只有核心线程,没有非核心线程。
线程池是通过Executors的new CachedThreadPool进行创建的。它是一种线程数目不固定的线程池,它没有核心线程,只有非核心线程,当线程池中的线程都处于活跃状态,就会创建新的线程来处理新的任务。否则就会利用闲置的线程来处理新的任务。线程池中的线程都有超时机制,这个超时机制时长是60s,超过这个时间,闲置的线程就会被回收。这种线程池适合处理大量并且耗时较少的任务。这里得说一下,CachedThreadPool的任务队列,基本都是空的。
线程池是通过Executors的new ScheduledThreadPool进行创建的,它的核心线程是固定的,但是非核心线程数是不固定的,并且当非核心线程一处于空闲状态,就立即被回收。这种线程适合执行定时任务和具有固定周期的重复任务。
线程池是通过Executors的new SingleThreadExecutor方法来创建的,这类线程池中只有一个核心线程,也没有非核心线程,这就确保了所有任务能够在同一个线程并且按照顺序来执行,这样就不需要考虑线程同步的问题。
AsyncTask是Android本身提供的一种轻量级的异步任务类。它可以在线程池中执行后台任务,然后把执行的进度和最终的结果传递给主线程更新UI。实际上,AsyncTask内部是封装了Thread和Handler。虽然AsyncTask很方便的执行后台任务,以及在主线程上更新UI,但是,AsyncTask并不合适进行特别耗时的后台操作,对于特别耗时的任务,个人还是建议使用线程池。
AsyncTask提供有4个核心方法:
1、onPreExecute():该方法在主线程中执行,在执行异步任务之前会被调用,一般用于一些准备工作。
2、doInBackground(String… params):这个方法是在线程池中执行,此方法用于执行异步任务。在这个方法中可以通过publishProgress方法来更新任务的进度,publishProgress方法会调用onProgressUpdate方法,另外,任务的结果返回给onPostExecute方法。
3、onProgressUpdate(Object… values):该方法在主线程中执行,主要用于任务进度更新的时候,该方法会被调用。
4、onPostExecute(Long aLong):在主线程中执行,在异步任务执行完毕之后,该方法会被调用,该方法的参数及为后台的返回结果。
除了这几个方法之外还有一些不太常用的方法,如onCancelled(),在异步任务取消的情况下,该方法会被调用。
源码可以知道从上面的execute方法内部调用的是executeOnExecutor()方法,即executeOnExecutor(sDefaultExecutor, params);而sDefaultExecutor实际上是一个串行的线程池。而onPreExecute()方法在这里就会被调用了。
接着看这个线程池。AsyncTask的执行是排队执行的,因为有关键字synchronized,而AsyncTask的Params参数就封装成为FutureTask类,FutureTask这个类是一个并发类,在这里它充当了Runnable的作用。
接着FutureTask会交给SerialExecutor的execute方法去处理,而SerialExecutor的executor方法首先就会将FutureTask添加到mTasks队列中,如果这个时候没有任务,就会调用scheduleNext()方法,执行下一个任务。如果有任务的话,则执行完毕后最后在调用scheduleNext()执行下一个任务。直到所有任务被执行完毕。
而AsyncTask的构造方法中有一个call()方法,而这个方法由于会被FutureTask的run方法执行,所以最终这个call方法会在线程池中执行。
而doInBackground这个方法就是在这里被调用的。我们好好研究一下这个call()方法。mTaskInvoked.set(true)表示当前任务已经执行过了。接着执行doInBackground方法,最后将结果通过postResult(result)方法进行传递。
postResult()方法中通过sHandler来发送消息,sHandler的中通过消息的类型来判断一个MESSAGE_POST_RESULT,这种情况就是调用onPostExecute(result)方法或者是onCancelled(result)。另一种消息类型是MESSAGE_POST_PROGRESS则调用更新进度onProgressUpdate。
我们先来了解一下这个类中每个方法的含义:
DESCRIPTOR:Binder的唯一标识,一般用于当前Binder的类名表示。
asInterface(android.os.IBinder obj):用于将服务端的Binder对象转换成客户端所需的AIDL接口类型的对象,这种转化过程是区分进程的,如果客户端和服务端位于同一个进程,那么这个方法返回的是服务端的stub对象本身,否则返回的是系统封装后的Stub.proxy对象。
asBinder():用于返回当前Binder对象。
onTransact:该方法运行在服务端的Binder线程池中,当客户端发起跨进程通信请求的时候,远程请求通过系统底层封装后交给该方法处理。注意这个方法public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags),服务端通过code可以确定客户端所请求的目标方法是什么,接着从data中取出目标方法所需的参数,然后执行目标方法。当目标方法执行完毕后,就像reply中写入返回值。这个方法的执行过程就是这样的。如果这个方法返回false,客户端是会请求失败的,所以我们可以在这个方法中做一些安全验证。
Binder的工作机制但是要注意一些问题:
1、当客户端发起请求时,由于当前线程会被挂起,直到服务端返回数据,如果这个远程方法很耗时的话,那么是不能够在UI线程,也就是主线程中发起这个远程请求的。
2、由于Service的Binder方法运行在线程池中,所以Binder方法不管是耗时还是不耗时都应该采用同步的方式,因为它已经运行在一个线程中了。
Binder机制具体有两层含义:
Android跨进程通信AIDL详解
Android-IPC之Binder机制简介
另一种理解:
Binde机制简单理解:
在Android系统的Binder机制中,是有Client,Service,ServiceManager,Binder驱动程序组成的,其中Client,service,Service Manager运行在用户空间,Binder驱动程序是运行在内核空间的。而Binder就是把这4种组件粘合在一块的粘合剂,其中核心的组件就是Binder驱动程序,Service Manager提供辅助管理的功能,而Client和Service正是在Binder驱动程序和Service Manager提供的基础设施上实现C/S 之间的通信。其中Binder驱动程序提供设备文件/dev/binder与用户控件进行交互,
Client、Service,Service Manager通过open和ioctl文件操作相应的方法与Binder驱动程序进行通信。而Client和Service之间的进程间通信是通过Binder驱动程序间接实现的。而Binder Manager是一个守护进程,用来管理Service,并向Client提供查询Service接口的能力。
Android自定义view,我们都知道实现有三部曲:onMeasure()、onLayout()、onDraw()。
View的绘制流程是从viewRoot的perfromTraversal方法开始的。它经过measure,layout,draw方法才能够将view绘制出来。其中 measure是测量宽高的,layout是确定view在父容器上的摆布位置的,draw是将view绘制到屏幕上的。
view的测量是需要MeasureSpc(测量规格),它代表一个32位int值,高2位代表SpecMode(测量模式),低(30)位的代表SpecSize(某种测量模式下的规格大小)。而一组SpecMode和SpeSize可以打包为一个MeasureSpec,反之,MeasureSpec可以解包得到SpecMode和SpeSize的值。SpecMode有三类:
unSpecified:父容器不对view有任何限制,要多大有多大。一般系统用这个多。
Exactly:父容器已经检测出view所需要的精确大小,这个时候,view的大小就是SpecSize所指定的值,它对应着layout布局中的math_parent或者是具体的数值
At_most:父容器指定了一个可变大小的SpecSize,view的大小不能够大于这个值,它对应这布局中的wrap_content.
对于普通的view,它的MeasureSpec是由父容器的MeasureSpec和自身的layoutParam共同决定的,一旦MeasureSpec确定后,onMeasure就可以确定view的宽高了。
View的measure过程:
onMeasure方法中有个setMeasureDimenSion方法来设置view的宽高测量值,而setMeasureDimenSion有个getDefaultSize()方法作为参数。一般情况下,我们只需要关注at_most和exactly两种情况,getDefaultSize的返回值就是measureSpec中的SpecSize,而这个值基本就是view测量后的大小。而UnSpecified这种情况,一般是系统内部的测量过程,它是需要考虑view的背景这些因素的。
前面说的是view的测量过程,而viewGroup的measure过程:
对于viewGroup来说,除了完成自己的measure过程以外,还要遍历去调用子类的measure方法,各个子元素在递归执行这个过程,viewGroup是一个抽象的类,没有提供有onMeasure方法,但是提供了一个measureChildren的方法。measureChild方法的思想就是取出子元素的layoutParams,然后通过getChildMeasureSpec来常见子元素的MeasureSpec,然后子元素在measure方法进行测量。由于viewGroup子类有不同的布局方式,导致他们的测量细节不一样,所以viewGroup不能象view一样调用onMeasure方法进行测量。
注意:在activity的生命周期中是没有办法正确的获取view的宽高的,原因就是view没有测量完。
1、在onWindowFocuschanged方法中获取 ----该方法含义是view已经初始化完毕。
2、View.post()方法,将消息队列的尾部。
3、使用viewTreeObserver的回调来完成。
4、通过view.measure方式手动测量。
普通的view的话,可以通过setFrame方法来的到view四个顶点的位置,也就确定了view在父容器的位置,接着就调用onLayout方法,该方法是父容器确定子元素的位置。
该方法就是将view绘制到屏幕上。分以下几步
总结一下View的绘制流程:
由于手机硬件的限制,内存和CPU都无法像pc一样具有超大的内存,Android手机上,过多的使用内存,会容易导致oom,过多的使用CPU资源,会导致手机卡顿,甚至导致anr。我主要是从一下几部分进行优化:
布局优化,绘制优化,内存泄漏优化,响应速度优化,listview优化,bitmap优化,线程优化
布局优化:工具 hierarchyviewer,解决方式:
1、删除无用的空间和层级。
2、选择性能较低的viewgroup,如Relativelayout,如果可以选择Relativelayout也可以使用LinearLayout,就优先使用LinearLayout,因为相对来说Relativelayout功能较为复杂,会占用更多的CPU资源。
3、使用标签重用布局,减少层级,进行预加载,使用的时候才加载。
绘制优化
绘制优化指view在ondraw方法中避免大量的耗时操作,由于ondraw方法可能会被频繁的调用。
1、ondraw方法中不要创建新的局部变量,ondraw方法被频繁的调用,很容易引起GC。
2、ondraw方法不要做耗时操作。
内存优化:参考内存泄漏。
context的使用,单列中传入的是activity的context,在关闭activity时,activity的内存无法被回收,因为单列持有activity的引用
在context的使用上,应该传入application的context到单列模式中,这样就保证了单列的生命周期跟application的生命周期一样
Android使用Handler造成内存泄露的分析及解决方法
通常Handler、AnycTask、Thread等内存泄漏都是由于创建了非静态内部类(或匿名内部类)并由子线程持有并执行耗时操作,导致Handler的生命周期比外部类的生命周期长;
应该在使用完成时主动销毁,或者在Activity销毁时注销BraodcastReceiver
通常Presenter要同时持有View和Model的引用,如果Activity退出的时候,Presenter正在处理一些耗时操作,那么Presenter的生命周期会比Activity长,导致Activity无法回收。因此,我们在使用MVP架构的时候要做好生命周期的维护,在View被销毁的时候要通知Presenter释放其持有
响应优化
主线程不能做耗时操作,触摸事件5s,广播10s,service20s。
listview优化:
1、getview方法中避免耗时操作。
2、view的复用和viewholder的使用。
3、滑动不适合开启异步加载。
4、分页处理数据。
5、图片使用三级缓存。
Bitmap优化:
1、等比例压缩图片。
2、不用的图片,及时recycler掉
线程优化
线程优化的思想是使用线程池来管理和复用线程,避免程序中有大量的Thread,同时可以控制线程的并发数,避免相互抢占资源而导致线程阻塞。
其他优化
1、少用枚举,枚举占用空间大。
2、使用Android特有的数据结构,如SparseArray来代替hashMap。
3、适当的使用软引用和弱引用。
概念:
ContentProvider是android四大组件之一,需要在Mainfest中注册
ContentProvider为跨进程访问数据提供接口(通讯录、闹钟、图库、视频、联系人等等)
ContentProvider提供数据存储的统一的接口(并不能实际存储数据)
数据的更新通知:使用ContentResolver
ContentProvider优缺点:
优点:
缺点:
原理:
ContentProvider的底层原理 = Android中的Binder机制
URI定义
深入理解Android之View的绘制流程
静态注册在App首次启动时,系统会自动实例化mBroadcastReceiver类,并注册到系统中
原理:
Android中的广播使用了设计模式中的观察者模式
BroadcastReceiver模型中有3个角色:
生命周期
BroadcastReceiver的生命周期与onReceive方法一致
调用流程
1、广播接收器接收到相应广播后,会自动回调 onReceive() 方法
2、一般情况下,onReceive方法会涉及 与 其他组件之间的交互,如发送Notification、启动Service等
3、默认情况下,广播接收器运行在 UI 线程,因此,onReceive()方法不能执行耗时操作,否则将导致ANR
广播的类型
使用方式:
sendOrderedBroadcast(intent);
注意点:
1、有序是针对广播接收者而言的;
2、广播接受者接收广播的顺序规则(同时面向静态和动态注册的广播接受者)
(1)、按照Priority属性值从大到下排序;
(2)、Priority属性相同者,动态注册的广播优先;
特点:
1、接收广播按顺序接收
先接收的广播接收者可以对广播进行截断(可以使用abortBroadCast来拦截),即后接收的广播接收者不再接收到此广播;
先接收的广播接收者可以对广播进行修改(setResultExtras()),那么后接收的广播接收者将接收到被修改后的广播(getResultExtras(true)))
由于在Android5.0 & API 21中已经失效,所以不建议使用,在这里也不作过多的总结。
应用内广播是指广播的发送者和接收者都同属于一个App,使用方式:
1、注册广播时将exported属性设置为false(exported对于有intent-filter情况下默认值为true),使得非本App内部发出的此广播不被接收;
2、在广播发送和接收时,增设相应权限permission,用于权限验证;
3、发送广播时指定该广播接收器所在的包名,此广播将只会发送到此包中的App内与之相匹配的有效广播接收器中
注意
对于不同注册方式的广播接收器回调OnReceive(Context context,Intent intent)中的context返回值是不一样的:
此处延伸:Volley里用的哪种请求方式(Volley2.3前HttpClient,2.3后HttpUrlConnection)
当前业界的Android进程保活手段主要分为黑、白、灰 三种,其大致的实现思路如下:
熟悉Android系统的童鞋都知道,系统出于体验和性能上的考虑,app在退到后台时系统并不会真正的kill掉这个进程,而是将其缓存起来。打开的应用越多,后台缓存的进程也越多。在系统内存不足的情况下,系统开始依据自身的一套进程回收机制来判断要kill掉哪些进程,以腾出内存来供给需要的app。这套杀进程回收内存的机制就叫 Low Memory Killer ,它是基于Linux内核的 OOM Killer(Out-Of-Memory killer)机制诞生。
进程的重要性,划分5级:
前台进程 (Foreground process)
可见进程 (Visible process)
服务进程 (Service process)
后台进程 (Background process)
空进程 (Empty process)
了解完 Low Memory Killer,再科普一下oom_adj。什么是oom_adj?它是linux内核分配给每个系统进程的一个值,代表进程的优先级,进程回收机制就是根据这个优先级来决定是否进行回收。对于oom_adj的作用,你只需要记住以下几点即可:
进程的oom_adj越大,表示此进程优先级越低,越容易被杀回收;越小,表示进程优先级越高,越不容易被杀回收
普通app进程的oom_adj>=0,系统进程的oom_adj才可能<0
有些手机厂商把这些知名的app放入了自己的白名单中,保证了进程不死来提高用户体验(如微信、QQ、陌陌都在小米的白名单中)。如果从白名单中移除,他们终究还是和普通app一样躲避不了被杀的命运,为了尽量避免被杀,还是老老实实去做好优化工作吧。
所以,进程保活的根本方案终究还是回到了性能优化上,进程永生不死终究是个彻头彻尾的伪命题!
getApplicationContext()和getApplication()方法得到的对象都是同一个application对象,只是对象的类型不一样。
Context数量 = Activity数量 + Service数量 + 1 (1为Application)
1.在我们取得Dialog对象后,需给它设置类型,即:
dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT)
2.在Manifest中加上权限:
这里先来个算是比较恰当的比喻来形容下它们的关系吧。Activity像一个工匠(控制单元),Window像窗户(承载模型),View像窗花(显示视图)LayoutInflater像剪刀,Xml配置像窗花图纸。
Android onTouch事件传递机制
Android 跨进程通信,像intent,contentProvider,广播,service都可以跨进程通信。
总的来说,Android动画可以分为两类,最初的传统动画和Android3.0 之后出现的属性动画;
传统动画又包括 帧动画(Frame Animation)和补间动画(Tweened Animation)
1、Tween Animation:
通过Animation类和AnimationUtils配合实现的。动画效果可以预先配置在res/anim目录下的xml文件中。
2、Frame Animation:
这种动画(也叫Frame动画、帧动画)其实可以划分到视图动画的类别,专门用来一个一个的显示Drawable的resources,就像放幻灯片一样,
3、Property Animation:
4、视频动画
由UI设计师提供MP4视频,通过Android的VideoView播放完成的动画效果;
5、Lottie动画
由UI设计师通过AE的Lottie插件生成json数据并提供给Android或IOS开发者,由开发者集成完成的动画;
Android中主线程是不能进行耗时操作的,子线程是不能进行更新UI的。所以就有了handler,它的作用就是实现线程之间的通信。
handler整个流程中,主要有四个对象,handler,Message,MessageQueue,Looper。当应用创建的时候,就会在主线程中创建handler对象,
我们通过要传送的消息保存到Message中,handler通过调用sendMessage方法将Message发送到MessageQueue中,Looper对象就会不断的调用loop()方法
不断的从MessageQueue中取出Message交给handler进行处理。从而实现线程之间的通信。
Android 之 Handler 机制
Android 热修复原理篇及几大方案比较
android架构设计之插件化、组件化
Handler造成内存泄漏的原因:
对于Android应用来说,就是你的用户打开一个Activity,使用完之后关闭它,内存泄露;又打开,又关闭,又泄露;几次之后,程序占用内存超过系统限制,FC。
另外,如果一组对象中只包含互相的引用,而没有来自它们外部的引用(例如有两个对象A和B互相持有引用,但没有任何外部对象持有指向A或B的引用),这仍然属于不可到达,同样会被GC回收。
解决方案:
方法一:通过程序逻辑来进行保护。
方法二:将Handler声明为静态类。
PS:在Java 中,非静态的内部类和匿名内部类都会隐式地持有其外部类的引用,静态的内部类不会持有外部类的引用。
静态类不持有外部类的对象,所以你的Activity可以随意被回收。由于Handler不再持有外部类对象的引用,导致程序不允许你在Handler中操作Activity中的对象了。所以你需要在Handler中增加一个对Activity的弱引用(WeakReference)。
static class MyHandler extends Handler
{
WeakReference mWeakReference;
public MyHandler(Activity activity)
{
mWeakReference=new WeakReference(activity);
}
@Override
public void handleMessage(Message msg)
{
final Activity activity=mWeakReference.get();
if(activity!=null)
{
if (msg.what == 1)
{
noteBookAdapter.notifyDataSetChanged();
}
}
}
}
PS:什么是WeakReference?
WeakReference弱引用,与强引用(即我们常说的引用)相对,它的特点是,GC在回收时会忽略掉弱引用,即就算有弱引用指向某对象,但只要该对象没有被强引用指向(实际上多数时候还要求没有软引用,但此处软引用的概念可以忽略),该对象就会在被GC检查到时回收掉。对于上面的代码,用户在关闭Activity之后,就算后台线程还没结束,但由于仅有一条来自Handler的弱引用指向Activity,所以GC仍然会在检查的时候把Activity回收掉。这样,内存泄露的问题就不会出现了。
总结:使用非静态内部类(匿名内部类)时,都会隐式地持有其外部类的引用(比如:Activity),如果此时内部类的生命周期比引用的外部类的生命周期长时(比如内部类开启子线程执行耗时任务),就会造成内存泄漏;
最常见的如Handler、Thread、AsyncTask、EventBus、RxAndroid
//获取系统分配给每个APP最高可使用的内存大小
int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
图片压缩
压缩后的图片大小应该和用来展示它的控件大小相近,在一个很小的ImageView上显示一张超大的图片不会带来任何视觉上的好处,但却会占用我们相当多宝贵的内存,而且在性能上还可能会带来负面影响
BitmapFactory这个类提供了多个解析方法(decodeByteArray, decodeFile, decodeResource等)用于创建Bitmap对象,我们应该根据图片的来源选择合适的方法。比如SD卡中的图片可以使用decodeFile方法,网络上的图片可以使用decodeStream方法,资源文件中的图片可以使用decodeResource方法
BitmapFactory提供了一个可选的BitmapFactory.Options参数,nJustDecodeBounds属性设置为true时系统不会真正的给bitmap分配内存,但是BitmapFactory.Options的outWidth、outHeight和outMimeType属性都会被赋值,但是需要注意压缩完成图片后需要将nJustDecodeBounds设置回false,这样才能获取到bitmap;
BitmapFactory.Options options = new BitmapFactory.Options();
//设置BitmapFactory.decodeResource不分配内存
options.inJustDecodeBounds = true;
//这个方法其实是有返回值的就是Bitmap,只是由于设置了inJustDecodeBounds=true,所以这时的返回值是null;
BitmapFactory.decodeResource(getResources(), R.id.myimage, options);
//BitmapFactory.Options的outWidth、outHeight和outMimeType属性都会被赋值
int imageHeight = options.outHeight;
int imageWidth = options.outWidth;
String imageType = options.outMimeType;
图片压缩的比例是通过BitmapFactory.Options的inSampleSize来设置的,比如我们有一张2048*1536像素的图片,将inSampleSize的值设置为4,就可以把这张图片压缩成512*384像素,由于Height和Width都压缩了4倍
,那么压缩后图片的内存大小是原来的1/16,
那么inSampleSize的值设置多少才合适呢?下面提供一种比较常用的方法:
/**
* @param options
* @param reqWidth -- 目标ImageView的Width
* @param reqHeight -- 目标ImageView的Height
* @return
*/
public static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
// 源图片的高度和宽度
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
// 计算出实际宽高和目标宽高的比率
final int heightRatio = Math.round((float) height / (float) reqHeight);
final int widthRatio = Math.round((float) width / (float) reqWidth);
// 选择宽和高中最小的比率作为inSampleSize的值,这样可以保证最终图片的宽和高
// 一定都会大于等于目标的宽和高。
inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
}
return inSampleSize;
}
图片缓存技术
对那些大量占用应用程序宝贵内存的图片提供了快速访问的方法。其中最核心的类是LruCache (此类在android-support-v4的包中提供) 。这个类非常适合用来缓存图片,它的主要算法原理是把最近使用的对象用强引用存储在 LinkedHashMap 中,并且把最近最少使用的对象在缓存值达到预设定值之前从内存中移除。
下面是一个使用 LruCache 来缓存图片的例子:
主要步骤:
1、要先设置缓存图片的内存大小,我这里设置为手机内存的1/8;
2、LruCache里面的键值对分别是URL和对应的图片;
3、重写了一个叫做sizeOf的方法,返回的是图片数量;
private LruCache mMemoryCache;
@Override
protected void onCreate(Bundle savedInstanceState) {
// 获取到可用内存的最大值,使用内存超出这个值会引起OutOfMemory异常。
// LruCache通过构造函数传入缓存值,以KB为单位。
int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
// 使用最大可用内存值的1/8作为缓存的大小。缓存大小可以自己确定
int cacheSize = maxMemory / 8;
mMemoryCache = new LruCache(cacheSize) {
@Override
protected int sizeOf(String key, Bitmap bitmap) {
// 重写此方法来衡量每张图片的大小,默认返回图片数量。
return bitmap.getByteCount() / 1024;
}
};
}
public void addBitmapToMemoryCache(String key, Bitmap bitmap) {
if (getBitmapFromMemCache(key) == null) {
mMemoryCache.put(key, bitmap);
}
}
public Bitmap getBitmapFromMemCache(String key) {
return mMemoryCache.get(key);
}
磁盘缓存没什么好说的直接将图片保存到SDCard上即可;
软引用
只要有足够的内存,就一直保持对象,直到发现内存吃紧且没有Strong Ref时才回收对象。
private Map> imageMap = new HashMap>();
获取:
SoftReference reference = imageMap.get(key);
Bitmap bitmap = reference.get();
两者的比较说到这里,有必要来进行一下比较了。网上有很多人使用软引用加载图片的多 ,但是现在已经不再推荐使用这种方式了,
(1)因为从
Android 2.3 (API Level 9)开始,垃圾回收器会更倾向于回收持有软引用或弱引用的对象,
这让软引用和弱引用变得不再可靠。
(2)另外,Android 3.0 (API Level 11)中,图片的数据会存储在本地的内存当中,
因而无法用一种可预见的方式将其释放,这就有潜在的风险造成应用程序的内存溢出并崩溃,
所以我这里用得是LruCache来缓存图片,当存储Image的大小大于LruCache设定的值,系统自动释放内存,
这个类是3.1版本中提供的,如果你是在更早的Android版本中开发,则需要导入android-support-v4的jar包。
BitmapRegionDecoder:主要用于显示图片的某一块矩形区域,如果你需要显示某个图片的指定区域,那么这个类非常合适。
BitmapRegionDecoder bitmapRegionDecoder = BitmapRegionDecoder.newInstance(inputStream, false);
bitmapRegionDecoder.decodeRegion(rect, options);
参数一很明显是一个rect,参数二是BitmapFactory.Options,你可以控制图片的inSampleSize,inPreferredConfig等
《Android源码设计模式解析》读书笔记——Android中你应该知道的设计模式
设计模式在Android源码中的运用
创建型
行为型
结构型
简单工厂模式
工厂方法模式
抽象工厂模式
相关文章:Android面试总结–Java篇