1、Android动画框架实现原理
android 动画框架分为三种类别:渐变动画、帧动画、属性动画
Android 动画框架是建立在 View 的级别上的,所以当有动画产生时会向View传递参数,当视图接收到变化参数时,通过 ParentView 来不断调整 ChildView 的画布坐标系来实现的动画,对于应用开发而言如何使用动画以及自定义属性是我们最为关心的问题
渐变动画:Animation和AnimationUtils结合调用xml文件中定义动画效果来实现(
属性动画:一般而言,属性动画用的比较多,因为更为灵活,同样也可以实现前面的动画效果,属性动画原理就是通过不断改变控件(get()和set()方法) 的属性然后不断的自我invalidate()刷新的过程
属性动画中有几个重要的类:
ObjectAnimator:可以完成控制控件属性的变化,如ObjectAnimator.ofFloat(view, "translationX", 0, translationX)
AnimatorSet :集合,把上述动画结合起来叠加使用
VauleAnimator:可以根据变化的百分比灵活控制值的改变,可以配合插值器和估值器进行使用
对于部分情况,ObjectAnimator无法直接生效,比如ObjectAnimator.ofInt(mButton, "width", 500).setDuration(5000).start();如果传入是Button,它继承至TextView,setWidth()针对textView而非View,针对这类情况,有三种办法解决:
- 给你的对象加上get和set方法,如果你有权限的话
- 用一个类来包装原始对象,间接为其提供get和set方法
- 采用ValueAnimator,监听动画过程,自己实现属性的改变
详情请参考博客:https://blog.csdn.net/singwhatiwanna/article/details/17841165
2、Android各个版本API的区别
Android 每个版本都会有很多新的功能提供以及对老版本问题的优化,最大的几个改变就是2014I/O退出的material design、安全方面敏感运行时权限的添加、解决碎片问题推出 的V4\v7包等等。这些都是对开发影响很大的
3、Requestlayout,onlayout,onDraw,DrawChild区别与联系
View绘制分三个步骤,顺序是:onMeasure,onLayout,onDraw。经代码亲测,log输出显示:调用invalidate方法只会执行onDraw方法;调用requestLayout方法只会执行onMeasure方法和onLayout方法,并不会执行onDraw方法。
所以当我们进行View更新时,若仅View的显示内容发生改变且新显示内容不影响View的大小、位置,则只需调用invalidate方法;若View宽高、位置发生改变且显示内容不变,只需调用requestLayout方法;若两者均发生改变,则需调用两者,按照View的绘制流程,推荐先调用requestLayout方法再调用invalidate方法。
当view为viewgroup时,会进行DrawChild的调用
4、invalidate和postInvalidate的区别及使用
都是使View树重绘,但两者的使用条件不同,postInvalidate是在非UI线程中调用,invalidate则是在UI线程中调用。
5、Activity-Window-View三者的差别
Activity像一个工匠(控制单元),Window像窗户(承载模型),View像窗花(显示视图) LayoutInflater像剪刀,Xml配置像窗花图纸。
在Activity中调用attach,创建了一个Window
创建的window是其子类PhoneWindow,在attach中创建PhoneWindow
在Activity中调用setContentView(R.layout.xxx)
其中实际上是调用的getWindow().setContentView()
调用PhoneWindow中的setContentView方法
创建ParentView: 作为ViewGroup的子类,实际是创建的DecorView(作为FramLayout的子类)
将指定的R.layout.xxx进行填充 通过布局填充器进行填充【其中的parent指的就是DecorView】
调用到ViewGroup
调用ViewGroup的removeAllView(),先将所有的view移除掉
添加新的view:addView()
6、谈谈对Volley的理解
Volley 是基于请求队列实现的,把一个个的post、get请求添加到请求队列,然后请求队列start一个个开始请求,Volley以前底层是基于HttpConnection的,现在可以把底层实现换成Okhttp,Volley中实现了对图片加载的处理
7、如何优化自定义View
1、避免过度重绘,即调用OnDraw();大多数时候导致onDraw都是因为调用了invalidate().因此请尽量减少调用invaildate() 的次数。如果可能的话,尽量调用 含有4个参数的invalidate() 方法而不是没有参数的invalidate()。没有参数的invalidate会强制重绘整个view
2、避免在OnDraw()或者循环中避免new对象,因为OnDraw()常被调用,而是对象初始化new好,可以提供重用
3、把握好I/O操作的时机,对于自定义View中读取属性和文件的时候,避免重复读取。
4、尽量减少或简化计算 ,在自定义View中,计算各个坐标,计算触摸事件、位置等等都占了很大比重。而且像是滑动这样的操作,每一帧都是通过实时计算的,所以减少或者简化计算是提高性能的有效方式
8、低版本SDK如何实现高版本api?
可以去support包中找相应的方法,例如5.0才出的background tint,那么如果你的minSdk是小于5.0的话,那么studio就报错了,你可以去使用support-v4包中的DrawableCompat类
9、描述一次网络请求的流程
HTTP协议就是基于TCP/IP协议模型来传输信息的,网络请求最主要的还是TCP传输层的三次握手:
第一次握手:建立连接。客户端发送连接请求报文段,将SYN位置为1,Sequence Number为x;然后,客户端进入SYN_SEND状态,等待服务器的确认;
第二次握手:服务器收到SYN报文段。服务器收到客户端的SYN报文段,需要对这个SYN报文段进行确认,设置Acknowledgment Number为x+1(Sequence Number+1);同时,自己自己还要发送SYN请求信息,将SYN位置为1,Sequence Number为y;服务器端将上述所有信息放到一个报文段(即SYN+ACK报文段)中,一并发送给客户端,此时服务器进入SYN_RECV状态;
第三次握手:客户端收到服务器的SYN+ACK报文段。然后将Acknowledgment Number设置为y+1,向服务器发送ACK报文段,这个报文段发送完毕以后,客户端和服务器端都进入ESTABLISHED状态,完成TCP三次握手。
为何时三次呢?其实时为了防止已失效的报文发送到服务端建立无用的链接,第一次握手请求可能延时导致失效,服务端接收到报文后并不知道是不是正常报文,故要发送一个确认给客户端,进行第二次握手,当客户端确认第一次握手报文有效后,再次发送一个确认有效报文给服务端确认报文有效
在HTTP应用层的话只是做了请求格式,请求头、请求内容的包装,当TCP链接80端口建立链接后,客户端就会向服务器发送GET或者POST网络请求,然后服务端会做出响应,应答的第一部分是协议的版本号和应答状态码
10、HttpUrlConnection 和 okhttp关系
HttpURLConnection是一种多用途、轻量极的HTTP客户端
okhttp是高性能的http库,支持同步、异步,而且实现了spdy、http2、websocket协议、没有争议的是目前OKHTTP已经是android网络访问框架的主流,大部分优秀的开源网络库都是基于OKHTTP的,如Retrofit
11、Bitmap对象的理解
Bitmap对象存于堆上,可以以createBitmap方法和BitmapFactory来创建对象
createBitmap -> nativeCreate
// resource
BitmapFactory.decodeResource(...)
// 字节数组
BitmapFactory.decodeByteArray()
// 文件
BitmapFactory.decodeFile()
// 流
BitmapFactory.decodeStream()
// FileDescriptor
BitmapFactory.decodeFileDescriptor()
Bitmap大图无损显示:一张大图压缩后不清晰显示对于我们来说没有实际意义,所以需要局部显示,可以通过BitmapRegionDecoder.newInstance方法创建一个BitmapRegionDecoder对象,
然后再通过BitmapRegionDecoder的decodeRegion方法获取图片某一区域的Bitmap
Bitmap优化:图片加载速度 内存 > 硬盘(本地)> 网络,使用图片加载框架Picasso,Glide,Fresco等
降低图片占用内存大小,一般采用压缩(鲁班压缩算法)
12、looper架构
Android 中Handler 、looper、MessageQueue一起构成了一套线程间通信的桥梁,每一个线程要使用hanlder,都需要创建线程自己的looper对象,然后通过开启死循环looper.loop(), 不断的从消息队列MessageQueue中去取数据,这个数据呢是hanlder通过Message 放进去的,我们知道不同的线程访问同一数据是需要加锁来解决的,但加锁降低了代码运行效率,这里采用队列的方式,先进先出,这样不断的取数据就不会导致同时访问的问题,所以在子线程中looper使用需要以下几步:
Looper.prepare();
Looper.myLooper();
....... //耗时操作
Looper.loop();
而在主线程中不用写这么多,因为系统已经帮我们写好了,当然我们还可以使用一个写好的线程类HandlerThread就不用那么麻烦了,直接new出该线程类的派生类对象,再start就可以了.
Looper内部有一个ThreadLocal比较重要,它并不是线程,是用于储存当前线程的数据副本
13、ActivityThread,AMS,WMS的工作原理
Activity与WIndow:
Activity只负责生命周期和事件处理
Window只控制视图
一个Activity包含一个Window,如果Activity没有Window,那就相当于Service
AMS与WMS:
AMS统一调度所有应用程序的Activity
WMS控制所有Window的显示与隐藏以及要显示的位置
Android的framework层主要是由WMS、AMS还有View所构成
14、自定义View如何考虑机型适配
这里要考虑的是屏幕的问题:
合理使用warp_content,match_parent.
尽可能的是使用RelativeLayout
针对不同的机型,使用不同的布局文件放在对应的目录下,android会自动匹配。
尽量使用点9图片。
使用与密度无关的像素单位dp,sp
引入android的百分比布局。
切图的时候切大分辨率的图,应用到布局当中。在小分辨率的手机上也会有很好的显示效果。
15、自定义View的事件
view的事件传递可做成一个U型图:
- 对于 dispatchTouchEvent,onTouchEvent,return true是终结事件传递。return false 是回溯到父View的onTouchEvent方法。
- ViewGroup 想把自己分发给自己的onTouchEvent,需要拦截器onInterceptTouchEvent方法return true 把事件拦截下来。
- ViewGroup 的拦截器onInterceptTouchEvent 默认是不拦截的,所以return super.onInterceptTouchEvent()=return false;
- View 没有拦截器,为了让View可以把事件分发给自己的onTouchEvent,View的dispatchTouchEvent默认实现(super)就是把事件分发给自己的onTouchEvent。
16、AstncTask+HttpClient 与 AsyncHttpClient有什么区别?
17、LaunchMode应用场景
Activity一共有以下四种launchMode:
standard
singleTop
singleTask
singleInstance
android:launchMode=”standard” : 模式是默认的启动模式,不用为配置android:launchMode属性即可,当然也可以指定值为standard,不管有没有已存在的实例,都生成新的实例
android:launchMode=”singleTop” : 如果发现有对应的Activity实例正位于栈顶,则重复利用,不再生成新的实例,可用于接受到消息后显示的界面
android:launchMode=”singleTask” : 对于同一队列中有目标activity的页面,依次出栈相应页面并使用该页面,适合在app层次比较深的页面返回首页的情况,依次出栈每个页面直接回到首页,而不是新建页面
android:launchMode=”singleInstance” : 将Activity放置于这个新的栈结构中,并保证不再有其他Activity实例进入。 singleInstance适合需要与程序分离开的页面
18、AsyncTask 如何使用?
在需要异步任务处理的地方:new TestAsyncTask(this).execute();
如果是多任务需求的话需要配合ExecutorService 一起使用:task.executeOnExecutor(FULL_TASK_EXECUTOR);
参考:https://blog.csdn.net/u012500046/article/details/53490766
TestAsyncTask内部我们需要重写几个函数:onPreExecute()异步任务前界面状态准备、doInBackground()异步任务子线程执行任务,该方法中可以调用publishProgress来通知onProgressUpdate进行界面刷新、而onProgressUpdate就是任务执行过程中更新界面、onPostExecute()后台任务执行完毕开始结束执行
这个是Android提供的异步任务处理方案之一,内部采用线程池+handler技术,用起来比较方便,初始化在主线程中执行,Android中的工作者线程主要有AsyncTask、IntentService、HandlerThread
19、SpareArray原理
SpareArray对于数据量小的键值对储存当其键为整型数据时比hashmap更为出色,因为其储存原理:HashMap是使用数组+链表的数据结构存储键值对;而SparseArray只是用了两个数组进行存储,对于稀疏数组非常适合。
所谓稀疏数组就是数组中大部分的内容值都未被使用(或都为零),在数组中仅有少部分的空间使用。因此造成内存空间的浪费,为了节省内存空间,并且不影响数组中原有的内容值,我们可以采用一种压缩的方式来表示稀疏数组的内容。
假设有一个97的数组,其内容如下:
在此数组中,共有63个空间,但却只使用了5个元素,造成58个元素空间的浪费。以下我们就使用稀疏数组重新来定义这个数组:
其中在稀疏数组中第一部分所记录的是原数组的列数和行数以及元素使用的个数、第二部分所记录的是原数组中元素的位置和内容。经过压缩之后,原来需要声明大小为63的数组,而使用压缩后,只需要声明大小为63的数组,仅需18个存储空间。
SparseArray是android里为
20、请介绍下ContentProvider 是如何实现数据共享的?
1、创建一个自己的TestProvider继承ContentProvider,实现查询、插入等数据库操作
2、将TestProvider在AndroidManifest.xml中注册,定义相关的permission和provider,其中android:exported="true" ,设置此provider可以被其他应用使用
3、在本应用不同进程中使用getContentResolver()方法来对该ContentProvider进行操作;在其他应用中用uses-permission权限,来对我们定义的provider进行访问,该权限是我们permission中定义的地址,最后用getContentResolver()方法来对相应Provider的URI进行访问,访问方式如下:
private void insertContact1(){
ContentValues values = new ContentValues();
values.put(ContactsData.CONTACT_NAME, "James");
values.put(ContactsData.CONTACT_TELEPHONE, "18888888888");
values.put(ContactsData.CONTACT_CONTENT, "NBA Star");
values.put(ContactsData.CONTACT_CREATE_DATE, System.currentTimeMillis());
Uri uri = getContentResolver().insert(ContactsData.CONTENT_URI, values);
Log.d("Test", "uri = "+uri);
}
private void deleteContact1(){
int count = getContentResolver().delete(ContactsData.CONTENT_URI, ContactsData.CONTACT_NAME+"='James'", null);
Log.d("Test", "count = "+count);
}
private void updateContact1(){
ContentValues values = new ContentValues();
values.put(ContactsData.CONTACT_TELEPHONE, "16666666666");
int count = getContentResolver().update(ContactsData.CONTENT_URI,values, ContactsData.CONTACT_NAME+"='James'", null);
Log.d("Test", "count = "+count);
}
21、Android Service与Activity之间通信的几种方式
- binder + 回调(listener)
主要思路:Acitivity 将实例传入 Service,同时利用回调更新UI - binder + Handler
主要思路:Service 持有 Activity的Handler 对象,Service 通过往该Handler send message 的方式,达到通信的目的。 - 广播 (推荐LocalBroadcastManager)
主要思路:利用系统的LocalBroadcastManager,Service send message, Activity receive message; - 开源组件(EventBus,otto)
主要思路:利用反射或者注释的方式实现对注册类的注册,然后遍历当前的注册表,通过key进行查询,然后dispatch,调用相应的事件处理方法。(不同的开源框架实现有所区别) - AIDL
22、IntentService原理及作用是什么?
(1)IntentService继承Service,专门处理异步请求。
(2)客户端通过调用startService(Intent)发起请求,自然数据是通过Intent进行传递的。
(3)一旦Service启动后,对于Intent所传递的数据操作都通过工作线程(worker thread)进行处理。
(4)在完成数据的处理之后,Handler会回调其处理结果。在任务结束后会将自身服务关闭。
IntentService通过Handler、Message、Looper在Service中实现的异步线程消息处理的机制
23、说说Activity、Intent、Service 是什么关系
在Android中Activity主要是对界面进行控制处理,而一些耗时的任务我们就放入Service中,我们在Activity中启动Service的时候就是通过Intent来启动的,还可以在Intent中携带部分数据;当我需要Activity和Service 通信的时候,也可以通过Binder机制获取一个Service 对象。然后调用Service 中相应方法获取数据
24、ApplicationContext和ActivityContext的区别
ApplicationContext在app运行的 整个生命周期内都有效;而ActivityContext只针对当前Activity生命周期之内有效
25、SP是进程同步的吗?有什么方法做到同步?
不是,SP是基于文件储存的,不适用与多进程,可以采用加锁的方式实现同步;也可以考虑采用ContentProvider