Android面试基础系列一——Android
此系列文章是我在毕业求职期间,对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、为什么没有去实习?
如果你没出去实习过,有可能会被问到这个问题。