近来由于公司运作上出现的问题,导致离职的人员大大增加,同时也大大增加了自己工作量。渐渐萌生出离职的念头。(虽然公司就剩一个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

Android插件化从入门到放弃-最强合集

github.com/Tim9Liu9/TimLiu-Android

Android面试相关文章以及github整理,偏2018,持续更新

更多相关文章

  1. “罗永浩抖音首秀”销售数据的可视化大屏是怎么做出来的呢?
  2. Nginx系列教程(三)| 一文带你读懂Nginx的负载均衡
  3. 不吹不黑!GitHub 上帮助人们学习编码的 12 个资源,错过血亏...
  4. Android控件 vs Flutter控件
  5. Android适应方案汇总(三)
  6. Winow&WindowManager
  7. Android触摸屏事件派发机制详解与源码分析一(View篇)
  8. Android(安卓)的属性动画的实现和使用详解
  9. Android(安卓)带清除功能的输入框控件EditText

随机推荐

  1. Android BaseAdapter如何获得每一项并添
  2. Android Kotlin继承
  3. 在android studio的虚拟机的sd卡上创建文
  4. android图片裁剪
  5. Android弹出对话框简单代码
  6. 使用universal-image-loader中出现的EOFE
  7. android编译系统之后刷img
  8. Android 开发架构学习篇
  9. Android(安卓)7.0新特性概览
  10. The specified Android SDK Build Tools