Android面试基础系列一——Android

0.476字数 14584阅读 1660

此系列文章是我在毕业求职期间,对Android面试相关的基础知识做的一个整理,内容还比较全面,现在将其发布出来,希望对即将求职的同学能有帮助。
这一系列的文章都是使用MarkDown编辑的,源文件也一并公布出来,大家可以在我文章的基础上,根据自己的情况修改或增加内容,定制自己的面试笔记。

链接:http://pan.baidu.com/s/1mhM4RSO 密码:hgzt

一、Activity

1、Activity生命周期

可以完整的写下来,注意不要遗漏了onReastart()方法。

2、Android任务栈

而Android是基于组件开发的软件架构,虽然我们开发android程序,仍然使用一个apk工程一个Application的开发形式,但是对于Aplication的开发就用到了Activity、service等四大组件,其中的每一个组件,都是可以被跨应用复用的,这就是android的神奇之处。虽然组件可以跨应用被调用,但是一个组件所在的进程必须是在组件所在的Aplication进程中。由于android强化了组件概念,弱化了Aplication的概念,所以在android程序开发中,A应用的A组件想要使用拍照或录像的功能就可以不用去针对Camera类进行开发,直接调用系统自带的摄像头应用(称其B应用)中的组件(称其B组件)就可以了,但是这就引发了一个新问题,A组件运行在A应用中,B组件运行在B应用中,自然都不在同一个进程中,那么从B组件中返回的时候,如何实现正确返回到A组件呢?Task就是来负责实现这个功能的,它是从用户角度来理解应用而建立的一个抽象概念。因为用户所能看到的组件就是Activity,所以Task可以理解为实现一个功能而负责管理所有用到的Activity实例的栈。

3、Activity启动模式

理解启动模式,还需要看看这篇文章:http://www.jianshu.com/p/2a9fcf3c11e4

  • Standar:每次启动一个Activity,都会重新创建一个Activity的实例,然后把它放入任务栈中。他不考虑栈中是否已经存在这个Activity的实例,是非常消耗资源的。一般不用这种模式。
  • SingleTop:栈顶复用模式,如果要创建的Activity在任务栈顶存在,就不存在。应用场景:在通知栏点击收到的通知,然后需要启动一个Activity,这个Activity就可以用singleTop,否则每次点击都会新建一个Activity。当然实际的开发过程中,测试妹纸没准给你提过这样的bug:某个场景下连续快速点击,启动了两个Activity。如果这个时候待启动的Activity使用 singleTop模式也是可以避免这个Bug的。
  • SingleTask:栈内复用,如果整个任务栈中存在当前需要启动的Activity,就把该Activity置于栈顶,该Activity上面的所有Activity都清除销毁应用场景:大多数App的主页。对于大部分应用,当我们在主界面点击回退按钮的时候都是退出应用,那么当我们第一次进入主界面之后,主界面位于栈底,以后不管我们打开了多少个Activity,只要我们再次回到主界面,都应该使用将主界面Activity上所有的Activity移除的方式来让主界面Activity处于栈顶,而不是往栈顶新加一个主界面Activity的实例,通过这种方式能够保证退出应用时所有的Activity都能报销毁。
  • SingleInstance:这个Activity在整个系统中只有一个实例,且独享一个任务栈。应用场景:呼叫来电界面。这种模式的使用情况比较罕见,在Launcher中可能使用。或者你确定你需要使Activity只有一个实例。建议谨慎使用。

4、Activity的启动过程

参考我的博客,面试不会问,可以作为了解。

ActivityThread的main()方法是程序的入口。

5、onSaveInstanceState()与onRestoreInstanceState()

1、onSaveInstanceState()

  • 用于保存Activity的状态,存储一些临时数据
  • Activity被覆盖或进入后台、用户改变屏幕方向、由于系统资源不足被kill,会被调用
  • 会在onStop之前被调用,和onPause的顺序不固定的

2、onRestoreInstanceState()

  • 用于恢复保存的临时数据,此方法的Bundle参数也会传递到onCreate方法中,你也可以在onCreate(Bundle savedInstanceState)方法中恢复数据
  • onRestoreInstanceState和onCreate的区别:当onRestoreInstanceState被调用时Bundle参数一定是有值的,不用做为null判断,onCreate的Bundle则可能会为null。官方文档建议在此方法中进行数据恢复。
  • 由于系统资源不足被kill之后又回到此Activity、用户改变屏幕方向重建Activity时,会被调用
  • 会在onStart之后被调用

Q1:如果一个Activity在用户可见时才处理某个广播,不可见时注销掉,那么应该在哪两个生命周期的回调方法去注册和注销BroadcastReceiver呢?

A:Activity 的可见生命周期发生在 onStart调用与 onStop调用之间。在这段时间,用户可以在屏幕上看到 Activity 并与其交互。我们可以在 onStart中注册一个 BroadcastReceiver以监控影响 UI 的变化,并在用户无法再看到您显示的内容时在 onStop中将其取消注册。

Q2:如果有一些数据在Activity跳转时(或者离开时)要保存到数据库,那么你认为是在onPause好还是在onStop执行这个操作好呢?

A:这题的要点和上一题是一样的,onPause较容易被触发,所以我们在做BroadcastReceiver注销时放在onStop要好些。onPause时Activity界面仍然是可见的,如弹出一个Dialog时。但在保存数据时,放在onPause去做可以保证数据存储的有效性,如果放在onStop去做,在某些情况下Activity走完onPause后有可能还没顺利走到onStop就被系统回收了。

Q3:简单说一下Activity A启动Activity B时,两个Activity生命周期的变化。

A:
1、Activity A 的 onPause方法执行。

2、Activity B 的 onCreate、onStart和 onResume方法依次执行。

3、然后,如果 Activity A 在屏幕上不再可见,则其 onStop方法执行。

Q4:如何判断Activity是否在运行?
从Activity A 启动一个线程去进行网络上传操作,在A中设立一个回调函数,当上传操作完成以后,在A的这个回调函数中会弹出一个对话框,用来显示回调信息。可是当上传的过程还在进行的时候,我按下back键,A的activity 被销毁了,可是那个上传的线程还在进行,当那个线程结束后,本来应该在A中弹出一个对话框,可是由于A已经不存在了,系统就会报错提示,“将对话框显示在不存在的页面上”,然后程序就挂掉了。

A:我看到过很多人用Handler来充当上面所提到的“回调函数”,即工作线程完成工作后,通过主线程的Handler处理UI更新,如弹出提示Dialog。可能有些人没有弄明白,Activity都被系统销毁了,工作线程怎么还能调它的变量呢?其实所谓的Activity销毁只是不再受系统的AMS控制,但Activity这个对象的实例还是存在于内存中的,具体什么时候真正把这个对象实例也销毁(回收)了,就要看内存回收机制了,哪怕是这个实例没有可达的引用了也不一定会马上回收。

针对这种用Handler更新UI的情况,我们需要在操作UI前判断一下此Activity是否已被销毁。很多人可能都用过isFinishing()来判断,用多了就会发现好象不太准,因为有时候Activity的生命周期没有按我们预想的来走时(如内存紧张时),会出现判断出错的情况。

所以在判断activity是否被回收时,要在添加一个isDestroyed()的判断。

Q5:Activity如何传递参数,并说说Parcelable和Serializable的区别。(day9)

A:使用Intent的Bundle协带参数,就是我们常用的Intent.putExtra方法。

Serializable是Java自带,Parcelable是Android专用。

Serializalbe会使用反射,序列化和反序列化过程需要大量I/O操作。Parcelable自已实现封送和解封,操作不需要用反射,效率要快很多。

6、Activity如何传递数据以及如何进行数据回传

传递数据使用Intent,传输的数据类型包括:基本类型、Bundle、Serializable对象、Parcelable对象。

数据回传使用Activity提供的startActivityForResult(Intent intent,int requestCode)方法。具体使用方式看《Android面试宝典》

7、已调用多个Activity,如何安全退出

1、使用广播,所有开启的Activity都注册有监听此广播的广播接收者,广播接收者收到此类广播后,直接调用finish方法。

2、在Activity的父类当中实现一个集合,在onCreat方法中调用add()方法把启动的Activity加入到集合当中,当退出app时,需要在父类创建一个killAll()方法,在该方法复制一份Activity的集合,遍历集合并销毁Activity。

3、递归退出。使用startActivityForResult()方法打开新的Activity,需要退出时,自定义一个flag标志,在ActivityonActivityResult()方法中处理这个flag,实现递归关闭。

8、如何应对Activity被系统回收

先说说Activity被系统回收的三种情况:

1、内存不足时,后台运行的一些应用程序会被杀死。

2、横竖屏切换时,Activity会被销毁和回收,然后重建

3、长期运行在后台,有时出于省电的目的,会进行回收。

答案就是使用onSavaInstanceState

二、Service

1、功能

Service是一个专门在后台处理长时间任务的Android组件,它没有UI。

Service 是android的一种机制,当它运行的时候如果是Local Service,那么对应的 Service 是运行在主进程的 UI线程上的。如:onCreate,onStart 这些函数在被系统调用的时候都是在主进程的 main 线程上运行的。

如果是Remote Service,那么对应的 Service 则是运行在独立进程的 UI线程上。因此请不要把 Service 理解成线程,它跟线程半毛钱的关系都没有!

2、两种启动方式

  • startService:只是启动Service,启动它的组件(如Activity)和Service并没有关联,只有当Service调用stopSelf()或者其他组件调用stopService()服务才会终止。
  • bindService:该方法启动Service,其他组件可以通过回调获取Service的代理对象和Service交互,而这两方也进行了绑定,当启动方销毁时,Service也会自动进行unBindService()操作,当发现所有绑定都进行了unBindService()时才会销毁Service。

3、Service与Activity怎么实现通信

1、通过Binder对象。Activity调用bindService (Intent service, ServiceConnection conn, int flags)方法,得到Service对象的一个引用,这样Activity可以直接调用到Service中的方法。

2、通过广播。Service向Activity发送消息,可以使用广播,当然Activity要注册相应的接收器。比如Service要向多个Activity发送同样的消息的话,用这种方法就更好。

4、生命周期

5、Service与Thread

很多时候,你可能会问,为什么要用 Service,而不用 Thread 呢,因为用 Thread 是很方便的,比起 Service 也方便多了,下面就讲解一下。

Thread 的运行是独立于 Activity 的,也就是说当一个 Activity 被 finish 之后,如果你没有主动停止 Thread 或者 Thread 里的 run 方法没有执行完毕的话,Thread 也会一直执行。因此这里会出现一个问题:一方面,当 Activity 被 finish 之后,你不再持有该 Thread 的引用。另一方面,你没有办法在不同的 Activity 中对同一 Thread 进行控制。

因此你可以把 Service 想象成一种消息服务,而你可以在任何有 Context 的地方调用 Context.startService、Context.stopService、Context.bindService,Context.unbindService,来控制它,你也可以在 Service 里注册 BroadcastReceiver,在其他地方通过发送 broadcast 来控制它,当然这些都是 Thread 做不到的。

参考

6、Activity、Intent、Service的关系

一个Activity通常是一个单独的屏幕,每一个Activity都被实现为一个单独的类,这些类都是从Activity基类中继承来的,Activity类显示有视图控件组成的用户接口,并对视图控件的事件做出响应。

Intent的调用是用来进行架构屏幕之间的切换的。Intent是描述应用想要做什么。Intent数据结果中最重要的部分是动作和动作对应的数据,一个动作对应一个动作数据。

Service是运行在后台的代码,不能与用户交互,可以运行在自己的进程,也可以运行在其他应用程序的上下文里。需要通过某一个Activity或其他Context对象来调用。

Activity 跳转到Activity,Activtiy启动Service,Service打开Activity都需要Intent表明跳转的意图,以及传递参数,Intent是这些组件间信号传递者。

7、如何保证 Service 在后台不被 kill

1、提升service优先级

在AndroidManifest.xml文件中对于intent-filter可以通过android:priority = “1000”这个属性设置最高优先级,1000是最高值,如果数字越小则优先级越低,同时适用于广播。

2、提升service进程优先级

Android中的进程是托管的,当系统进程空间紧张的时候,会依照优先级自动进行进程的回收。Android将进程分为6个等级,它们按优先级顺序由高到低依次是:

  • 前台进程
  • 可视进程
  • 次要服务进程
  • 后台进程
  • 内容供应节点
  • 空进程

3、onDestroy方法里重启service

service +broadcast 方式,就是当service走ondestory的时候,发送一个自定义的广播,当收到广播的时候,重新启动service;

Q1:Service的onCreate回调函数可以做耗时的操作吗?

A:不可以。Service的onCreate是在主线程(ActivityThread)中调用的,耗时操作会阻塞UI。

8、Service的混合开启模式

start——bind——unbind——stop

这种模式既保证了服务可以长期运行在后台,又可以让调用者远程调用服务中的方法。

三、BroadcastReceiver

1、功能

BroadcastReceiver 是对发送出来的 广播进行过滤、接收和响应的组件。首先将要发送的消息和用于过滤的信息(Action,Category)装入一个 Intent 对象,然后通过调用 Context.sendBroadcast() 、 sendOrderBroadcast() 方法把 Intent 对象以广播形式发送出去。 广播发送出去后,所有已注册的 BroadcastReceiver 会检查注册时的 IntentFilter 是否与发送的 Intent 相匹配,若匹配则会调用 BroadcastReceiver 的 onReceiver() 方法。

2、两种类型

Broadcast被分为两种:

  • Normal broadcasts 无序广播:会异步的发送给所有的Receiver,接收到广播的顺序是不确定的,有可能是同时。
  • Ordered broadcasts 有序广播:广播会先发送给优先级高(android:priority)的Receiver,而且这个Receiver有权决定是继续发送到下一个Receiver或者是直接终止广播。
  • 本地广播:只在APP内部传播。LoacalBroadcastManager高效的原因主要是因为它内部通过Handler实现,它的sendBroadcast()方法含义并非和我们平时所用的一样,它的它的sendBroadcast()方法其实是通过handler发送一个Message实现的。 既然它内部是通过Handler来实现广播的发送的,那么相比与系统广播通过Binder实现那肯定是更高效了,同时使用Handler来实现,别的应用无法向我们的应用发送该广播,而我们应用内发送的广播也不会离开我们的应用。

3、两种注册方式

  • 静态注册:静态注册即在清单文件中为 BroadcastReceiver 进行注册,使用< receiver >标签声明,并在标签内用 < intent-filter > 标签设置过滤器。这种形式的 BroadcastReceiver 的生命周期伴随着整个应用,如果这种方式处理的是系统广播,那么不管应用是否在运行,该广播接收器都能接收到该广播。
  • 动态注册:动态注册 BroadcastReceiver 是在代码中定义并设置好一个 IntentFilter 对象,然后在需要注册的地方调用registerReceiver()方法,调用unregisterReceiver()方法取消注册,此时就不需要在清单文件中注册 Receiver 了。

4、内部实现机制

1、自定义广播接收者BroadcastReveiver,并复写onReceive()方法

2、通过Binder机制向AMS进行注册

3、广播发送者通过Binder机制向AMS发送广播

4、AMS查找符合相应条件(IntentFilter/Permission等)的BroadcastReceiver,将广播发送到BroadcastReceiver(一般情况是Activity)相应的消息循环队列中

5、消息循环执行拿到此广播,回调BroadcastReceiver中的onReceive()方法

Q1:我们想发送广播只有自己(本进程)能接收到,该如何去做?

A:
1、使用权限,在发送广播时限定有权限(receiverPermission)的接收者才能收到。

2、使用Handler,往主线程的消息池(Message Queue)发送消息,只有主线程的Handler可以分发处理它,广播发送的内容是一个Intent对象,我们可以直接用Message封装一下。在handleMessage时把Intent对象传递给已注册的Receiver。

3、使用LocalBroadcastManager类,其实现方式也是使用Handler,思路和上面也是一样的。

Q2:一个 app 被杀掉进程后,是否还能收到广播?

静态注册的常驻型广播接收器还能接收广播。

四、ContentProvider

  • ContentProvider 为存储和读取数据提供了统一的接口
  • 使用ContentProvider,应用程序可以实现 app 间数据共享
  • Android内置的许多数据都是使用ContentProvider形式,供开发者调用的(如视频,音频,图片,通讯录等)

这个在面试时一般不怎么问,使用过即可,最好写过相关的demo。

1、ContentProvider、ContentResolver、ContentObserver

  • ContentProvider:把一个应用程序的私有数据暴露给其它应用程序
  • ContentResolver:根据ContentProvider提供的URI路径,对数据进行CRUDE操作
  • ContentObserver:

五、Intent

Q1: Intent的分类

A1:
1、显式Intent:可以通过类名来找到相应的组件,在应用中用显式Intent去启动一个组件,通常是因为我们知道这个组件(Activity或者Service)的名字。

2、隐式Intent:不指定具体的组件,但是它会声明将要执行的操作,从而匹配到相应的组件。

Intent中包含的属性包括:

  • Component:要启动的组件名称。这个属性是可选的,但它是显式Intent的一个重要属性,设置了这个属性后,该Intent只能被传递给由Component定义的组件。
  • Action:表明执行操作的字符串。它会影响Intent的其余信息,比如Data、Extras。用户可以自定义这个属性,也可以使用系统中已经有的Action值。
  • Data:它是待操作数据的引用URI或者数据MIME类型的URI,它的值通常与Intent的Action有关联。比如,如果设置Action的值为ACTION_EDIT,那么Data的值就必须包含被编辑文档的URI。当我们创建Intent的时候,设置MIME类型非常重要。例如,一个可以显示图片的Activity可能不能播放音频,图片和音频的URI非常类似,如果我们设置了MIME类型,可以帮助系统找到最合适的组件接受Intent。有时候,MIME类型也可以从URI判断出来,例如当Data是一个包含content:字符串的URI时候,可以明确的知道,待处理的数据存在设备中,而且由ContentProvider控制。
  • Category:这个属性是对处理该Intent组件信息的补充。它是一个ArraySet类型的容器,所以可以向里面添加任意数量的补充信息,同时,Intent没有设置这个属性不会影响解析组件信息。
  • Extras:以key-value键值对的形式来存储组件执行操作过程中需要的额外信息,可以调用putExtra()方法来设置该属性,这个方法接受两个参数,一个是key,一个是value。
  • Flags:这个属性可以指示系统如何启动一个Activity,以及启动之后如何处理。例如Activity属于哪一个task(参考Activity的四种启动方式)。

Q2: Intent的使用方法,可以传递哪些数据类型?

A2:通过查询Intent/Bundle的API文档,我们可以获知,Intent/Bundle支持传递基本类型的数据和基本类型的数组数据,以及String类型的数据和String类型的数组数据。而对于其它类型的数据貌似无能为力,其实不然,我们可以在Intent/Bundle的API中看到Intent/Bundle还可以传递Parcelable和Serializable类型的数据,以及它们的数组/列表数据。

所以要让非基本类型和非String/CharSequence类型的数据通过Intent/Bundle来进行传输,我们就需要在数据类型中实现Parcelable接口或是Serializable接口。

Q3:介绍一下Intent、IntentFilter

A3:IntentFilter通常是定义在AndroidManifest.xml文件中,也可以动态设置,通常是用来声明组件想要接受哪种Intent。例如,你如果为一个Activity设置了IntentFilter,你就可以在应用内或者其他应用中,用特定的隐式Intent来启动这个Activity,如果没有为Activity设置IntentFilter,那么你就只能通过显示Intent来启动这个Activity。

六、Fragment

1、为什么被称为第五大组件

有人说View是第五大组件,但是View没有生命周期,而Fragment有生命周期,有了周期就可以灵活的进行处理。

但是他并不是完全独立的,虽然有自己的生命周期,但是还是要依附于Activity,同时被加载到Activity当中。

2、Fragment加载到Activity的两种方式

1、静态加载

在XML文件中添加Fragment到Activity的布局文件中。

2、动态加载

使用FragmentManager管理所有要启动的Fragment,
并用FragmentTransaction来添加或替换Fragment,
并用容器资源来作为标志位,设置Fragment将要显示到Activity当中的位置,
最后调用commit方法来完成整个步骤。

3、FragmentPagerAdapter与FragmentStatePagerAdapter的区别

FragmentPagerAdapter适用于页面较少的情况,FragmentStatePagerAdapter则适用于页面较多的情况。

4、Fragment的生命周期

5、Fragment之间的通信

Fragment与Activity

在Fragment中调用Activity中的方法:getActivity()

Fragment与Fragment

在Fragment中调用Fragment中的方法:先调用getActivity(),然后调用findFragmentById()

Activity与Fragment

若activity中包含自己管理的Fragment的引用,则可以直接访问Fragment的公有方法。若没有,则通过getFragmentManager().findFragmentById()获得相应的实例,然后进行操作。

6、Fragment管理器:FragmentManager

  • add():将Fragment实例添加到Activity的最上层
  • replace():替换Fragment的实例,而不是直接将实例添加到最上层
  • remove():

7、Fragment的切换

开发的时候,有时候Fragment的切换,是不需要对原先的Fragment进行重新加载的。而Fragment的界面是既可以进行重新加载,也可以不进行重新加载。
每次重新加载的话,我们是使用的replace来进行fragment的替换的。
如果,不对fragment的数据进行重新加载的话,那么就用add来添加fragment,显示的时候用show,切换的时候用hide掉当前的,show切换的目标。

8、Fragment事务

什么是“事务”,一直不明白,不懂。看了《Android面试宝典》,上面说:事务指的就是一种原子性、不可拆分的操作。所谓的Fragment事务,就是对Fragment进行添加、移除、替换或执行其它动作,提交给Activity的每一个变化,这就是Fragment事务。

七、Context

参考文章:Context都没弄明白,还怎么做Android开发?

1、为什么不能 new Activity()

Android程序不能像Java程序一样,随便创建个类,写个Main()方法就能跑了。Android应用模型是基于组件的应用设计模式,组件的运行要有一个完整的Android工程环境,在这个环境下,Activity、Service、BroadcastReceiver等系统组件才能正常工作,而这些组件并不能采用普通的Java对象创建方式,new一下就能创建实例了,而是要有它们各自的上下文环境,也就是Context。

2、Context的继承关系


Context的两个子类分工明确,其中ContextImpl是Context的具体实现类,ContextWrapper是Context的包装类。

Activity、Application、Service虽然都继承自ContextWrapper,但Context的具体实现类是ContextImpl,所以它们的初始化过程都会创建ContextImpl对象有ContextImpl实现Context中的方法。

BroadcastReceiver、ContentProvider并不是Context的子类,它们所持有的Context都是其它地方传过来的,所以不计入Context总数。

一个应用程序中的:Context数量 = Activity数量 + Service数量 + 1(一个Application)

3、生动形象的理解Context

一个Android应用程序,可以理解为一部电影或者一部电视剧,Activity,Service,Broadcast Receiver,Content Provider这四大组件就好比是这部戏里的四个主角。他们是由剧组(系统)一开始就定好了的,整部戏就是由这四位主演领衔担纲的,所以这四位主角并不是大街上随随便便拉个人(new 一个对象)都能演的。有了演员当然也得有摄像机拍摄啊,他们必须通过镜头(Context)才能将戏传递给观众,这也就正对应说四大组件(四位主角)必须工作在Context环境下(摄像机镜头)。那Button,TextView,LinearLayout这些控件呢,就好比是这部戏里的配角或者说群众演员,他们显然没有这么重用,随便一个路人甲路人乙都能演(可以new一个对象),但是他们也必须要面对镜头(工作在Context环境下),所以Button mButton=new Button(Context)是可以的。

4、Context的作用域

从上图我们可以发现Activity所持有的Context的作用域最广,无所不能。因为Activity继承自ContextThemeWrapper,而Application和Service继承自ContextWrapper,很显然ContextThemeWrapper在ContextWrapper的基础上又做了一些操作使得Activity变得更强大。

而且我们可以总结,凡是涉及UI的操作都要在Activity的环境中来执行。

5、Application Context 和 Activity Context 异同

1、Application-Context的生命周期是整个应用,所以,对于它的使用必须慎重,大部分情况下都要避免使用它,因为它会导致内存泄露的问题。

如果我们现在在一个Activity中引入一个Application-Context,那么,当我们这个Activity关闭的时候,这个Application-Context是不会消失的,因为它的生命周期要比我们的Activity长,如果只是一些用来计算的数据还好,但是如果这个Context与我们的Activity的创建有关,或者与我们在Activity要销毁的资源比如图片资源有关,那么,问题就大条了!因为我们的Activity或图片就不能正常销毁,因为它与Application-Context相关联,如果不能正常的释放掉与它们相关的内存,就会出现所谓的内存泄露的问题。这种问题有时候是非常隐晦的,以至于我们根本无法察觉到,所以我们必须遵守相关的使用原则。

2、Activity Context的生命周期是和得到它的引用的Activity一样长,如果这个Activity结束了,那么,这个Context也会得到释放。它并不像我们上面的Application-Context需要特意去获得,可以在一个Activity中使用this就可以获得当前Activity的Context。

八、HandlerThread

Android实现多线程的几种方式

  • Thread、Runnable
  • Handler
  • AsyncTask
  • HandlerThread
  • IntentService
  • 线程池

1、介绍

HandlerThread继承于Thread,所以它本质就是个Thread(thread+handler+looper)。与普通Thread的差别就在于,它不仅建立了一个线程,并且创立了消息队列,有自己的looper,可以让我们在自己的线程中分发和处理消息,并对外提供自己这个Looper对象的get方法。

传统的Thread在任务结束后,线程就会被自动销毁,倘若我们又要创建一个新的任务,就不得不重新创建Thread,这样的不断的创建和销毁操作是非常消耗系统资源的。

HandlerThread自带Looper使他可以通过消息队列,来重复使用当前线程,当有耗时任务时,就会执行该任务,没有任务时,循环线程就会处于阻塞状态,等待新的任务,节省系统资源开销。这是它的优点也是缺点,每一个任务都将以队列的方式逐个被执行到,一旦队列中有某个任务执行时间过长,那么就会导致后续的任务都会被延迟处理。

与线程池注重并发不一样,Handler是一个串行队列,因为它背后只有一个线程,当然相对于线程池,它更安全了,因为它不涉及线程间数据的交互问题。

2、使用方法

自己写demo。

九、IntentService

1、概念

IntentService是继承并处理异步请求的一个类,在IntentService内有一个工作线程来处理耗时操作,启动IntentService的方式和启动传统的Service一样。

1、当任务执行完后,IntentService会自动停止,而不需要我们手动去控制或stopSelf()。

2、另外,可以启动IntentService多次,而每一个耗时操作会以工作队列的方式在IntentService的onHandlerIntent回调方法中执行,并且,每次只会执行一个工作线程,执行完第一个再执行第二个。所以总结优点:

1、一方面不需要自己去new Thread

2、另一方面不需要考虑在什么时候关闭该Service

2、使用方法

自己写demo。

十、AsyncTask

参考我的博客

十一、AIDL

没用过的话就不会问。
参考我的博客

十二、View

1、绘制流程

参考我的博客

2、事件分发

参考我的博客

3、listView相关

适配器模式

recycleBin机制

recycleBin机制就是调用addScrapView(View scrap,int position)方法,将即将划出屏幕的View进行缓存,这个方法接受的view参数就是刚刚被滑出屏幕的View。这就解释了为什么ListView有几千条数据也不会出现OOM。

优化

  • convertView重用:getView方法中有一个convertView参数,它做的就是缓存的作用,如果缓存为空,才创建相应的View,如果不为空(已经有ItemView被移除屏幕),就走else。 RecyclerBin具备回收与复用机制,它负责的其实就是convertView的复用。
  • ViewHolder:现在用一个ViewHolder,第一个创建convertView时把这些子View通过findViewById实例化并保存在ViewHolder对象中,以后就省却了findViewById的调用。

与RecyclerView区别

  • RecyclerView 自带 ViewHolder;而 ListView 则需要自定义。
  • RecyclerView 支持水平和垂直滚动;而 ListView 只支持垂直滚动。
  • RecyclerView 提供默认的列表项动画实现,例如:添加、删除和移动列表项动画。
  • ListView通过AdapterView.OnItemClickListener接口来监听点击事件。而* RecyclerView则通过RecyclerView.OnItemTouchListener接口来监听触摸事件。它虽然增加了实现的难度,但是却给予开发人员拦截触摸事件更多的控制权限。
  • ListView可以设置选择模式,并添加MultiChoiceModeListener;而 RecyclerView 没有该功能。

4、自定义View

1、绕不开的几个类

1)Configuration

用来描述设备的配置信息:输入模式、屏幕大小、屏幕方向等。

Configuration config = getResource().getConfiguration();

2)ViewConfiguration

与ViewConfigureation没有什么关系,也不是继承关系。它提供了一些自定义控件用到的标准常量,比如UI超时(按住状态转变为长按状态需要的时间)、尺寸大小、滑动距离、敏感度等等。

ViewConfiguration viewConfig = ViewConfiguration.get(context);
//获取touchSlop(滑动最少多少才算滑动)
int touchSlop = viewConfig.getScaledTouchSlop();

3)GestureDetector

主要作用是简化Touch操作。

4)VelocityTracker

用于跟踪触摸屏事件的速度。使用步骤:

1)开始追踪

mVelocityTracker.addMovment(event);

2)获取速度

mVelocityTracker.gerXVelocity(); //获取X方向的速度
mVelocityTracker.gerYVelocity(); //获取Y方向的速度

3)停止追踪

mVelocityTracker.recycle();
mVelocityTracker = null;

5)Scroller

scroll的本质:滑动的本质是滑动view的内容。

6)ViewDragHelper

用于简化View的拖拽的相关操作。

2、常见实现方式

1)继承自View

这里要注意处理Measure过程中,对”wrap_content“属性的处理。具体什么问题可以看我之前写的一篇文章,如果不想这么麻烦,就用第二种方法,除非不得已。

2)继承自系统已有的View

3)继承自ViewGroup

这里要亲自处理Layout方法,因为不同的ViewGroup(比如线性布局、相对布局),布局方式是不一样的。同样如果不想麻烦,就用第四种方法,除非不得已。

4)继承自系统已有的ViewGroup

5、屏幕适配

**px: **pixel,即像素,1px代表屏幕上的一个物理的像素点。但px单位不被建议使用。因为同样像素大小的图片在不同手机显示的实际大小可能不同。要用到px的情况是需要画1像素表格线或阴影线的时候,如果用其他单位画则会显得模糊。

**dip (dp): **device independent pixel。dp (dip)是最常用也是最难理解的尺寸单位。与像素密度密切相关。Android系统定义了四种像素密度:低(120dpi)、中(160dpi)、高(240dpi)和超高(320dpi),它们对应的dp到px的系数分别为0.75、1、1.5和2,这个系数乘以dp长度就是像素数。例如界面上有一个长度为“80dp”的图片,那么它在240dpi的手机上实际显示为80x1.5=120px,在320dpi的手机上实际显示为80x2=160px。如果你拿这两部手机放在一起对比,会发现这个图片的物理尺寸“差不多”,这就是使用dp作为单位的效果。

**sp: **Scale-independent Pixel,即与缩放无关的抽象像素。sp和dp很类似但唯一的区别是,Android系统允许用户自定义文字尺寸大小(小、正常、大、超大等等),当文字尺寸是“正常”时,1sp=1dp=0.00625英寸,而当文字尺寸是“大”或“超大”时,1sp>1dp=0.00625英寸。类似我们在windows里调整字体尺寸以后的效果——窗口大小不变,只有文字大小改变。

6、动画

Android 起初有两种动画:Frame Animation(逐帧动画) Tween Animation(补间动画),但是在用的时候发现这两种动画有时候并不能满足我们的一些需要,所以Google在Androi3.0的时候推出了(Property Animation)属性动画,至于为什么前边的两种动画不能满足我们的需要,请往下看:

1、Frame Animation(逐帧动画)

逐帧动画就是UI设计多张图片组成一个动画,然后将它们组合链接起来进行动画播放。该方式类似于早期电影的制作原理:具体实现方式就不多说了,你只需要让你们的UI出多张图片,然后你顺序的组合就可以(前提是UI给您做图)

2、Tween Animation(补间动画)

Tween Animation:是对某个View进行一系列的动画的操作,包括淡入淡出(Alpha),缩放(Scale),平移(Translate),旋转(Rotate)四种模式

Tween Animation(补间动画)的一些缺点:

Tween Animation(补间动画)只是针对于View,超脱了View就无法操作了,这句话的意思是:假如我们需要对一个Button,ImageView,LinearLayout或者是其他的继承自View的各种组件进行动画的操作时,Tween Animation是可以帮我们完成我们需要完成的功能的,但是如果我们需要用到对一个非View的对象进行动画操作的话,那么补间动画就没办法实现了。举个例子:比如我们有一个自定义的View,在这个View中有一个Point对象用于管理坐标,然后在onDraw()方法中的坐标就是根据该Pointde坐标值进行绘制的。也就是说,如果我们可以对Point对象进行动画操作,那么整个自定义的View,那么整个自继承View的当前类就都有了动画,但是我们的目的是不想让View有动画,只是对动画中的Point坐标产生动画,这样补间动画就不能满足了。

Tween Animation动画有四种动画操作(移动,缩放,旋转,淡入淡出),但是我们现在有个需求就是将当前View的背景色进行改变呢?抱歉Tween Animation是不能帮助我们实现的。

Tween Animation动画只是改变View的显示效果而已,但是不会真正的去改变View的属性,举个例子:我们现在屏幕的顶部有一个小球,然后通过补间动画让他移动到右下角,然后我们给这个小球添加了点击事件,希望位置移动到右下角的时候点击小球能的放大小球。但是点击事件是绝对不会触发的,原因是补间动画只是将该小球绘制到了屏幕的右下角,实际这个小球还是停在屏幕的顶部,所以你在右下角点击是没有任何反应的。
Property Animatior(属性动画)

3、属性动画是Android3.0之后引进的,它更改的是动画的实际属性,在Tween Animation(补间动画)中,其改变的是View的绘制效果,真正的View的属性是改变不了的,比如你将你的Button位置移动之后你再次点击Button是没有任何点击效果的,或者是你如何缩放你的Button大小,缩放后的有效的点击区域还是只有你当初初始的Button的大小的点击区域,其位置和大小的属性并没有改变。而在Property Animator(属性动画)中,改变的是动画的实际属性,如Button的缩放,Button的位置和大小属性值都会发生改变。而且Property Animation不止可以应用于View,还可以应用于任何对象,Property Animation只是表示一个值在一段时间内的改变,当值改变时要做什么事情完全是你自己决定的。

十三、数据存储

sqlite

自己写demo。

主要类为:SqliteOpenHelper、SqliteDatabase、Cursor

SharedPreference

除SQLite数据库外,另一种常用的数据存储方式,其本质就是一个xml文件,常用于存储较简单的参数设置。

File

即常说的文件(I/O)存储方法,常用语存储大数量的数据,但是缺点是更新数据将是一件困难的事情。

数据量非常少,可以使用文件。
如果涉及到查询等操作,并且数据量大,则应该使用数据库。

十四、Android项目构建

1、构建流程

2、Git

工作流

3、Gradle

4、Proguard

  • 压缩
  • 优化
  • 混淆
  • 预检测

十三、框架层源码

都参考我的博客。

1、AMS、WMS

https://thinkchao.github.io/2017/07/17/android-10/

2、Handler

https://thinkchao.github.io/2017/06/25/android-1/

3、AsyncTask

https://thinkchao.github.io/2017/07/23/android-12/

十五、第三方框架

  • 1、了解与使用
  • 2、代码逻辑
  • 3、类间关系
  • 4、细节问题

1、OkHttp

了解和使用

了解

使用

1、创建OkHttpClient对象

OkHttpClient okHttp = new OkHttpClient();

全局对象,所有的Http请求都共用这一个,和Volley的第一步一样。
2、创建request对象

Request request = new Request.Builder().url(……).build();

3、创建一个Call对象
调用newcall(request)).execute();,同步获取数据,阻塞当前进程。

或调用newcall(request).enqueue();,异步获取数据,开启一个子线程。请求成功回调OnReponse()方法,请求失败回调OnFailure()方法。

2、Volley

http://extremej.itscoder.com/volley_source/#

了解和使用

了解

  • Json,图像等异步下载
  • 网络请求的排序(scheduling)
  • 网络请求的优先级处理
  • 缓存
  • 多级别取消请求
  • 和 Activity 的生命周期联动(Activity 结束时同时取消所有网络请求)

使用

http://blog.csdn.net/guolin_blog/article/details/17482095
1、创建 RequestQueue

RequestQueue mRequestQueue = Volley.newRequestQueue(this);

2、接下来为了要发出一条HTTP请求,我们还需要创建一个StringRequest对象

StringRequest stringRequest = new StringRequest(………………);

StringRequest的构造函数需要传入三个参数:
第一个参数就是目标服务器的URL地址;
第二个参数是服务器响应成功的回调;
第三个参数是服务器响应失败的回调。
3、最后,将这个StringRequest对象添加到RequestQueue里面就可以了

mQueue.add(stringRequest);  

代码逻辑

  • 首先会把这个请求加入到一个当前请求的队列中,在这个队列中的请求都表示被 Volley 处理过。在后面结束或者取消请求的时候需要用到
  • 接着判断是否需要缓存,需要走左边,不需要走右边直接加入 网络请求队列中
  • 左边是一个等待队列,用于判断当前是否有相同的请求正在进行,如果有则相同的请求都放到这个等待队列中不马上执行。如果没有则向下放到缓存队列中,同时在等待队列中标记该请求
  • 如果请求被加入到等待队列中了,当正在进行的那个请求被执行完毕后,会将等待队列中重复的请求都放到缓存队列中,也就是直接使用缓存数据而不请求网络了。

3、Retrofit

http://www.jianshu.com/p/45cb536be2f4

4、Glide

5、RxJava

http://gank.io/post/560e15be2dca930e00da1083

了解与使用

了解

RxJava 的本质可以压缩为异步这一个词。说到根上,它就是一个实现异步操作的库,而别的定语都是基于这之上的。

同样是做异步,为什么人们用它,而不用现成的 AsyncTask / Handler / XXX / ...?

异步操作很关键的一点是程序的简洁性,因为在调度过程比较复杂的情况下,异步代码经常会既难写也难被读懂。 Android 创造的 AsyncTask 和Handler ,其实都是为了让异步代码更加简洁。RxJava 的优势也是简洁,但它的简洁的与众不同之处在于,随着程序逻辑变得越来越复杂,它依然能够保持简洁。

十六、Android热门前沿知识

1、MVC

  • M:业务逻辑处理
  • V:处理数据显示的部分
  • C:Activity处理用户交互问题,起到了非常重要的桥梁作用,所以说它里面的业务逻辑也过于冗余。

采用MVC的好处就是便于UI界面的显示和业务逻辑的分离,具体来说:Model层主要用于业务逻辑的处理,比如数据库、网络操作;View层主要用于视图的显示,也就是xml里的内容;Ctroller层用于处理用户和界面的交互,通过读取VIew层的数据,然后将数据交给界面来显示。

优点:

1、耦合性低

2、可扩展性好

3、模块职责划分明确

缺点:

View层对应的是Android中的xml布局文件,而布局文件并不像java web那么强大,能做的处理很有限,所以很多View层该做的操作就交给了Activity,它里面负责了很多操作UI的业务逻辑,而这些功能明显是应该交给View层来做的,这就导致了Activity的冗余。

2、MVP

  • M:依然是处理业务逻辑和实体模型
  • V:对应Activity,负责View的绘制与用户交互
  • P:负责完成View与Model间的交互

优点:

耦合性更低,与MVC最大的区别就是Model层和View层不再有交互,而是通过Presenter层交互。如图:

需要注意的一点是,Presenter层的实现必须通过接口定义,这个需要在实战中去了解。

3、MVVM

理论上比MVP更先进,但实战用的很少。

  • Model:实体模型,主要提供数据接口供ViewModel层调用
  • View:对应于Activity和xml,负责View的绘制以及用户交互
  • ViewModel:负责完成View与Model间的交互,负责业务逻辑

4、插件化

插件化来由

65536/64K ,当方法达到65536个的时候,就不能再创建新的方法。

所谓插件化,就是让我们的应用不必再像原来一样把所有的内容都放在一个apk中,可以把一些功能和逻辑单独抽出来放在插件apk中,然后主apk做到按需调用,这样的好处是一来可以减少主apk的体积,让应用更轻便,二来可以做到热插拔,更加动态化。

要解决的问题

1、动态加载apk

2、代码加载

3、资源加载

5、热更新

6、进程保活

概念

想要我们的进程在内存中永远存在,杀不死,就算杀死了,也能让它马上重启。

进程的优先级

1、前台进程:前台进程是目前正在屏幕上显示的进程和一些系统进程,也就是和用户正在交互的进程。例如,我正在使用qq跟别人聊天,在我的android手机上这个进程就应该是前台进程。

2、可见进程:可见进程指部分程序界面能够被用户看见,却不在前台与用户交互的进程。例如,我们在一个界面上弹出一个对话框(该对话框是一个新的Activity),那么在对话框后面的原界面是可见的,但是并没有与用户进行交互,那么原界面就是可见进程。

3、服务进程:服务进程是通过 startService() 方法启动的进程,但不属于前台进程和可见进程。例如,在后台播放音乐或者在后台下载就是服务进程。

4、后台进程:后台进程指的是目前对用户不可见的进程。例如我正在使用qq和别人聊天,这个时候qq是前台进程,但是当我点击Home键让qq界面消失的时候,这个时候它就转换成了后台进程。当内存不够的时候,可能会将后台进程回收。

5、空进程:空进程指的是在这些进程内部,没有任何东西在运行。保留这种进程的的唯一目的是用作缓存,以缩短该应用下次在其中运行组件所需的启动时间。

前三个进程,内存够用的情况下,是不会回收的,而后两个系统可以随意的进行回收。

进程回收策略

  • Low memory Killer
  • OOM_ODJ

进程保活方案

1、利用系统广播拉活

2、利用系统Service机制拉活

3、利用Native进程拉活

7、Kotlin

概念

  • 是一种基于JVM的编程语言
  • 是对Java的一种扩展,基于Java,但比Java更简单
  • 支持函数式编程
  • Kotlin类与Java类能相互调用

8、ReactNative、WebView

目前主流的APP开发方式主要以下四种:

Native App

就是所说的原生开发,Native Code编程,代码编译之后以2进制或者字节码的形式运行在OS上,直接调用OS的API。

Web App

以HTML+JS+CSS等WEB技术编程,代码运行在浏览器中,通过浏览器来调用API(取决于HTML5未来的支持能力)。

Hybrid App

就是混合开发。部分代码以WEB技术编程,部分代码由某些Native Container承担(例如PhonGAP插件,BAE插件),其目的是在HTML5尚未完全支持Device API和Network API的目前阶段,承担这部分职责。常用的技术就是webview+html5。

Hybrid模式下,由原生提供统一的API给JS调用,实际的主要逻辑有Html和JS来完成,而由于最终是放在webview中显示的,所以只需要写一套代码即可,达到跨平台效果,另外也可以直接在浏览器中调试,

ReactNative

Facebook发起的开源的一套新的APP开发方案,使用JS+部分原生语法来实现功能。不同于H5,也不同于原生,更像是用JS写出原生应用。

对比

9、Android8.0

十七、Dalvik虚拟机及系统服务

Dalvik架构

Binder机制

Zygote、SystemServer

十八、优化

1、绘制优化

就是针对meaure、layout、draw三个过程中可能遇到的问题进行优化。

工具

  • Hierarchy Viewer
  • Profile GPU Rendering
  • Trace View
  • Systrace UI

布局优化

OverDraw

Layout太复杂。

频繁重新渲染

UI卡顿原因

  • 人为在UI线程中做轻微耗时操作,导致UI线程卡顿
  • View的过度绘制,layout布局过于复杂,无法在16ms内完成渲染,Android系统1s要渲染60次。
  • 同一时间动画执行的次数过多,导致CPU和GPU负载过重
  • View频繁触发measure、layout,导致频繁重新渲染
  • 内存频繁GC过多,导致暂时阻塞渲染操作
  • 冗余资源和逻辑导致执行缓慢

2、内存优化

工具

  • Memory Monitor
  • Heap Viewer
  • Allocation Tracker

3、安装包大小优化

4、耗电优化

5、异常

1)ANR(application not responding)

产生原因

这个异常经常会遇到,它会弹出一个对话框,让用户选择是等待还是关闭程序。

在Activity最长的执行时间是5s,Service中是20s,BroadcastReceiver中,最长的执行时间是10s,如果在这个事件内没有完成,系统就会弹出这个对话框,主要是由WMS和AMS监视的。所以可见,产生这个异常的原因就是在主线程中做了耗时操作。

解决

1、使用AsyncTask处理耗时IO操作

2、使用handler处理工作线程的耗时任务

3、Activity的onCreate()和onResume()回调中尽量避免耗时的代码

2)OOM(out of memory)

产生原因

当前占用的内存加上我们申请的内存超过了Dalvik虚拟机最大的内存限制就会抛出该异常。

关于内存异常容易混淆的概念

  • 内存溢出:就是oom异常。这个问题最严重。
  • 内存抖动:短时间内大量的对象被创建,然后又被马上释放,这个问题相较那两者较轻。
  • 内存泄漏:内存中的某些对象,已经无法被其它对象引用到了,但是它还引用到其它没有被回收的对象,导致GC无法产生作用,累积到一定程度,就会造成内存溢出。

3)内存泄漏

《Android面试宝典》P115

  • 单例:
  • 避免在非静态内部类中创建静态实例:
  • 及时关闭不适用的资源:
  • 有效利用已有对象:例如构造Adapter时没有使用缓存的convertView对象。

十九、其它问题

1、自我介绍

2、我的优缺点

优点

缺点

3、还有什么问题想要问吗

技术面

1、刚才在面试的时候,如果有没回答上来的,就可以问面试官。

2、您觉得我还要加强哪方面的能力?

诸如此类的问题吧

hr面

4、公司了解

规模

产品

为什么想来我们公司

5、项目或论文相关

这个还是挺重要的,根据个人情况,要好好准备,一定要把一些细节要琢磨透。

6、在校期间都做过什么?

如果你是研究生,面试官就会询问你的研究领域,这个也要准备准备,一定要表达清晰。

7、平时写什么代码写的比较多,除了看框架代码,自己有去写过什么吗?

8、为什么没有去实习?

如果你没出去实习过,有可能会被问到这个问题。

推荐阅读更多精彩内容