原题目在这里,这里是对大部分题目的个人理解,如有不同意见,请留言交流。
仕兰微Android社招
1. 四大组件是什么
Activity,Service,BroadcastReceiver,ContentProvider
2. 四大组件的生命周期和简单用法
- Activity
生命周期:onCreate -> onStart -> onResume -> onPause -> onStop -> onDestroy
简单用法:
- 创建自定义Activity与相应的布局文件;
- 将创建的自定义Activity注册在Manifest文件中。
- Service
- start模式下:
生命周期:onCreate -> onStartCommand -> onDestroy
简单用法:
- 创建自定义Service
- 将自定义Service注册在Manifest文件中
- 在需要使用Service的地方调用 startService 方法启动Service
- bind模式下:
生命周期:onCreate -> onBind -> onUnbind -> onDestroy
简单用法:
- 创建自定义Service,并在其中创建IBinder接口的自定义类;
- 在onBind方法中返回IBinder接口的自定义类对象;
- 将Service注册在Manifest文件中;
- 在需要使用自定义Service的地方,创建ServiceConnetion对象,并利用ServiceConnetion对象获取到自定义Service对象
- 调用bindService方法,将自定义Service与相应组件绑定
- BroadcastReceiver
- 动态注册
生命周期:与注册BroadcastReceiver的组件的生命周期保持一致
简单用法:MyReceiver rec = new MyReceiver(); IntentFilter filter = new IntentFilter(); intentFilter.addAction("xxx"); registerReceiver(rec, filter); // ... unregisterReceiver(rec);
- 静态注册
生命周期:在onRceive方法中的任务执行完毕之后的任意时间段进行销毁,不需要手动进行取消注册操作
简单用法:
- 自定义BroadcastReceiver
- 在Manifest文件中注册自定义BroadcastReceiver
- ContentProvider
生命周期:执行onCreate方法时创建成功,直到进程被销毁,生命周期结束
简单用法:
- 创建自定义ContentProvider类,并实现
onCreate 创建ContentProvider时使用
query 查询指定uri的数据时返回一个Cursor
insert 向指定uri的ContentProvider中添加数据
delete 删除指定uri的数据
update 更新指定uri的数据
getType 返回指定uri中的数据MIME类型等方法
- 在Manifest文件中注册自定义ContentProvider类
- 在需要调用数据的地方,自定义ContentResolver类
3. Activity之间的通信方式
- Intent与Bundle
- 类的静态变量
- 使用全局变量Application
- 借助数据持久化工具SharedPreferences/sQLite/File
- Service
- 第三方工具如EventBus等
4. Activity各种情况下的生命周期
- 按返回键:
启动:onCreate -> onStart -> onResume
按返回键:onPause -> onStop -> onDestroy
- 按HOME键:
启动:onCreate -> onStart -> onResume
按HOME键:onPause -> onStop
启动:onRestart -> onStart -> onResume
- 横竖屏切换:
启动:onCreate -> onStart -> onResume
切换:onPause -> onStop -> onDestroy -> onCreate -> onStart -> onResume
- 横竖屏切换(配置configChanges):
在Activity标签下配置:android:configChanges="orientation|keyboardHidden|screenSize"启动:onCreate -> onStart -> onResume
横竖屏切换:onConfigurationChanged
5. 横竖屏切换时,Activity各种情况下的生命周期
6. Activity与Fragment之间生命周期比较
当Activity包含一个Fragment时,Activity与Fragment的生命周期变化:
Activity(onCreate) -> Fragment(onAttach -> onCreate -> onCreateView -> onActivityCreated) ->
Activity(onStart) -> Fragment(onStart) ->
Activity(onResume) -> Fragment(onResume) ->
Fragment(onPause) -> Activity(onPause) ->
Fragment(onStop) -> Activity(onStop) ->
Fragment(onDestroyView -> onDestroy -> onDetach) -> Activity(onDestroy)
7. Android动画框架实现原理
Android动画实现原理
Android提供了三种动画:Frame Animation、Tween Animation、Property Animation
- Frame Animation
使用了Choreographer机制AnimationDrawable animationDrawable = (AnimationDrawable)> image.getDrawable(); animationDrawable.start();
- Tween Animation
在绘制过程中,尝试获取动画在当前时刻的变换,然后应用到View的绘制中Animation translateAnimation = new TranslateAnimation(0, 100, 0, 0); translateAnimation.setDuration(500); translateAnimation.setInterpolator(new AccelerateInterpolator()); translateAnimation.setFillAfter(true);// 动画结束之后,保持当前状态(即不返回到动画开始时的状态) imageView.startAnimation(translateAnimation);
- Property Animation
不依赖于Android本身的回调机制,但依赖于Looper的Thread
8. Android各个版本API的区别
9. requestLayout,onLayout,onDraw,drawChild区别与联系
- requestLayout:当前View发生一些改变,这个改变使得现有的View失效,所以调用requestLayout方法对View树重新布局,过程包括measure和layout过程,但不会调用draw过程,即不会发生重新绘制视图的过程。
- onLayout:调用onLayout的时机是,View需要给自己设置大小和位置,或者ViewGroup需要设置子View和自身时调用。
- onDraw:绘制视图时调用。
- drawChild:ViewGroup绘制自身的子View时会调用dispatchDraw(canvas)方法,这个方法中调用drawChild。
10. invalidate与postInvalidate的区别及使用
invalidate工作在主线程中,在非主线程中可以通过Handler来通知主线程进行界面更新。
postInvalidate在非主线程中被调用。
11. Activity-Window-View三者的差别
原文
Activity
- Activity不负责视图的控制,它只是控制生命周期和处理事件,真正控制视图的是Window。一个Activity包含一个Window,Window才是真正代表一个窗口。
- Activity就是一个控制器,统筹视图的添加与显示,以及通过其他回调方法与Window、View进行交互。
Window
- 表示一个窗口,是所有View的直接管理者,任何视图都通过Window呈现。
- Window是一个抽象类,具体实现是PhoneWindow,PhoneWindow有一个内部类DecorView,通过创建DecorView来加载Activity中设置的布局。
View
- DecorView是Android视图树的根节点视图。
- Activity通过setContentView设置的布局文件就是被加载道DecorView的内容栏的,成为其唯一的子View。
12. 谈谈对Volley的理解
Volley适合频繁的网络通信操作,能同时实现多个网络通信,扩展性强。
用法:使用Volley的通用步骤是通过Volley暴露的newRequestQueue方法,创建RequestQueue对象,接着向RequestQueue对象中添加Request(StringRequest、JsonRequest、ImageRequest或自定义Request)对象。RequestQueue queue = Volley.newRequestQueue(this); String url = ""; StringRequest strRequest = new StringRequest(Request.Method.GET, url, new Response.Listener
() { @Override public void onRequest(String response) { } }, new Response.ErrorListener() { @Override public void onErrorListener(VolleyError error) { } }); queue.add(strRequest); queue.start(); 步骤有三步:
创建一个RequestQueue对象;
创建一个StringRequest对象;
将StringRequest对象添加到RequestQueue里面。
- 对于post方式的网络请求:
RequestQueue queue = Volley.newRequestQueue(this); String url = ""; StringRequest strRequest = new StringRequest(Request.Method.POST, url, new Response.Listener
() { @Override public void onResponse(String response) { } }, new Response.ErrorListener() { @Override public void onErrorListener(VolleyError error) { } }){ @Override protected Map getParams() throws AuthFailureError { return super.getParams(); } }; queue.add(strRequest); queue.start(); getParams方法获取的数据是用于向服务器提交的参数。
13. Handler机制与底层实现
- Looper.prepare():实现创建Looper对象、MessageQueue对象的操作。
- Handler对象调用send或post方法,将Message对象传入MessageQueue中,并将自身传递给名为target的对象。
- Looper.loop():开始不断从MessageQueue中取出消息并处理消息。
- 对于来自不同线程的消息,使用ThreadLocal对象区分线程。
14. Handler、Thread和HandlerThread的差别
- Handler:在Android中负责发送和处理消息,通过它可以实现其他线程与主线程之间消息通信。
- Thread: Java进程中执行运算的最小单位,即执行处理机调度的基本单位。
- HandlerThread:一个继承自Thead类的HandlerThread类,这个类对Java的Thread做了很多便利的封装。HandlerThread在start之后可以获得Looper对象,利用这个Looper对象可以方便地创建Handler对象,实现线程间的通信。
15. Handler发消息给子线程,Looper怎么启动
Looper对象存在于与创建Handler对象所在线程一致的线程中,也就是Looper对象与Handler对象保持线程一致。当Handler对象向子线程发送消息时,消息被保存在MessageQueue对象中,Looper对象会在loop方法中不断检测MessageQueue中是否有消息存在,如果存在则取出、处理并调用发送消息的Handler对象发送消息。
16. 关于Handler,在任何地方new Handler都是什么线程下
- 不传递Looper对象创建Handler:在哪个线程创建Handler,默认获取的就是该线程的Looper对象,Handler的一系列操作都是在该线程下进行的。
- 传递Looper对象创建Handler:传递的Looper对象是哪个线程的,Handler对象绑定的就是该线程。
17. ThreadLocal原理,实现及如何保证Local属性
在Thread类的内部有一个成员专门用于存储线程的ThreadLocal数据:ThreadLocal.Values,不同线程访问同一个ThreadLocal的get方法,ThreadLocal内部会从各自的线程中取出一个数组,然后再从数组中根据ThreadLocal的索引去查找对应的value值。
ThreadLocal的set/get方法所操作的对象都是当前线程的ThreadLocal.Values的对象的table数组,在不同线程中访问同一个ThreadLocal的set/getchafe,它们对ThreadLocal所做的读/写操作仅限于各自线程内部,这就是为什么ThreadLocal可以在多个线程中互不干扰地存储和修改数据。
18. 在单线程模型中Message、Handler、Message Queue、Looper之间的关系
Handler获取当前线程的Looper对象,由Looper对象从存放Message的MessageQueue中取出Message,再由Handler进行Message的分发与处理。
19. View事件传递分发机制
- 触摸事件有一个down、多个move、一个up组成;
- 事件的传递是从Activity开始的,Activity -> PhoneWindow -> DecorView -> ViewGroup -> View,主要操作在ViewGroup和View中;
- ViewGroup类主要调用 dispatchTouchEvent -> onInterceptTouchEvent -> dispatchTransformedTouchEvent ;ViewGroup不直接调用onTouchEvent方法;
View类主要调用 dispatchTouchEvent -> onTouchEvent -> performClick 。事件传递顺序:
- down从最外层的View向里层的子Viwe传递,如果最里层的View不响应事件,再将事件向父View传递;
- 把父View当作普通View对待,如果当前View也不响应事件,继续向父View传递;
- 不断重复第2步,整个View的传递与分发过程就完成了。
20. ListView中图片错位的问题是如何产生的
由于复用contentView与异步加载图片造成的。
21. 服务器只提供数据接收接口,在多线程或多进程条件下,如何保证数据的有序到达
- 有序,多线程要使用同步,多进程要使用进程通信保障数据传输的顺序
- 到达,使用TCP可靠传输,服务器返回传输成功后才能传输下一个
22. 性能优化,如何检测一段代码的执行时间,界面卡顿如何修复
性能优化分类:
- 卡顿优化
- 内存优化
- 电量优化
- 网络优化
- 启动优化、安装包体积优化
检测一段代码执行时间:
?
界面卡顿如何修复:
过度绘制
去掉不必要的背景色
- 设置窗口背景色为通用背景色,去除根布局背景色
- 去除与列表背景色相同的Item背景色
布局视图树扁平化
- 移除嵌套布局
- 使用merge、include标签
- 使用性能消耗更小的布局
减少透明色,即alpha属性的使用
通过使用半透明颜色值代替
其他
- 使用ViewStub标签,延迟加载不必要的视图
- 使用AsyncLayoutInflator异步解析视图
主线程耗时操作
- Json数据解析耗时
- 文件操作
- Binder通信
- 正则匹配
- 相机操作
- 组件初始化
- 循环删除、创建View
- WebView首次初始化
主线程挂起
- 异步线程与主线程竞争CPU资源
- 频繁GC使主线程挂起
23. 滑动不流畅怎么处理
导致Android界面滑动卡顿的原因主要有两个:
1.UI线程有耗时操作
2.视图渲染时间过长
对于第一种情况,主线程中不要有耗时操作,耗时操作可以放入Thread中并通过Handler与主线程交互,或者使用AsyncTask来完成耗时操作。
对于第二种情况,?
24. fps如何提高
安卓App帧率优化的一些经验总结
25. 内存泄露怎么检测
使用LeakCanary、Bugly或者AS自带的Profiler
附内存泄漏相关知识:
定义:一个不再被使用的对象,因为另一个正在使用的对象持有对该对象的引用,导致不再被使用的对象无法被回收,而停留在堆内存中,就产生了内存泄漏。
如何解决:
- 持有Context造成的内存泄漏
在Android中有两种Context对象:Activity和Application,当给一个类传递Context时经常使用第一种,这就导致了该类持有对Activity的引用;当Activity关闭时,因为其他类持有而导致无法正常被回收,导致内存泄漏。
解决方案:
在给其他类传递Context时使用Application对象,这个对象的生命周期与应用同长,而不依赖Activity的生命周期;
对Context的引用不要超过其本身的生命周期,谨慎对Context使用static关键字。
- Handler造成的内存泄漏
解决方案:
- 将Handler放在单独的类中,或者使用静态内部类
- 如果要在Handler内部调用Activity中的资源,可以在Handler中使用弱引用的方式指向所在的Activity,使用static+WeakReference的方式断开Handler与Activity之间的关系
- 单例模式造成的内存泄漏
单例模式的静态特性使得单例模式的生命周期与应用一样长
解决方案:
一般参考持有Context造成的内存泄漏 即可解决
- 非静态内部类创建静态实例造成的内存泄漏
在项目中通常为了避免多次的初始化操作,会创建静态对象保存这些对象,这种情况也容易引发内存泄漏。因为非静态内部类默认持有外部类的引用,而创建的静态实例的生命周期与应用的一样长,这就导致该静态实例一直持有该Activity的引用,导致Activity不能正常回收。
解决方案:
- 将内部类修改为静态的,这样不再持有外部类的引用
- 将该对象抽取出来封装成一个单例
- 线程造成的内存泄漏
当使用线程时一般都会使用匿名内部类,而匿名内部类会默认持有外部类引用,当Activity关闭之后,如果线程中的内务还没有执行完毕,就会导致Activity不能正常回收,造成内存泄漏
解决方案:
创建一个静态的类,实现Runnable类,在使用的时候实例化该自定义Runnable类。
- 资源没有关闭造成的内存泄漏
对于使用了BroadcastReceiver、ContentProvider、File、Cursor、Stream、Bitmap等资源的代码,应该在Activity销毁时及时关闭或者注销,否则这些资源将不会被回收,造成内存泄漏。
- 监听器没有注销造成的内存泄漏
在Android程序中存在很多需要register/unregister的监听器,需要确保及时unregister监听器
- 集合中的内存泄漏
通常会把一些对象的引用加入道集合容器中,当不再需要该对象时,并没有把对该对象的引用从集合中清理出去,这样这个集合就会越来越大,如果这个集合是static的,那情况就会更严重。
退出程序之前,要将集合中的对象clear,然后设置为null,再退出程序
26. 线程和线程池
线程:程序执行流的最小执行单位,是进程中的实际运作单位
线程池:Java中开辟出的一种管理线程的概念,是若干线程的集合。
27. wait()和sleep()的区别
wait:Object类的方法,线程休眠时会释放对象的同步锁,其他线程可以访问该对象。必须在synchronized的代码块下使用
sleep:Thread类的方法,当在一个synchronized方法中调用sleep时,线程虽然休眠了,但是对象的同步锁没有被释放,其他线程仍然无法访问该对象。
28. 线程池的参数详解
ThreadPoolExecutor是线程池的真正实现,它的构造方法提供了一系列参数来配置线程池。
下面是ThreadPoolExecutor的一个比较常用的构造方法:public ThreadPoolExecutor(int corePoolSize, int maimumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue
workQueue, ThreadFactory threadFactory)
- corePoolSize
线程池的核心线程数,默认情况下,核心线程数会在线程池中一直存在,即使它们处于闲置状态
- maximumPoolSize
线程池所能容纳的最大线程数,当活动线程数达到这个数值后,后续的新任务将会被阻塞
- keepAliveTime
非核心线程闲置时的超时时长,超过这个时长,非核心线程就会被回收。
- unit
用于指定keepAliveTime参数的事件单位,这是一个枚举值
- workQueue
线程池中的任务队列,通过线程池的execute方法提交的Runnable对象会存储的这个参数中
- threadFactory
线程工厂,为线程池提供创建新线程的功能。ThreadFActory是一个接口,它只有一个方法:Thread newThread(Runnable r)
ThreadPoolExecutor执行任务时遵循如下规则:
- 如果线程池中的线程数量未达到核心线程的数量,那么会直接启动一个核心线程来执行任务。
- 如果线程池中的线程数量已经达到或者超过核心线程的数量,那么任务会被插入道任务队列中排队等待执行。
- 如果第二步中无法将任务插入到任务队列中,这往往是任务队列已满,这时如果线程数量没有达到线程池规定的最大值,那么会立刻启动一个非核心线程来执行任务。
- 如果第三步中的线程数量已经达到线程池规定的最大值,那饿就拒绝执行此任务,ThreadPoolExecutor会调用RejectedExecutionHandler的rejectedExecution方法来通知调用者。