Android面试小结

来源:互联网 发布:知乎阿波罗和雅典娜 编辑:程序博客网 时间:2024/06/14 16:09

小弟不才从一个做ROM的公司裸辞,一心想扑倒互联网的怀抱,可是天不由人面了好久,很是惨烈,没有做APP的项目经验真的头大啊,也算是给自己一个成长的机会吧,希望自己能够如愿找到称心的工作,最近一直在听一首歌分享给大家《欢乐颂》,人生低潮的时候可以听,很鼓舞士气,哈哈哈哈哈哈哈

好了,下面把自己面试一些总结写出来给大家参考,还有个人的一些答案,如有错误,请帮忙指正,感谢!


关于四大组件:

1.Broadcast的启动方式 (广播的启动是和接收注册,发送在一起的也就是说注册了一个广播后,写好他的接受方式,就可以接收到这个)

2.有几种广播(本地、全局、无序、有序、系统)
3.广播的发送方式,有什么不同,差异点  以及用处(有序广播按照优先级进行接受、并且是在第一个处理完成以后才会向后传递,也可以进行再这个过程中进行广播的修改和终止动作  无序广播,直接发给所有人)sendOrderedBroadcast abortBroadcast setResultExtras(Bundle)  getResultExtras(true)
4.广播接受注册方式,有什么区别(静态:AndroidManifast.xml中进行注册 动态: 创建一个filter 在创建一个内部类Receiver的对象,然后registerReceiver(receiver,filter)就可以了。静态是长驻广播,可以在任何时候接受广播,动态只能在app启动以后才能接受广播)
5.app没有启动,能收到广播么  (静态可以)
6.本地广播的实现原理:
BroadcastReceiver的通信是基于Binder机制,而LocalBroadcastManager的核心是基于Handler机制(获取了主线程的Loop来创建了一个对象Handler)
LocalBroadcastManager使用单例模式对象,初始化时会在内部初始化一个Handler对象用来接受广播。注册广播时,会将自定义的BroadcastReceiver对象和IntentFilter对象保存到HashMap中。发送广播时,则根据IntentFilter的Action值从已保存的HashMap找到对应接受者,并发送Handler消息去执行receiver的onReceive方法
mReceivers:一个HashMap,用来保存已注册的自定义的receiver和intentFilter
mActions :一个HashMap,保存action和ReceiverRecord列表的键值对
mPendingBroadcasts :一个ArrayList,用来保存待通知的receiver对象

1.service的启动方式    (starservice bindservice)
2.service的生命周期    (bind随context的生命周期,start必须显示stop)
3.service bind启动的最后还需要desdroy吗 start启动的呢? 一般都什么应用在什么情况下  (bind的当unbind后系统就会进行destory的操作,start的需要显示stop后才可以,一般在需要和activity进行数据交互的时候就需要bind 这样才可以在onbind中进行数据的传递)
4.讲一下intentservice  (用来处理异步的请求。IntentService在执行onCreate的方法的时候,其实开了一个线程HandlerThread,并获得了当前线程队列管理的looper,并且在onStart的时候,把消息置入了消息队列,在消息被handler接受并且回调的时候,执行了onHandlerIntent方法。 请求是在工作线程(仅仅启动一个工作线程)处理的,会把所有请求放到一个消息队列中,当消息队列中的所有消息都处理好之后会自动销毁。需要重写onHandleIntent)
5.bindSerivce: onBind()函数 -- 传递MyBinder对象---->onServiceConnected()--> 通过传递的Binder对象获取刚刚和Binder对象对应的BindService 对象-->调用Service中定义的方法
6.如何让一个seivice长驻:
在内存低的时候系统会自动清理进程,这时候后台service可能会被杀掉。可以在onStartCommand中返回START_STICKY,这样系统有足够多资源的时候,就会重新开启service。

1.activity的启动过程 从android源码端分析一下启动的流程以及调用的方法  (已熟练)
2.activity的生命周期 
3.activity的四种启动模式 singletask中被唤醒的activity会先调用onnewintent->onrestart->onstart->onreseum
4.activity之间的通信  intent传递bundle携带信息 


1.contentProvider --- 目前还没有人问过  有一家创业公司问过,很简单不做过多阐述
  提供数据存储的统一接口,通过contentResolver来获取数据
  URI 通过URI来确认从谁、要什么数据。
  Android为常见的一些数据提供了默认的ContentProvider(包括音频、视频、图片和通讯录等)



ANR的原因 由于什么导致的 案例是什么 怎么解决的(分析log和trace,确定是由什么控件引起的,找到对应pid)
原因:
1.应用进程自身引起的,比如:主线程阻塞、挂起、死循环,执行耗时操作等;
2.其他进程引起的,比如:其他进程CPU占用率过高,导致当前应用进程无法抢占到CPU时间片。常见的问题如文件读写频繁,io进程CPU占用率过高,导致当前应用出现ANR;
解决办法:
1.主线程需要做耗时操作时,比如网络访问、数据库操作及位图变换等,必须启动一子线程处理,并利用handler来更新UI;
2.子线程尽量使用Android提供的API,比如HandlerThread,AsyncTask,AsyncQueryHandler等,这些API都提供了对于线程的系统级管理。如果应用直接使用Thread实现的话,则需要对这些子线程进行显式管理,比如线程池及线程周期的控制,以防止系统资源和内存泄漏


binder通信:这个一定要会的   这里只说一个点吧

client端是怎么确定我要使用哪一个service中的方法的呢? 

client端在查询servermanager 获得所需的的BpBinder后,BpRefBase负责管理当前获得的BpBinder实例



looper Handler messagequeue  推荐一个博客(http://blog.csdn.net/ldld1717/article/details/75366813?locationNum=1&fps=1)

Looper.prepare()方法不能被调用两次,同时也保证了一个线程中只有一个Looper实例,同时一个Looper实例也只有一个MessageQueue

loop()方法,不断从MessageQueue中去取消息,交给消息的target属性的dispatchMessage去处理
Handler的构造方法,会首先得到当前线程中保存的Looper实例,进而与Looper实例中的MessageQueue想关联
Handler的sendMessage方法,会给msg的target赋值为handler自身,然后加入MessageQueue中
主线程中的Looper.loop (epoll+pipe):这基本是一个类似生产者消费者的模型,简单说如果在主线程的MessageQueue没有消息时,就会阻塞在loop的queue.next()方法里,这时候主线程会释放CPU资源进入休眠状态,直到有下个消息进来时候就会唤醒主线程,在2.2 版本以前,这套机制是用我们熟悉的线程的wait和notify 来实现的,之后的版本涉及到Linux pipe/epoll机制,通过往pipe管道写端写入数据来唤醒主线程工作。原理类似于I/O,读写是堵塞的,不占用CPU资源


内存泄露的原因 以及解决方案 
原因:持有对象的强引用,且没有及时释放,进而造成内存单元一直被占用,浪费空间,甚至可能造成内存溢出!
解决:1.活在Activity生命周期之外的线程。没有清空对Activity的强引用。2.全局进程(process-global)的static变量。这个无视应用的状态,持有Activity的强引用的怪物。


场景:1.Handler 引起的内存泄漏  message消息没有处理掉activity就进到后台了,但是此时handler有外部activity的引用,导致该activity不能被释放 解决:使用静态内部类创建Handler(没有activity强引用)
2.单例模式引起的内存泄漏  单例的生命周期是和app的生命周期一致,构建该单例的一个实例时需要传入一个Context,此时传入的Context就非常关键,如果此时传入的是Activity,由于Context会被创建的实例一直持有,当Activity进入后台或者开启设置里面的不保留活动时,Activity会被销毁,但是单例持有它的Context引用,Activity又没法销毁,导致了内存泄漏  解决:实现Application,通过此实例来获取ApplicationContext,然后重构Singleton,把构建单例时的context去掉。
3.非静态内部类创建静态实例引起的内存泄漏  SecondActivity2包含一个内部类InnerClass,并且在onCreate代码中创建了InnerClass的静态实例mInner,该实例和app的生命周期是一致的,SecondActivity2想要销毁的时候会发现,内部类中的静态实例有外部类SecondActivity的引用,故不能释放掉,导致内存泄露 解决:把内部类修改成静态
4.非静态匿名内部类引起的内存泄漏   如果匿名内部类被异步线程使用,在activity销毁的是时候会发生内存泄露,应该将匿名内部类设置为静态


使用 LeakCanary 检测 Android 的内存泄漏 在APP中配置一下 就可以监测内存泄露的情况


TraceView(性能分析):可以从视图中看到父方法,子方法,耗时总时间,执行次数+递归次数,每次执行的时间

MAT(内存分析):Heap Size 堆的大小, Allocated 堆中已分配的大小 对单一操作进行多次重复,看堆的大小是否变化,如果一直变大则说明有内存泄露的嫌疑 ,其次可以通过Histogram的查询来看到所有类的实例对象,with incoming refs以后可以看到对象间的引用关系,首先通过操作前后获取的两次histgram作对比,找到操作前后多出来的实例,然后可以通过Path To GC Roots来找到导致该对象无法释放的一条可达性通路(也就是说找到谁引用的他,导致有强引用不能被释放)


SystemTrace:还没看....


oom和内存泄露是不一样的!!! 一定要清楚两者的区别,不要混淆


二叉树先序中序后序
前序遍历:根结点 ---> 左子树 ---> 右子树
中序遍历:左子树---> 根结点 ---> 右子树
后序遍历:左子树 ---> 右子树 ---> 根结点


二分查找(先排序 快排)



几种内部类:静态内部类(没有强引用)  匿名内部类  普通内部类


finalize函数(处理除内存外其他的系统资源,在其中释放系统资源或者做其它的清理工作,如关闭输入输出流,清理掉以便对象被gc回收,调用不会触发GC 且尽量少用)


设置进程和线程的优先级 SetPriorityClass--- 进程  SetThreadPriority---线程  
获取当前线程   GetCurrentThread  //返回当前线程句柄  GetCurrentThreadId //返回当前进程 ID 值


堆栈区别 栈里面放的是普通类型的变量  常量在堆里面 堆是树结构 栈是先进后出 堆向着内存地址增加的方向(向上生长)向着内存地址减小的方向增长(向下生长)


为什么堆要分代  提高效率 减小内存整理的过程


网络层四大协议:http  FTP TCP IP ICMP等等

每一层的协议至少知道两个 然后会根据你的回答 继续深问,例如你知道底层是怎么将HTTP封装的吗?
HTTP的实现你有看过吗?


http协议中不常用的命令:
请求方法(所有方法全为大写)有多种,各个方法的解释如下:
GET     请求获取Request-URI所标识的资源
POST    在Request-URI所标识的资源后附加新的数据
HEAD    请求获取由Request-URI所标识的资源的响应消息报头
PUT     请求服务器存储一个资源,并用Request-URI作为其标识
DELETE  请求服务器删除Request-URI所标识的资源
TRACE   请求服务器回送收到的请求信息,主要用于测试或诊断
CONNECT 保留将来使用
OPTIONS 请求查询服务器的性能,或者查询与资源相关的选项和需求

http2.0和1.1什么区别 基于tcp 1.1复用tcp 2.0不复用  (多路复用 数据压缩 服务器推送)


get和post区别

get只能1024所以之请求一些固定的东西(网页一类的)不能请求数据  post可以更多

              数据放的地方不一样   GET请求的数据会附在URL之后(就是把数据放置在HTTP协议头中)              POST把提交的数据则放置在是HTTP包的包体中。


https和http  HTTP下加入SSL层 端口也不一样,https是443,http是80 https有身份认证比较安全  


那你知道SSL的实现原理么?

ssl的实现原理:
数据传输的机密性:利用对称密钥算法对传输的数据进行加密。
身份验证机制:基于证书利用数字签名方法对服务器和客户端进行身份验证,其中客户端的身份验证是可选的。
消息完整性验证:消息传输过程中使用MAC算法来检验消息的完整性。


view的绘制流程:
measure:measure过程主要就是从顶层父View向子View递归调用view.measure方法(measure中又回调onMeasure方法)的过程  measure操作完成后得到的是对每个View经测量过的measuredWidth measuredHeight


layout:也是顶层父View向子View递归layout操作完成之后得到的是对每个View进行位置分配后的mLeft、mTop、mRight、mBottom View.layout方法可被重载,ViewGroup.layout为final的不可重载,ViewGroup.onLayout为abstract的,子类必须重载实现自己的位置逻辑


draw:递归绘制其所包含的所有子Viewdraw过程也是在ViewRootImpl的performTraversals()内部调运的,其调用顺序在measure()和layout()之后,这里的mView对于Actiity来说就是PhoneWindow.DecorView,ViewRootImpl中的代码会创建一个Canvas对象,然后调用View的draw()方法来执行具体的绘制工


invalidate:(只能在UI Thread中执行)View调运invalidate方法的实质是层层上传到父级View回溯的过程,直到传递到ViewRootImpl后触发了scheduleTraversals方法,然后整个View树开始重新按照上面分析的View绘制流程进行重绘任务


postInvalidate:通过ViewRootImpl类的Handler发送了一条MSG_INVALIDATE消息,实质就是又在UI Thread中调运了View的invalidate();方法


forceLayout 


requestLayout:会调用measure过程和layout过程,不会调用draw过程,也不会重新绘制任何View包括该调用者本身 


requestFocus:请求View树的draw过程,只绘制“需要重绘”的View


MeasureSpec:(View的内部类)测量规格为int型,值由高2位规格模式specMode和低30位具体尺寸specSize组成


整个View树的绘图流程是在ViewRootImpl类的performTraversals()方法开始的 !!!Activity第一次setContentView , 并不是在invalidate(true)开始绘制流程, 在第一次setContentView时,做的操作是初始化各个view对象, 并制定上下级关系,仅此而已。真正开始的地方是ActivityThread.handleResumeActivity中通过windowManager的实现类的addView方法,方法内部通过ViewRootImpl的实例调用setView方法。该方法内部的requestlayout发出消息,开始执行doTraversal,从而开始view三大流程


重绘:直接调用invalidate方法.请求重新draw,但只会绘制调用者本身。
触发setSelection方法。请求重新draw,但只会绘制调用者本身。
触发setVisibility方法。 当View可视状态在INVISIBLE转换VISIBLE时会间接调用invalidate方法,继而绘制该View。当View的可视状态在INVISIBLE\VISIBLE 转换为GONE状态时会间接调用requestLayout和invalidate方法,同时由于View树大小发生了变化,所以会请求measure过程以及draw过程,同样只绘制需要“重新绘制”的视图。
触发setEnabled方法。请求重新draw,但不会重新绘制任何View包括该调用者本身。
触发requestFocus方法。请求View树的draw过程,只绘制“需要重绘”的View。


java层调用c层的类 把C的类取地址 返给java层 然后在jni层强制转化成地址
c调java层 直接把java的类名传给jni   jni调用findclass和getMethod
jni global references


hashmap怎么实现的:HashMap是基于哈希表的Map接口的非同步实现,HashMap调用 indexFor(int h, int length) 方法来计算该对象应该保存在 table 数组的哪个索引处,该方法中使用的是: m & (length - 1)来确定语速在数组中位置。可是,通常根据hashcode确定元素在数组中的位置时,使用取模的方式,也就是 m % length。 二者哪个更好?答案:当 length = 2^n 的时候,二者的结果是一样的,而位运算明显会快一些(“&”更快一些,这是HashMap在速度上的优化)。而HashMap在初始化时保证了HashMap的容量总是2的n次方,即底层数组的长度总是为2的n次方
HashMap:按照特性来说明一下,储存的是键值对,线程不安全,非Synchronied,储存的比较快,能够接受null。按照工作原理来叙述一下,Map的put(key,value)来储存元素,通过get(key)来得到value值,通过hash算法来计算hascode值,根据hashcode值来决定bucket(桶),储存结构就算哈希表。
 
两个hashcode相同的时候会发生说明? 
hashcode相同,bucket的位置会相同,也就是说会发生碰撞,哈希表中的结构其实有链表(LinkedList),这种冲突通过将元素储存到LinkedList中,解决碰撞。新加入的放在链头,最先加入的放在链尾。 
使用不可变的、声明作final的对象作为key,并且采用合适的equals()和hashCode()方法的话,将会减少碰撞的发生,提高效率。不可变性使得能够缓存不同键的hashcode,这将提高整个获取对象的速度,使用String,Interger这样的wrapper类作为键是非常好的选择


如果两个键的hashcode相同,如何获取值对象? 
如果两个键的hashcode相同,我们通过key.equals()找到LinkedList中正确的节点。 


如果HashMap的大小超过了负载因子?怎么办? 
默认的HashMap里面的负载因子大小是0.75,将会重新创建一个原来HashMap大小的两倍bucket数组。 


重新调整的话会出现什么问题? 
多线程情况下会出现竞争问题,因为你在调节的时候,LinkedList储存是按照顺序储存,调节的时候回将原来最先储存的元素(也就是最下面的)遍历,多线程就好试图重新调整,这个时候就会出现死循环。


resize(rehash):原数组中的数据必须重新计算其在新数组中的位置,并放进去,这就是resize (最消耗性能的点)
当重新调整HashMap大小的时候,确实存在条件竞争,因为如果两个线程都发现HashMap需要重新调整大小了,它们会同时试着调整大小。在调整大小的过程中,存储在链表中的元素的次序会反过来,因为移动到新的bucket位置的时候,HashMap并不会将元素放在链表的尾部,而是放在头部,这是为了避免尾部遍历(tail traversing)。如果条件竞争发生了,那么就死循环了(因为插入过程不是原子操作,每个HashEntry是一个链表节点,很可能在插入的过程中,已经设置了后节点,实际还未插入,最终反而插入在后节点之后,造成链中出现环,破坏了链表的性质,失去了尾节点,出现死循环)


为什么要用String、Wrapper类,来作为键值对?因为他们一般不是不可变的,源码上面final,使用不可变类,而且重写了equals和hashcode方法,避免了键值对改写。提高HashMap性能。 


使用CocurrentHashMap代替Hashtable? 
可以,但是Hashtable提供的线程更加安全。segments
CocurrentHashMap:采用分段锁的方式(segment+HashEntry)


HashMap中插入null key的过程:
直接去遍历table[0]Entry链表,寻找e.key==null的Entry或者没有找到遍历结束
如果找到了e.key==null,就保存null值对应的原值oldValue,然后覆盖原值,并返回oldValue
如果在table[0]Entry链表中没有找到就调用addEntry方法添加一个key为null的Entry


单例模式三种写法有什区别 懒汉式(等到用的时候再创建,非线程安全) 饿汉式(直接创建出实例,线程安全) 


三方框架:
volley实现  网络通信框架 Volley既可以访问网络取得数据,也可以加载图片
okhttp  一个处理网络请求的开源项目
 
设计模式   单例 工厂 监听者模式 命令模式 观察者模式
观察者模式和监听者模式的差别:
在一个系统中有多个被监听对象时,订阅/发布(观察者模式)确实比监听模式可以集中管理事件处理,事件的触发也可以做成统一模型,程序的结构更加清晰。
不管怎样,观察者模式是监听模式的高级演进版,监听模式是基础。
当需要监听的对象只有一两个时,就没有必要再使用观察者模式了,使用反而增加了系统的复杂度。
根据java jdk的api,监听模式至少要实现两个接口java.util.EventListener和java.util.Event,Event的构造函数里有一个保留事件发生点的钩子(对象),用于在监听对象里回调钩子相关的东西。



代码重构 为什么重构 ? 要实现新能的优化,代码块之间的解耦,以便于后续更多新功能的加入



socket网络编程技术(网络中进程之间通信)  socket()创建一个socket描述符   bind()服务器在启动的时候都会绑定一个众所周知的地址(如ip地址+端口号)  listen()服务器用来来监听这个socket  connect()客户端通过其来建立与TCP服务器的连接  TCP服务器监听到这个请求之后,就会调用accept()函数取接收请求  read()、write()  close()计数减一 为零时客户端才会发送tcp4次挥手请求



反射机制 


Binder中的asInterface解析:是根据调用是否属于同进程而返回不同的实例对象
同进程时,调用asInterface返回的是Stub对象,其实就是在onBind中返回的mBinder
跨进程时,调用asInterface返回的是Stub.Proxy对象


Activity销毁流程:
1.用户主动退出
2.内存不足时释放掉,此时会存储该实例的数据 onSaveInstanceState()


MediaPlayer和AudioTrack:
MediaPlayer可以播放多种格式的声音文件,例如MP3,AAC,WAV,OGG,MIDI等。MediaPlayer会在framework层创建对应的音频解码器。
而AudioTrack只能播放已经解码的PCM流,如果是文件的话只支持wav格式的音频文件,因为wav格式的音频文件大部分都是PCM流。AudioTrack不创建解码器,所以只能播放不需要解码的wav文件。
MediaPlayer在framework层还是会创建AudioTrack,把解码后的PCM数流传递给AudioTrack,AudioTrack再传递给AudioFlinger进行混音,然后才传递给硬件播放。
MediaPlayer包含了AudioTrack。(pcm是一种数据编码格式,CD唱盘上刻录的就直接用pcm格式编码的数据文件;wav是一种声音文件格式,wav里面包含的声音数据可以是采用pcm格式编码的声音数据,也可以是采用其它格式编码的声音数据,但目前一般采用pcm编码的声音数据)


Audio:
AudioRcorder和AudioTrack是Audio系统对外提供API类,AudioRcorder主要用于完成音频数据的采集,而AudioTrack则是负责音频数据的输出。AudioManager管理Audio系统的
AudioFlinger管理着系统中的输入输出音频流,并承担着音频数据的混合,通过读写Audio硬件实现音频数据的输入输出功能;AudioPolicyService是Audio系统的策略控制中心,掌管系统中声音设备的选择和切换、音量控制等。


AudioManager:
整个系统上声音的策略,eg:通过AudioManager操作得到当前的音量会首先通过binder机制先获得AudioService代理,然后AudioService在通过AudioSystem获得AudioFlinger的代理才能操作到实际的实现部分


AudioTrack:
AudioTrack.getMinBufferSize,获取一个满足最小要求的缓冲区大小(根据采样率,采样精度,单双声道来得到整个frame的大小,一个frame就是1个采样点的字节数*声道)
然后new了一个AudioTrack,调用set函数,把AudioTrackJniStorage等信息传进去,调用了AudioTrack的start函数,调用AudioTrack的write函数。
createTrack的时候AudioTrack会得到AudioFlinger中的一个IAudioTrack对象,这里边有一个很重要的数据结audio_track_cblk_t,它包括一块缓冲区地址,包括一些进程间同步的内容,可能还有数据位置等内容。star以后,AudioTrack启动了一个线程,叫AudioTrackThread,这个线程里面其实就是通知了一下,专门做回调处理。AudioTrack调用write函数,肯定是把数据写到(memcpy)那块共享缓冲了,然后IAudioTrack在另外一个进程AudioFlinger中(其实AudioFlinger是一个服务,在mediaservice中运行)接收数据,并最终写到音频设备中。


AudioFlinger:
在MediaServer的main函数中实例化了AudiaoFlinger,将这个实例加入到了系统服务中,在AF的构造函数中设置了硬件的模式,所以Android系统启动的时候,AudioFlinger也准备好了硬件,在createTrack中获取了一个AF的IAF对象,然后由于是C/S架构,那么作为服务端的AF肯定有地方保存作为C的AT的信息,那么,AF是根据pid作为客户端的唯一标示的,mClients是一个类似map的数据组织结构,确定AT是AF的Client端以后,会返回给AF一个trackHandle对象,在这之前AF已经创建好了一个线程(Mixer类型的线程)所以该流程中AF没有再次创建线程。

 AF创建了一个代表HAL对象的东西
 APS创建了两个AudioCommandThread,一个用来处理命令,一个用来播放tone。我们还没看。
 APS同时会创建AudioManagerBase,做为系统默认的音频管理
 AMB集中管理了策略上面的事情,同时会在AF的openOutput中创建一个混音线程。同时,AMB会更新一些策略上的安排。



activityThread:管理主线程的执行调度和执行activities、broadcasts和其它操作,在main方法中首先获取一个MainLooper,然后将ActivityThread绑定到应用进程,并且传递给AMS一个Binder对象,接着进入到loop循环中,等待AMS的消息传来,在其内部类ApplicationThread中能够处理Activity等控件的一些生命周期函数,(利用的是内部类H来进行消息的管理,H继承了Handler)


自启动 开机自启动 


软引用(SoftReference)可以延迟回收,与弱引用(WeakReference)中急切回收对象不同。鉴于软引用和弱引用的这一区别,软引用更适用于缓存机制,而弱引用更适用于存贮元数据


framgment生命周期
onAttach()->onCreate()->onCreateView()->onActivityCreated()->onStart()->onResume()->onPause()->onStop()->onDestroyView()->onDestroy()->onDetach()
几种集合的类  List Set Queue Map


链表和数组 :数组寻址容易,插入和删除困难 链表寻址困难,插入和删除容易


接口和抽象类的区别:接口是对动作的抽象,抽象类是对根源的抽象 接口是有没有的关系 抽象类是 是不是的关系


service启动是在主线程中么  是的 


activity和service的通信 可以直接bind方式启动,这时候会在返回给activity一个service的binder对象,通过该bind对象来进行通信,也可以在service端定义一个接口,将消息反馈给activity,更新UI


怎么判断一个线程是否是主线程:Looper.getMainLooper() == Looper.myLooper(); !!!
获得主线程的Looper :Looper.getMainLooper()


如何快速定位anr问题   系统里面的工具(这个我也没有找到答案,有知道是什么工具的,还请帮忙回答下 ,感谢)


Adapter:连接后端数据和前端显示的适配器接口,是数据和UI(View)之间一个重要的纽带,首次将inflate(解析xml)获取的covertView的findViewByid都存到viewHolder中并且将其setTag给这个convertView,那么下次再进来的时候时候可以直接用这个convertView而不是重新从xml中解析。以上一系列操作都是在adapter的getview中操作

简要介绍下JVM:

可以从堆 栈 GC  方法区  本地方法栈来阐述

会根据你的回答来问一些问题,例如类加载的过程和机制,JRE、JVM、JDK的区别等(这些问题网上很多博客,就不写了)

关于GC的几种算法一定要知道,标记清除,标记压缩,复制,分代(新生代和旧生代,新生代用的copying算法,旧生代用的标记清除)

为什么新生代用的copy?

因为标记清除算法的过程会让其他所有的活动都停止,只有GC运行此时用户会有明显的卡顿感觉,而新生代触发GC的次数会比老年代的频繁,所以新生代才用的是copy算法,以空间换效率(个人理解,不知道是否准确,网上没有找到相关解释)


java虚拟机中变量的存储位置:

局部变量位于栈区,静态变量位于方法区,实例变量位于堆区,对象位于堆区



Restful Api有了解吗


Scrum敏捷开发流程  


手写观察者模式:
抽象观察者(Observer)
里面定义了一个更新的方法:


public interface Observer {
    public void update(String message);
}


具体观察者(ConcrereObserver)
用户是观察者,里面实现了更新的方法:


public class ConcrereObserver implements Observer {
    // 用户名
    private String name;
    public ConcrereObserver (String name) {
        this.name = name;
    }
    @Override
    public void update(String message) {
        System.out.println(name + "-" + message);
    }




}


抽象被观察者(Subject)
抽象主题,提供了attach、detach、notify三个方法:


public interface Subject {
    /**
     * 增加订阅者
     * @param observer
     */
    public void attach(Observer observer);
    /**
     * 删除订阅者
     * @param observer
     */
    public void detach(Observer observer);
    /**
     * 通知订阅者更新消息
     */
    public void notify(String message);
}


具体被观察者(ConcreteSubject)
具体主题(具体被观察者),里面存储了订阅该公众号的微信用户,并实现了抽象主题中的方法:


public class ConcreteSubject implements Subject {
    //储存订阅的用户
    private List<Observer> concrereObserverlist = new ArrayList<Observer>();


    @Override
    public void attach(Observer observer) {
        concrereObserverlist.add(observer);
    }


    @Override
    public void detach(Observer observer) {
        concrereObserverlist.remove(observer);
    }


    @Override
    public void notify(String message) {
        for (Observer observer : concrereObserverlist) {
            observer.update(message);
        }
    }
}


客户端调用
public class Client {
    public static void main(String[] args) {
        SubscriptionSubject mConcreteSubject=new ConcreteSubject();
        //创建用户
        ConcrereObserver user1=new ConcrereObserver("小小");
        ConcrereObserver user2=new ConcrereObserver("中中");
        ConcrereObserver user3=new ConcrereObserver("大大");
        //订阅
        mConcreteSubject.attach(user1);
        mConcreteSubject.attach(user2);
        mConcreteSubject.attach(user3);
        //更新发出消息给订阅的用户
        mConcreteSubject.notify("更新了");
    }
}


手写单例模式:

pubilc class Singleton
{
  private static Singleton instance;
  private Singleton (){}
  public static synchronized Singleton getInstance()
  {
     if(instance == null)
     inastance = new Singleton();
     return instance;
   }
}


public class Singleton {
    private static Singleton instance = new Singleton();
    private Singleton (){}
    public static Singleton getInstance() {
return instance;
    }
}




touch的调用顺序

事件分发!!!!

ACTION_DOWN  ACTION_MOVE  ACTION_UP
onTouchEvent():处理、消费某个事件
dispatchTouchEvent():事件的传递,一般来说传递给onTouchEvent或者View,view的话就直接处理时间,Viewgroup的话就会判断是否拦截,以及后续的处理。
onInterceptTouchEvent():只有ViewGroup才有该方法,用来判断是否拦截该事件并且自己处理
总体流程:从Activity中的dispatchTouchEvent方法中开始,并且调用DecorView,开始进行事件的分发,如果子view都返回true,那么表示有子view消费了这个事件,如果是false的话则Activity需要自己处理这个Touch事件。
View中的事件分发:如果设置了OnTouchListener(我们自己写的监听器),并且在这个监听器里处理掉了这个事件,那么onTouchEvent则不会被执行。也就是说我们自己写的touch事件的处理方法优先级高。在onTouchEvent方法中有对这个view是否是Clickable的判断 也就是说如果该view不可点击那么onTouchEvent方法会返回false,代表没有处理这个touch事件


一个ACTION_DOWN 事件传递到子View的时候,返回了false,那么接下来的action会再传递到这个view吗?

ACTION_MOVE ACTION_UP事件不会在传递进来,因为事件的处理是从Down开始,这个子View没有能处理Down,所以后续的都不会再传递进来,直到下一个Down事件



自定义view :要重写measure layout 和draw过程的方法,实现自己的逻辑流程


能创建哪几种线程池:

1.newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
2.newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
3.newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。
4.newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行

各个线程池是怎么实现的?有哪些关键性的变量?

当前核心线程数  最大线程数  workqueue  threadFactory等等

关于线程池 这一块 给大家推荐一篇博客写的蛮好的(https://www.cnblogs.com/exe19/p/5359885.html)



画图都有哪些方法?怎么画一个圆环?


dump文件:这些文件记录了JVM运行期间的内存占用、线程执行等情况,heap dump记录内存信息的,thread dump是记录CPU信息的


1.wait和sleep的区别
  sleep不出让系统资源;wait是进入线程等待池等待,出让系统资源,其他线程可以占用CPU
  wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用


2.创建线程的几种不同的方式
   ①继承Thread类,重写run方法;
   ②实现Runnable接口,重写run方法,但是比继承Thread类好用,实现接口还可以继承类,避免了单继承带来的局限性;
   ③实现callable接口,重写call方法,有返回值。
   ④使用实现了Executor接口的ThreadPoolExecutor来创建线程池。


3.如何获取线程的dump

  jstack 线程pid > thread.txt   


  
4.如何获取app的已使用内存
   Runtime.getRuntime().totalMemory()

   Runtime.getRuntime().freeMemory()    两者相减即可


5.java如何调用外部程序

   Runtime.getRuntime().exec("****.exe");  


6.TreeMap和hashMap、LinkedHashMap的区别
   LinkedHashMap保存了记录的插入顺序
   TreeMap实现SortMap接口,能够把它保存的记录根据键排序,默认是按键值的升序排序


7.手写生产者消费者模式代码


8.app中如何永久保存数据


9.根据Button的状态不同设置不同的背景
   Button 有focused, selected, pressed 等不同状态
   <selector xmlns:android="http://schemas.android.com/apk/res/android">
     <item android:state_pressed="true"
           android:drawable="@drawable/btn_pressed" /> <!-- pressed -->
     <item android:state_focused="true"
           android:drawable="@drawable/btn_normal" /> <!-- focused -->
     <item android:drawable="@drawable/btn_normal" /> <!-- default -->
   </selector>


10.ndk是什么 为什么一定要用
   Android应用运行在Dalvik虚拟机中。NDK允许开发人员使用本地代码语言(例如C和C++)实现应用的部分功能,帮助开发者快速开发C(或C++)的动态库。这样以代码重用的形式能够给某类应用提供方便,而且在某些情况下能提高运行速度,并能自动将so和java应用一起打包成apk。
11.activity和Fragment的关系


12.java中的线程之间的通信(不是Android中的)
①同步
②while轮询的方式 
③wait/notify机制
④管道通信就是使用java.io.PipedInputStream 和 java.io.PipedOutputStream进行通信



14.crash的分析思路
首先根据报错log查看是什么原因的crash,然后根据对应的错误信息进行排查,或者根据coverity等类似工具对代码进行静态扫描,以便于提早发现问题
14.1     空指针问题
通常暴露出java.lang.NullPointerException(NPE)的Crash问题, 原因包括字符串变量未初始化;类对象没有做初始化;对象的值为空时,没有判断为空的情况等等。
14.2     越界问题
java.lang.IndexOutOfBoundsException和java.lang.ArrayIndexOutOfBoundsException
等数组或者索引越界问题,原因是列表,数组等集合类的对象的索引超出了范围,比如对象的size为空去索引值或者size不为空去索引超出范围的值。
14.3     类查找问题
java.lang.ClassNotFoundException
类存在但是所在的bundle未被加载或者类实际上不存在。
14.4     资源查找问题
android.content.res.Resources$NotFoundException
资源文件在编译阶段和打包阶段未被拷贝到指定的目录或者资源文件缺失。
14.5     非法参数问题
java.lang.IllegalArgumentException
API调用时传递了不合法或不正确的参数,包括参数类型错误,图形参数超出坐标范围等。
14.6     数据传输超限问题
android.os.TransactionTooLargeException
通常发生的问题包括在服务和应用之间交换大量的数据包括缓存等,在intent里传递大量数据,从服务中接收bitmap文件等
14.7     资源加载引起的超时问题
java.util.concurrent.TimeoutException
典型的Crash问题调用AssetManager处理资源时未考虑超时问题,也未考虑对超时异常情况进行拦截处理。
14.8     数据库问题
数据库操作暴露出的问题体现在在数据库的流程处理不合理和不正确,包括事务处理时没考虑事务的逻辑处理。
14.9  第三方Jar包,动态库和框架问题
引入很多第三方的库和框架,动态库在不同层面会体现出各种各样的问题。
14.10新特性引入的问题
APP引入新的特性时,会引入新的Crash问题,比如React-native和JavaScriptCore的库的改动。需要对RN的原理和框架以及JS引擎非常熟悉,加强Code Review并且充分的测试后才可减少产品上线后的Crash问题。


Android开发中常用的框架,UI框架,网络请求,图片加载等

UI的基础layout,谷歌的AppBarLayout。ConstrainLayout等   这些有了解么,有使用过么。

然后会根据你的回答来问一些具体的实现逻辑



广播的Context能否创建dialog?
主要看创建AlertDialog的Context实例是不是Activity,只有Activity的Context才能创建AlertDialog对话框(也可是说对话框需要绑定一个Activity实例,当Activity关闭时对话框也会随着关闭)。另外:不要尝试在广播里显示对话框,因为不知道用户会什么时候关闭对话框,广播的生命周期很短,应该起个Service再去执行你需要的业务;如果你当前没有Activity的实例,你可以用WindowManager创建全局的悬浮视图(对话框的底层也是基于这个创建视图);


用过哪些同步锁?

这个我也没有答上来,只是介绍了synchronized的实现机制,使用了monitor通过指令monitorenter进入到锁定状态

1.monitor进入数为0,则可以进入monitor的状态,然后这个值+1

2.如果该线程重复进入,则+1

3.其他线程占用的时候,则该线程进入阻塞等待状态,知道monitor为0是才可以进入

这部分 网上也有很多博客,我写的比较少,想要了解的,可以去看看同步锁的实现细节



装箱和拆箱:装箱就是自动将基本数据类型转换为包装器类型;拆箱就是自动将包装器类型转换为基本数据类型  valueOf方法实现装箱


Java并发编程:(也就是多线程同时访问同一数据,这两个问题我觉得是一样的)

1.可以使用Synchronized和Volatile来保证锁和可见性,让同一时刻只有一个线程可以访问数据

2.可以使用ThreadLocal ,(如果你说到了这一点,那么好,接下来他就会问你,你知道这个类的实现细节么,讲一讲。个人觉得这个是加分项,也就是说你要知其然更知所以然,每一个常用工具类的实现细节你都知道,说明你对Android理解的很到位,你可以先看了,然后面试的时候把他往这个方向上引导,就算他问你实现的细节,你也提一嘴)


接口和虚类的区别:

接口:有没有

虚类:是不是  (这个问题,比较常规,网上有很多解释,我就记住了这六个字,然后展开叙述就好了)


Android的新虚拟机ART你有了解么?

ART虚拟机是在安装时候执行优化了,执行dex2oat程序得到的是elf文件,和DVM的区别就是工作的时间节点不一样,处理的文件类型不一样,DVM是通过SDK中DEX工具将文件转化为.dex文件,然后DVM再从其中读取指令和数据


 目前就这么多,后续还有的话 我还会继续补充上来,希望对大家有所帮助!



原创粉丝点击