近来由于公司运作上出现的问题,导致离职的人员大大增加,同时也大大增加了自己工作量。渐渐萌生出离职的念头。(虽然公司就剩一个Android老板肯定不会放人)
好了!废话不多说。不管怎么样,不忘初心,多学习多整理。万变不离其宗,基础永远是最重要的。今天开始参考其他资料的同时结合自身整理一份Android面试题。(欢迎小伙伴们一起带着你们的面试经验来完善它。)
Android基础:
Q: Android的四大组件是哪些,它们的作用?
Android四大组件:Activity、service、content Provider、BroadCastReceiver
1、activity
(1)一个Activity通常就是一个单独的屏幕(窗口)。
(2)Activity之间通过Intent进行通信。
(3)android应用中每一个Activity都必须要在AndroidManifest.xml配置文件中声明,否则系统将不识别也不执行该Activity。
2、service
(1)service用于在后台完成用户指定的操作。service分为两种:
(a)started(启动):当应用程序组件(如activity)调用startService()方法启动服务时,服务处于started状态。
(b)bound(绑定):当应用程序组件调用bindService()方法绑定到服务时,服务处于bound状态。
( 2 )startService()与bindService()区别:
( a )started service(启动服务)是由其他组件调用startService()方法启动的,这导致服务的onStartCommand()方法被调用。当服务是started状态时,其生命周期与启动它的组件无关,并且可以在后台无限期运行,即使启动服务的组件已经被销毁。因此,服务需要在完成任务后调用stopSelf()方法停止,或者由其他组件调用stopService()方法停止。
( b )使用bindService()方法启用服务,调用者与服务绑定在了一起,调用者一旦退出,服务也就终止,大有“不求同时生,必须同时死”的特点。
( 3 )开发人员需要在应用程序配置文件中声明全部的service,使用标签。
( 4 )Service通常位于后台运行,它一般不需要与用户交互,因此Service组件没有图形用户界面。Service组件需要继承Service基类。Service组件通常用于为其他组件提供后台服务或监控其他组件的运行状态。
3、content provider
(1)android平台提供了Content Provider使一个应用程序的指定数据集提供给其他应用程序。其他应用可以通过ContentResolver类从该内容提供者中获取或存入数据。
(2)只有需要在多个应用程序间共享数据是才需要内容提供者。例如,通讯录数据被多个应用程序使用,且必须存储在一个内容提供者中。它的好处是统一数据访问方式。
(3)ContentProvider实现数据共享。ContentProvider用于保存和获取数据,并使其对所有应用程序可见。这是不同应用程序间共享数据的唯一方式,因为android没有提供所有应用共同访问的公共存储区。
(4)开发人员不会直接使用ContentProvider类的对象,大多数是通过ContentResolver对象实现对ContentProvider的操作。
(5)ContentProvider使用URI来唯一标识其数据集,这里的URI以content://作为前缀,表示该数据由ContentProvider来管理。
4、broadcast receiver
(1)你的应用可以使用它对外部事件进行过滤,只对感兴趣的外部事件(如当电话呼入时,或者数据网络可用时)进行接收并做出响应。广播接收器没有用户界面。然而,它们可以启动一个activity或serice来响应它们收到的信息,或者用NotificationManager来通知用户。通知可以用很多种方式来吸引用户的注意力,例如闪动背灯、震动、播放声音等。一般来说是在状态栏上放一个持久的图标,用户可以打开它并获取消息。
(2)广播接收者的注册有两种方法,分别是程序动态注册和AndroidManifest文件中进行静态注册。
(3)动态注册广播接收器特点是当用来注册的Activity关掉后,广播也就失效了。静态注册无需担忧广播接收器是否被关闭,只要设备是开启状态,广播接收器也是打开着的。也就是说哪怕app本身未启动,该app订阅的广播在触发时也会对它起作用。
Q:Android 6大布局
常用五种布局方式,分别是:FrameLayout(框架布局),LinearLayout (线性布局),AbsoluteLayout(绝对布局),RelativeLayout(相对布局),TableLayout(表格布局)。还有一种 GridLayout
一、FrameLayout:所有东西依次都放在左上角,会重叠,这个布局比较简单,也只能放一点比较简单的东西。
二、LinearLayout:线性布局,每一个LinearLayout里面又可分为垂直布局(android:orientation="vertical")和水平布局(android:orientation="horizontal" )。当垂直布局时,每一行就只有一个元素,多个元素依次垂直往下;水平布局时,只有一行,每一个元素依次向右排列。
三、AbsoluteLayout:绝对布局用X,Y坐标来指定元素的位置,这种布局方式也比较简单,但是在屏幕旋转时,往往会出问题,而且多个元素的时候,计算比较麻烦。
四、RelativeLayout:相对布局可以理解为某一个元素为参照物,来定位的布局方式。主要属性有:相对于某一个元素android:layout_below、 android:layout_toLeftOf相对于父元素的地方android:layout_alignParentLeft、android:layout_alignParentRigh;
五、TableLayout:表格布局,每一个TableLayout里面有表格行TableRow,TableRow里面可以具体定义每一个元素。每一个布局都有自己适合的方式,这五个布局元素可以相互嵌套应用,做出美观的界面。
六、GridLayout :布局使用虚细线将布局划分为行、列和单元格,也支持一个控件在行、列上都有交错排列。而GridLayout使用的其实是跟LinearLayout类似的API,只不过是修改了一下相关的标签而已,所以对于开发者来说,掌握GridLayout还是很容易的事情。
Q:Activity的生命周期函数有哪些?点击HOME键、BACK键等操作时,生命周期函数如何迁移?
onCreate 、onStart、onRestart、onResume、onPause、onDestory
onCreate // 创建Act实例时调用。通常进行一些数据的初始化,比如获取控件、申请数组或集合的内存、变量赋值
onRestart // Act停留在onStop但是没有onDestory
onStart // 该方法在onCreate或者onRestart之后调用,调用之后,Act进入可视生命周期
onResume // onStart之后调用,调用该方法后Act进入活动(运行、前台)状态,可以和用户进行交互,比如响应用户的输入、点击、触摸等操作
onPause // 在onResume之后,调用该方法,此Act就不能继续和用户交互,用户自定义的一些数据可以在此方法中进行保存
onStop // onPause之后进行调用,一旦调用onStop,Act就退出了可视状态,但是Act实例并没有销毁
onDestroy // 此方法是在销毁Act的时候调用,一旦调用表明该Act实例的生命周期就结束了,通常会在此方法做一些释放资源的操作,比如将引用变量值置为null
HOME键的执行顺序:onPause->onStop->onRestart->onStart->onResume
BACK键的顺序: onPause->onStop->onDestroy->onCreate->onStart->onResume
Q:activity在屏幕旋转时的生命周期
不设置Activity的android:configChanges时,切屏会重新调用各个生命周期,切横屏时会执行一次,切竖屏时会执行两次;
设置Activity的android:configChanges="orientation"时,切屏还是会重新调用各个生命周期,切横、竖屏时只会执行一次;
设置Activity的android:configChanges="orientation|keyboardHidden"时,切屏不会重新调用各个生命周期,只会执行onConfigurationChanged方法可以在这里保存数据。
Q : Activity缓存方法。
有a、b两个Activity,当从a进入b之后一段时间,可能系统会把a回收,这时候按back,执行的不是a的onRestart而是onCreate方法,a被重新创建一次,这是a中的临时数据和状态可能就丢失了。
可以用Activity中的onSaveInstanceState()回调方法保存临时数据和状态,这个方法一定会在活动被回收之前调用。方法中有一个Bundle参数,putString()、putInt()等方法需要传入两个参数,一个键一个值。数据保存之后会在onCreate中恢复,onCreate也有一个Bundle类型的参数。
一、onSaveInstanceState (Bundle outState)
当某个activity变得“容易”被系统销毁时,该activity的onSaveInstanceState就会被执行,除非该activity是被用户主动销毁的,例如当用户按BACK键的时候。
注意上面的双引号,何为“容易”?言下之意就是该activity还没有被销毁,而仅仅是一种可能性。这种可能性有哪些?通过重写一个activity的所有生命周期的onXXX方法,包括onSaveInstanceState和onRestoreInstanceState方法,我们可以清楚地知道当某个activity(假定为activity A)显示在当前task的最上层时,其onSaveInstanceState方法会在什么时候被执行,有这么几种情况:
1、当用户按下HOME键时。
这是显而易见的,系统不知道你按下HOME后要运行多少其他的程序,自然也不知道activity A是否会被销毁,故系统会调用onSaveInstanceState,让用户有机会保存某些非永久性的数据。以下几种情况的分析都遵循该原则
2、长按HOME键,选择运行其他的程序时。
3、按下电源按键(关闭屏幕显示)时。
4、从activity A中启动一个新的activity时。
5、屏幕方向切换时,例如从竖屏切换到横屏时。(如果不指定configchange属性) 在屏幕切换之前,系统会销毁activity A,在屏幕切换之后系统又会自动地创建activity A,所以onSaveInstanceState一定会被执行
总而言之,onSaveInstanceState的调用遵循一个重要原则,即当系统“未经你许可”时销毁了你的activity,则onSaveInstanceState会被系统调用,这是系统的责任,因为它必须要提供一个机会让你保存你的数据(当然你不保存那就随便你了)。另外,需要注意的几点:
1.布局中的每一个View默认实现了onSaveInstanceState()方法,这样的话,这个UI的任何改变都会自动地存储和在activity重新创建的时候自动地恢复。但是这种情况只有在你为这个UI提供了唯一的ID之后才起作用,如果没有提供ID,app将不会存储它的状态。
2.由于默认的onSaveInstanceState()方法的实现帮助UI存储它的状态,所以如果你需要覆盖这个方法去存储额外的状态信息,你应该在执行任何代码之前都调用父类的onSaveInstanceState()方法(super.onSaveInstanceState())。 既然有现成的可用,那么我们到底还要不要自己实现onSaveInstanceState()?这得看情况了,如果你自己的派生类中有变量影响到UI,或你程序的行为,当然就要把这个变量也保存了,那么就需要自己实现,否则就不需要。
3.由于onSaveInstanceState()方法调用的不确定性,你应该只使用这个方法去记录activity的瞬间状态(UI的状态)。不应该用这个方法去存储持久化数据。当用户离开这个activity的时候应该在onPause()方法中存储持久化数据(例如应该被存储到数据库中的数据)。
4.onSaveInstanceState()如果被调用,这个方法会在onStop()前被触发,但系统并不保证是否在onPause()之前或者之后触发。
二、onRestoreInstanceState (Bundle outState)
至于onRestoreInstanceState方法,需要注意的是,onSaveInstanceState方法和onRestoreInstanceState方法“不一定”是成对的被调用的,(本人注:我昨晚调试时就发现原来不一定成对被调用的!)
onRestoreInstanceState被调用的前提是,activity A“确实”被系统销毁了,而如果仅仅是停留在有这种可能性的情况下,则该方法不会被调用,例如,当正在显示activity A的时候,用户按下HOME键回到主界面,然后用户紧接着又返回到activity A,这种情况下activity A一般不会因为内存的原因被系统销毁,故activity A的onRestoreInstanceState方法不会被执行
另外,onRestoreInstanceState的bundle参数也会传递到onCreate方法中,你也可以选择在onCreate方法中做数据还原。 还有onRestoreInstanceState在onstart之后执行。 至于这两个函数的使用,给出示范代码(留意自定义代码在调用super的前或后):
Q:Activity的四种加载(启动)模式分别是?各自有什么特点?
standard:特点是每一次启动该Act时,会重建一个新的该Act实例
singleTop:特点是每一次该Act时,检查栈顶是否存在该Act的实例
存在:则直接重用该Act实例
不存在:则需要新建一个该Act的实例
singleTask:特点是每一次启动该Act时,需要在栈中去检查栈中是否存在该Act的实例
存在:
在栈顶:直接复用该Act的实例
不在栈顶:首先要把其上的Act实例移除掉,使该Act的实例回到栈顶去,然后再复用该Act的实例
不存在:新建一个该Act的实例
singleInstance:看进程中是否有该Act实例
存在:直接从该独享栈中取出该Act的实例复用
不存在:新建一个栈,然后新建一个该Act的实例,放入该栈中
注意:如果把程序入口MainAct的Act设置为singleInstance时,通过该Act启动了别的Act(SecondAct),再由其他 (SecondAct)启动ThirdAct,在ThirdAct中启动MainAct,由MainAct再去启动SecondAct时,不会产生第四个实例只是把MainAct所在的栈切换到SecondAct所在的栈,把栈顶的Act实例展示出来
Q : Activity意外退出时,如何进行数据保存和恢复?
开发者提前可以复写onSaveInstanceState方法,创建一个Bundle类型的参数,把数据存储在这个Bundle对象中,这样即使Activity意外退出,Activity被系统摧毁,当重新启动这个Activity而调用onCreate方法时,上述Bundle对象会作为参数传递给onCreate方法,开发者可以从Bundle对象中取出保存的数据,利用这些数据将Activity恢复到被摧毁之前的状态。
Q:Intent是什么?有什么用处?intent传值?
Intent(意图):作用是调用某个组件去做一个事情,它既能充当桥梁的角色,也能传递数据
Intent的传值: 通过Intent类提供的setData和putExtra方法传递。
Bundle(捆):
两个Activity之间的通讯可以通过bundle类来实现,把要传递的数据通过key-value的形式加入数据,另外一个Activity里面取出数据时,用key找出对应的value
如何实现使用Intent来传递自定义对象:
序列化要传递的自定义的对象,再通过Bundle来传值。
(序列化:将对象的状态转换为可保持或传输的格式的过程)与序列化相对的是反序列化,它将流转换为对象。通过序列化和反序列化就可以实现存储和传输数据。
Q:为什么在Service中创建子线程而不是Activity中
这是因为Activity很难对Thread进行控制,当Activity被销毁之后,就没有任何其它的办法可以再重新获取到之前创建的子线程的实例。而且在一个Activity中创建的子线程,另一个Activity无法对其进行操作。但是Service就不同了,所有的Activity都可以与Service进行关联,然后可以很方便地操作其中的方法,即使Activity被销毁了,之后只要重新与Service建立关联,就又能够获取到原有的Service中Binder的实例。因此,使用Service来处理后台任务,Activity就可以放心地finish,完全不需要担心无法对后台任务进行控制的情况。
Q : Service和Thread的区别?
答:servie是系统的组件,它由系统进程托管(servicemanager);它们之间的通信类似于client和server,是一种轻量级的ipc通信,这种通信的载体是binder,它是在linux层交换信息的一种ipc。而thread是由本应用程序托管。
1). Thread:Thread是程序执行的最小单元,它是分配CPU的基本单位。可以用Thread来执行一些异步的操作。
2). Service:Service是android的一种机制,当它运行的时候如果是LocalService,那么对应的Service是运行在主进程的main线程上的。如:onCreate,onStart这些函数在被系统调用的时候都是在主进程的main线程上运行的。如果是Remote Service,那么对应的Service则是运行在独立进程的main线程上。
既然这样,那么我们为什么要用Service呢?其实这跟android的系统机制有关,我们先拿Thread来说。Thread的运行是独立于Activity的,也就是说当一个Activity被finish之后,如果你没有主动停止Thread或者Thread里的run方法没有执行完毕的话,Thread也会一直执行。因此这里会出现一个问题:当Activity被finish之后,你不再持有该Thread的引用。另一方面,你没有办法在不同的Activity中对同一Thread进行控制。
举个例子:如果你的Thread需要不停地隔一段时间就要连接服务器做某种同步的话,该Thread需要在Activity没有start的时候也在运行。这个时候当你start一个Activity就没有办法在该Activity里面控制之前创建的Thread。因此你便需要创建并启动一个Service,在Service里面创建、运行并控制该Thread,这样便解决了该问题(因为任何Activity都可以控制同一Service,而系统也只会创建一个对应Service的实例)。
因此你可以把Service想象成一种消息服务,而你可以在任何有Context的地方调用Context.startService、Context.stopService、Context.bindService,Context.unbindService,来控制它,你也可以在Service里注册BroadcastReceiver,在其他地方通过发送broadcast来控制它,当然这些都是Thread做不到的。
Q : android中的动画有哪几类,它们的特点和区别是什么
A:两种,一种是Tween动画、还有一种是Frame动画。
Tween动画,这种实现方式可以使视图组件移动、放大、缩小以及产生透明度的变化;
Frame动画,传统的动画方法,通过顺序的播放排列好的图片来实现,类似电影。
Q :android 中有哪几种解析xml的类?官方推荐哪种?以及它们的原理和区别。
答:XML解析主要有三种方式,SAX、DOM、PULL。
常规在PC上开发我们使用Dom相对轻松些,但一些性能敏感的数据库或手机上还是主要采用SAX方式;
SAX读取是单向的,优点:不占内存空间、解析属性方便,但缺点就是对于套嵌多个分支来说处理不是很方便。
DOM方式会把整个XML文件加载到内存中去,这里Android开发网提醒大家该方法在查找方面可以和XPath很好的结合如果数据量不是很大推荐使用。
PULL常常用在J2ME对于节点处理比较好,类似SAX方式,同样很节省内存,在J2ME中我们经常使用的KXML库来解析。
Q : 谈谈android数据存储方式。
答:Android提供了5种方式存储数据:
(1)使用SharedPreferences存储数据;它是Android提供的用来存储一些简单配置信息的一种机制,采用了XML格式将数据存储到设备中。只能在同一个包内使用,不能在不同的包之间使用。
(2)文件存储数据;文件存储方式是一种较常用的方法,在Android中读取/写入文件的方法,与Java中实现I/O的程序是完全一样的,提供了openFileInput()和openFileOutput()方法来读取设备上的文件。
(3)SQLite数据库存储数据;SQLite是Android所带的一个标准的数据库,它支持SQL语句,它是一个轻量级的嵌入式数据库。
(4)使用ContentProvider存储数据;主要用于应用程序之间进行数据交换,从而能够让其他的应用保存或读取此Content Provider的各种数据类型。
(5)网络存储数据;通过网络上提供给我们的存储空间来上传(存储)和下载(获取)我们存储在网络空间中的数据信息。
Q:Android多线程与界面交互的方法
Activity.runOnUiThread(Runnable)
View.post(Runnable),View.postDelay(Runnable,long)
Handler
AsyncTask
www.jianshu.com/p/8e756803211f
Q : Android 多线程
AsyncTask
HandlerThread
IntentService
android中的线程池
www.jianshu.com/p/c7b16c3c4625
Q : 什么是OOM?如何避免OOM?
OOM概念:内存溢出(OutOfMemor),内存占有量超过了JVM分配的最大内存。
避免OOM:
<1.避免对activity的超过生命周期的引用(尽量使用application代替activity)。
因为程序一般是由很多个Activity构成的,从一个Activity跳转了以后,
系统就有可能回收这个Activity的各种内存占用。可是此时如果你的一些不可回收变量(比如静态变量)保持了对此Activity对象的引用,
那么GC就不会对此Activity进行回收,无故占用了大量的内存。这种情况最好的办法就是用application代替activity。
用Context.getApplicationContext() 或者 Activity.getApplication()可以很方便的得到application对象。
<2.在展示高分辨率图片时,先将图片进行压缩到与空间大小相近。
<3.及时释放不使用的Bitmap,动态回收内存,方法:bitmap.recycle()。
<4.对适配器视图进行优化处理,避免过多加载数据和对象的生成。
Q:什么是ANR?产生ANR的原因是什么?如何避免ANR的发生?
ANR概念:
应用程序无响应(application not response)。
原因:
主线程中做了非常耗时的操作。
解决办法:
<1.运行在主线程里的任何方法都尽可能少做事情,尽量用Handler来处理UIthread和别的thread之间的交互;
<2.应用程序应该避免在BroadcastReceiver里做耗时的操作或计算;
<3.避免在Intent Receiver里启动一个Activity,因为它会创建一个新的画面,并从当前用户正在运行的程序上抢夺焦点;
<4.在主线程中更新UI。
Q : View, surfaceView, GLSurfaceView有什么区别。
答:view是最基础的,必须在UI主线程内更新画面,速度较慢。
SurfaceView是view的子类,类似使用双缓机制,在新的线程中更新画面所以刷新界面速度比view快
GLSurfaceView是SurfaceView的子类,opengl专用的
Q:根据自己的理解描述下Android数字签名。
答:(1)所有的应用程序都必须有数字证书,Android系统不会安装一个没有数字证书的应用程序
(2)Android程序包使用的数字证书可以是自签名的,不需要一个权威的数字证书机构签名认证
(3)如果要正式发布一个Android,必须使用一个合适的私钥生成的数字证书来给程序签名,而不能使用adt插件或者ant工具生成的调试证书来发布。
(4)数字证书都是有有效期的,Android只是在应用程序安装的时候才会检查证书的有效期。如果程序已经安装在系统中,即使证书过期也不会影响程序的正常功能
Q:目前能否保证service不被杀死的方法有哪些?
Service设置成START_STICKY
kill 后会被重启(等待5秒左右),重传Intent,保持与重启前一样
提升service优先级
在AndroidManifest.xml文件中对于intent-filter可以通过android:priority = "1000"这个属性设置最高优先级,1000是最高值,如果数字越小则优先级越低,同时适用于广播。
【结论】目前看来,priority这个属性貌似只适用于broadcast,对于Service来说可能无效
提升service进程优先级
Android中的进程是托管的,当系统进程空间紧张的时候,会依照优先级自动进行进程的回收
当service运行在低内存的环境时,将会kill掉一些存在的进程。因此进程的优先级将会很重要,可以在startForeground()使用startForeground()将service放到前台状态。这样在低内存时被kill的几率会低一些。
【结论】如果在极度极度低内存的压力下,该service还是会被kill掉,并且不一定会restart()
onDestroy方法里重启service
service +broadcast 方式,就是当service走onDestory()的时候,发送一个自定义的广播,当收到广播的时候,重新启动service
也可以直接在onDestroy()里startService
【结论】当使用类似口口管家等第三方应用或是在setting里-应用-强制停止时,APP进程可能就直接被干掉了,onDestroy方法都进不来,所以还是无法保证
监听系统广播判断Service状态
通过系统的一些广播,比如:手机重启、界面唤醒、应用状态改变等等监听并捕获到,然后判断我们的Service是否还存活,别忘记加权限
【结论】这也能算是一种措施,不过感觉监听多了会导致Service很混乱,带来诸多不便
在JNI层,用C代码fork一个进程出来
这样产生的进程,会被系统认为是两个不同的进程.但是Android5.0之后可能不行
root之后放到system/app变成系统级应用
大招: 放一个像素在前台(手机QQ)
Android进阶:
Q:谈谈对Android View的理解
view负责Android ui 的绘制以及event的处理。Android 利用 View 打造出所 Widgets,利用 Widget 可打造出互动式的使用者介面,每个View 负责一定区域的绘制。
View 的几个重要方法:
requestLayout
View重新调用一次layout过程
invalidate
View重新调用一次draw过程
forceLayout
标识View在下一次重绘,需要重新调用layout过程。
postInvalidate
这个方法与invalidate方法的作用是一样的,都是使View树重绘,但两者的使用条件不同,postInvalidate是在非UI线程中调用,invalidate则是在UI线程中调用。
View 的绘制:
1.测量——onMeasure():决定View的大小
2.布局——onLayout():决定View在ViewGroup中的位置
3.绘制——onDraw():如何绘制这个View。
自定义View的分类
继承View
继承ViewGroup
继承系统控件(Button,LinearLayout…)
Q:recyclerview 滑动优化
RecyclerView 虽然比ListView灵活的多, 但是滚动时的卡顿确比ListView更明显, 有些人有疑问 :这难道不是绑定数据造成的吗? 其实不然。 是在滚动时会额外通过getView或者onCreateViewHolder 里面继续inflate.layout造成的。 很多有又有疑问, 为啥我没有这个感觉呢?? 那是因为很多layout不复杂的情况下效果很轻微, 但是如果你设置了header, 第一屏幕显示的都是header view , 而list是从第2页屏幕开始显示的呢??? 你有试过吗, 如果试过或者知道它们的缓存原理就会发现, 再RecyclerView或者ListView被显示出来的时候, getView或者onCreateViewHolder 并没有被执行, 或者执行很少, 只有在滚动时才获取执行, 然后就造成了卡顿。 那是因为它们的缓存机制导致, 初始化只加载可视范围内的item (这里不细细谈到它们的缓存机制,如果需要验证, 那么请在getView 或者onCreateViewHolder 哪里添加打印) 。 而滚动时inflate.layout 如果不复杂可能10ms就解决了, 你还能流程的体验, 但是如果复杂, 那么就会体验到滚动开始有轻微的卡顿(丢帧) , 知道它们的缓存机制完全足以支撑你的滚动, 不再去重新inflate.layout 。
卡顿的原因:
1.产品设计不合理导致卡片布局过度复杂,产品只追求界面的高大上,而忽略了实现上的复杂度。
2.卡顿的另一个最大原因是图片的加载,如果图片过大,卡顿甚至崩溃都不是问题。即使图片使用了压缩后的,并且用了Fresco等图片加载框架,发现还是会有卡顿。虽然图片是异步加载的,但是图片的加载都伴随着三级缓存,图片IO会导致卡
经过以上1,2分析,然后就开始考虑该怎么去优化呢?如果产品的需求改变不了,那就要另辟蹊径了。也就是主要的优化集中在复杂布局和图片加载。
复杂布局的优化:
1.尽量减少布局嵌套,层级越深,每次测量时间久越久。
2. 如果布局很复杂,可以考虑自定义布局能不能实现。
3.尽量减少过度绘制区域。这个可以在开发者选项中看到:调试GPU过度绘制。
Q:ButterKnife原理
ButterKnife对性能的影响很小,因为没有使用使用反射,而是使用的Annotation Processing Tool(APT),注解处理器,javac中用于编译时扫描和解析Java注解的工具。在编译阶段执行的,它的原理就是读入Java源代码,解析注解,然后生成新的Java代码。新生成的Java代码最后被编译成Java字节码,注解解析器不能改变读入的Java 类,比如不能加入或删除Java方法
Q : Handler内存泄漏分析及解决
###一、介绍
首先,请浏览下面这段handler代码:
public class SampleActivity extends Activity {
private final Handler mLeakyHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
// ...
}
}
}
在使用handler时,这是一段很常见的代码。但是,它却会造成严重的内存泄漏问题。在实际编写中,我们往往会得到如下警告:
⚠ In Android, Handler classes should be static or leaks might occur.
###二、分析
1、 Android角度
当Android应用程序启动时,framework会为该应用程序的主线程创建一个Looper对象。这个Looper对象包含一个简单的消息队列Message Queue,并且能够循环的处理队列中的消息。这些消息包括大多数应用程序framework事件,例如Activity生命周期方法调用、button点击等,这些消息都会被添加到消息队列中并被逐个处理。
另外,主线程的Looper对象会伴随该应用程序的整个生命周期。
然后,当主线程里,实例化一个Handler对象后,它就会自动与主线程Looper的消息队列关联起来。所有发送到消息队列的消息Message都会拥有一个对Handler的引用,所以当Looper来处理消息时,会据此回调[Handler#handleMessage(Message)]方法来处理消息。
2、 Java角度
在java里,非静态内部类 和 匿名类 都会潜在的引用它们所属的外部类。但是,静态内部类却不会。
###三、泄漏来源
请浏览下面一段代码:
public class SampleActivity extends Activity {
private final Handler mLeakyHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
// ...
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Post a message and delay its execution for 10 minutes.
mLeakyHandler.postDelayed(new Runnable() {
@Override
public void run() { /* ... */ }
}, 1000 * 60 * 10);
// Go back to the previous Activity.
finish();
}
}
当activity结束(finish)时,里面的延时消息在得到处理前,会一直保存在主线程的消息队列里持续10分钟。而且,由上文可知,这条消息持有对handler的引用,而handler又持有对其外部类(在这里,即SampleActivity)的潜在引用。这条引用关系会一直保持直到消息得到处理,从而,这阻止了SampleActivity被垃圾回收器回收,同时造成应用程序的泄漏。
<<<<<<< HEAD 注意,上面代码中的Runnable类--非静态匿名类--同样持有对其外部类的引用。从而也导致泄漏。
======= 注意,上面代码中的Runnable类--非静态匿名类--同样持有对其外部类的引用。从而也导致泄漏。
四、泄漏解决方案
首先,上面已经明确了内存泄漏来源:
只要有未处理的消息,那么消息会引用handler,非静态的handler又会引用外部类,即Activity,导致Activity无法被回收,造成泄漏;
Runnable类属于非静态匿名类,同样会引用外部类。
为了解决遇到的问题,我们要明确一点:静态内部类不会持有对外部类的引用。所以,我们可以把handler类放在单独的类文件中,或者使用静态内部类便可以避免泄漏。
另外,如果想要在handler内部去调用所在的外部类Activity,那么可以在handler内部使用弱引用的方式指向所在Activity,这样统一不会导致内存泄漏。
对于匿名类Runnable,同样可以将其设置为静态类。因为静态的匿名类不会持有对外部类的引用。
public class SampleActivity extends Activity {
/**
* Instances of static inner classes do not hold an implicit
* reference to their outer class.
*/
private static class MyHandler extends Handler {
private final WeakReference mActivity;
public MyHandler(SampleActivity activity) {
mActivity = new WeakReference(activity);
}
@Override
public void handleMessage(Message msg) {
SampleActivity activity = mActivity.get();
if (activity != null) {
// ...
}
}
}
private final MyHandler mHandler = new MyHandler(this);
/**
* Instances of anonymous classes do not hold an implicit
* reference to their outer class when they are "static".
*/
private static final Runnable sRunnable = new Runnable() {
@Override
public void run() { /* ... */ }
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Post a message and delay its execution for 10 minutes.
mHandler.postDelayed(sRunnable, 1000 * 60 * 10);
// Go back to the previous Activity.
finish();
}
}
###五、小结
<<<<<<< HEAD 虽然静态类与非静态类之间的区别并不大,但是对于Android开发者而言却是必须理解的。至少我们要清楚,如果一个内部类实例的生命周期比Activity更长,那么我们千万不要使用非静态的内部类。最好的做法是,使用静态内部类,然后在该类里使用弱引用来指向所在的Activity。
Q : ART和Dalvik区别
Art上应用启动快,运行快,但是耗费更多存储空间,安装时间长,总的来说ART的功效就是"空间换时间"。
ART: Ahead of Time Dalvik: Just in Time
什么是Dalvik:Dalvik是Google公司自己设计用于Android平台的Java虚拟机。Dalvik虚拟机是Google等厂商合作开发的Android移动设备平台的核心组成部分之一,它可以支持已转换为.dex(即Dalvik Executable)格式的Java应用程序的运行,.dex格式是专为Dalvik应用设计的一种压缩格式,适合内存和处理器速度有限的系统。Dalvik经过优化,允许在有限的内存中同时运行多个虚拟机的实例,并且每一个Dalvik应用作为独立的Linux进程执行。独立的进程可以防止在虚拟机崩溃的时候所有程序都被关闭。
什么是ART:Android操作系统已经成熟,Google的Android团队开始将注意力转向一些底层组件,其中之一是负责应用程序运行的Dalvik运行时。Google开发者已经花了两年时间开发更快执行效率更高更省电的替代ART运行时。ART代表Android Runtime,其处理应用程序执行的方式完全不同于Dalvik,Dalvik是依靠一个Just-In-Time(JIT)编译器去解释字节码。开发者编译后的应用代码需要通过一个解释器在用户的设备上运行,这一机制并不高效,但让应用能更容易在不同硬件和架构上运行。ART则完全改变了这套做法,在应用安装的时候就预编译字节码到机器语言,这一机制叫Ahead-Of-Time(AOT)编译。在移除解释代码这一过程后,应用程序执行将更有效率,启动更快。
ART优点:
系统性能的显著提升
应用启动更快、运行更快、体验更流畅、触感反馈更及时
更长的电池续航能力
支持更低的硬件
ART缺点:
更大的存储空间占用,可能会增加10%-20%
更长的应用安装时间
Q : Android 内存泄漏的总结
总结
1、对 Activity 等组件的引用应该控制在 Activity 的生命周期之内; 如果不能就考虑使用 getApplicationContext 或者 getApplication,以避免 Activity 被外部长生命周期的对象引用而泄露。
2、尽量不要在静态变量或者静态内部类中使用非静态外部成员变量(包括context ),即使要使用,也要考虑适时把外部成员变量置空;也可以在内部类中使用弱引用来引用外部类的变量。
3、对于生命周期比Activity长的内部类对象,并且内部类中使用了外部类的成员变量,可以这样做避免内存泄漏:
将内部类改为静态内部类
静态内部类中使用弱引用来引用外部类的成员变量
4、Handler 的持有的引用对象最好使用弱引用,资源释放时也可以清空 Handler 里面的消息。比如在 Activity onStop 或者 onDestroy 的时候,取消掉该 Handler 对象的 Message和 Runnable.
5、在 Java 的实现过程中,也要考虑其对象释放,最好的方法是在不使用某对象时,显式地将此对象赋值为 null,比如使用完Bitmap 后先调用 recycle(),再赋为null,清空对图片等资源有直接引用或者间接引用的数组(使用 array.clear() ; array = null)等,最好遵循谁创建谁释放的原则。
6、正确关闭资源,对于使用了BraodcastReceiver,ContentObserver,File,游标 Cursor,Stream,Bitmap等资源的使用,应该在Activity销毁时及时关闭或者注销。
7、保持对对象生命周期的敏感,特别注意单例、静态对象、全局性集合等的生命周期。
未完待续...
分享几个不错的资源:
github.com/GeniusVJR/LearningNotes/blob/master/README.md
github.com/JackyAndroid/AndroidInterview-Q-A/blob/master/README-CN.md
www.jianshu.com/p/13786463635d
www.diycode.cc/wiki/androidinterview
网友评论