Android
FrameWork
架构图
1、Application
2、Application Framework
3、Library Android Runtime
4、Linux Kernel
包含内容
服务端
1、ActivityManagerService(AMS):内存管理、进程管理,统一调度所有应用程序中的Activity,所有Activity的启动必须通知AMS,决定进程的存活;
2、WindowManagerService(WMS):管理所有的窗口。客户端
1、ActivityThread:主线程
2、Activity:apk运行的最小单位
3、Window:提供一些通用的窗口操作API
4、PhoneWindow:
5、DecorView:对FrameLayout的修饰;
6、ViewRoot:吧WMS的IPC调用转换为本地的一个异步调用
7、W
8、WindowsManager:Linux驱动
与Framework相关:SurfaceFlingger,Binder,每一个窗口都对应一个Surface,SurfaceFlingger负责将画面显示到屏幕上,Binder负责进程间通信。
启动分析
应用程序启动分析
1、应用程序启动时,系统先创建一个Dalvik虚拟机进程,加载APK应用程序类及资源;
2、APK程序从main()方法开始执行。
- main()为UI线程创建一个消息队列;
- 创建ActivityThread对象,初始化时会创建一个Handler和一个Binder;
- UI主线程调用Looper.loop()方法进入消息循环体,不断从消息队列中读取并处理消息。
3、ActivityThread收到AMS发送的startActivity消息后,会创建指定Activity对象,Activity创建PhoneWindow类,PhoneWindow创建DecorView,内部包含Activity.setContentView(R.layout.XXX);
4、创建完成后,Activity将布局显示在屏幕上,调用WindowManager.addView()方法,创建一个ViewRoot对象,调用WMS提供的远程接口将窗口显示在屏幕上。
APK中的线程
- 主线程:UI线程,应用程序本身所在的线程,主要处理用户消息以及绘制程序界面;
- ViewRoot:W对象对应的线程,会启动一个对象负责接收Linux bInder驱动的IPC调用;
- Application Thread:启动一个线程用于IPC调用。
自定义Thread与UI线程的区别
- 自定义线程需要手动调用Looper.prepareLooper()为Thread创建消息队列;UI线程会自己调用创建消息队列。
- 不能直接给Thread对象发消息;可以直接给UI线程发消息;
APK程序的运行过程
1、ActivityThread从main()函数开始启动,调用prepareMainLooper()为UI线程创建一个消息队列;
2、创建ActivityThread对象;
3、UI线程调用Looper.loop()进入消息队列,不断读取并处理消息;
4、ActivityThread接收到AMS发送startActivity消息后创建Activity对象;
5、事件线程不断将用户操作消息发送到消息队列中,调用WMS的函数处理。
Acticity
概念
一个用户交互界面,是Context的子类。
生命周期
1、onCreate:创建;
2、onStart:启动,此时Activity可见但为出现在前台,不可交互。
3、onResume:继续、重新开始。可见且出现在前台,独占设备。
4、onPause:暂停。Activity跳转时或正常退出时执行。只有执行了该方法后另一个Activity才会启动。
5、onStop:停止。Activity已不可见,但Activity还在内存中,没有被销毁。主要执行资源回收工作。
6、onDestroy:毁灭。Activity被销毁,不可见。可释放未释放的资源,进行回收工作。
7、onRestart:重新开始。Activity此时可见,切换后再次打开会执行这个方法。
Activity状态
- running:当前显示在屏幕,位于任务栈的顶部,用户可见。
- poused:用户可见,但焦点已失去,无法交互。
- stopped:用户不可见,无法交互,activity被完全覆盖。
- killed:界面被销毁,等待系统回收。
dialog会不会影响Activity生命周期
不会
启动另一个Activity才会执行onPause。Dialog本质是一个View,不影响生命周期。
Activity启动模式
- 标准模式(standard):普通Activity;
- 栈顶复用模式(singleTop):接受推送消息的展示页。
- 栈内复用模式(singleTask):适合程序入口(启动页)
- 单例模式(singleInstance):适合需要与程序分离的页面。
Activity任务栈
前台栈清空后再清空后台栈
1、TaskAffinity能给Activity指定Task,但必须使用FLAG_ACTIVITY_NEW_TASK标记。
2、默认的taskAffinity的值是应用的包名。
退出Activity
1、直接调用finish();
2、记录打开的Activity,存入list中,需要退出时遍历关闭;
3、发送广播。Activity收到广播后关闭;
4、递归退出,打开新Activity使用startActivityForResult,添加标记后再onActivityResult中处理;
5、将Activity设置为SingleTop,启动时会把它上面的所有Activity关闭。
Activity之间传输数据的方法
- intent
- 广播接受者
- contentProvider
- static静态属性,public static静态成员变量
- 外部存储:File、SharedPreferences、Sqlite
横竖屏切换生命周期
- 不设置Activity的android:configChanges时,切屏会重新调用各个生命周期,并销毁当前生命周期。
- 设置android:configChanges="orientation|keyboardHidden|screenSize"时,切屏会不重新调用各个生命周期,只会执行onConfiguartionChanged方法
Activity跳转必然执行的方法
- MainActivity 启动 SecondActivity
Main.onCreate()---->Main.onStart()---->Main.onResume()-----“点击按钮,开启一个新的Activity”------>Main.onPause()---------> Second.onCreate()----> Second.onStart()----> Second.onResume()---->Main.onStop()- 点击Back按钮返回MainActivity
“点击Back按钮”--------->Second.onPause()---->Main.onReStart()---->Main.onStart()---->Main.onResume()---->Second.onStop()---->Second.onDestory()
将Activity设置为窗口
adnroid:theme="@android:style/Theme.Dialog"
Activitu-Window-View
Activity:四大组件之一,负责界面显示、用户交互与业务逻辑处理;
Window:负责页面展示以及交互,是Activity的下属。
View:Window是View的载体,View是Window中的元素。
Service
概念
服务,一个后台运行的组件,负责后台任务的处理。执行长时间运行且不用用户交互的任务。应用销毁后也依然可以工作。
Activity与Service绑定
Activity通过bindService与Service绑定,当绑定成功时Service会将代理对象通过回调的形式传给coon。
Activity中启动Service,一般想获取Service的服务对象需要通过bindService,日音乐播放器、第三方支付;若仅需开启一个后台任务可使用startService。
startService和bindService
生命周期
startService
onCreate->>onStart->>onStartCommand->>Running->>onDestory
bindService
onCreate->>onBind->>Running->>onUnBind->>onDestory
区别
startService启动的服务与启动者无必然联系;bindService启动的服务与启动者相关联。
应用场景
startService:后台长期任务;
bindService:短暂使用;
同时使用:启动一个后台服务长期进行任务,且在这个过程中需要与调用者进行交互。
Service是否能执行耗时操作
不能,Service运行在主线程main thread中,不能执行耗时操作。
IntentService
是Service的子类,解决了Service的问题:
- Service不会启动一条单独的进程;
- Service也不是专门一条新线程,因此不应该在Service中直接处理耗时操作。
IntentService特征:
- 会创建独立的worker线程来处理所有的Intent请求;
- 会创建独立的worker线程来处理onHandleIntent方法实现的代码,无序处理多线程问题;
- 所有请求处理完成后,IntentService会自动停止;
- 为Service的onBind提供默认实现;
- 为Service的onStartCommand提供默认实现。
Broadcast Receiver
注册
静态注册
在清单文件中进行配置。
常驻,应用程序关闭后也会被系统调用;
<receiver android:name=".BroadcastReceiver1">
<intent-filter>
<action android:name="android.intent.action.Call"></action>
</intent-filter>
</receiver>
动态注册
代码中注册。
不常驻,广播跟随程序的生命周期;
动态注册优先级高于静态注册。
receiver = new BroadcastReceiver();
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(CALL_ACTION);
context.registerReceiver(receiver, intentFilter);
作用
1、实现MVC架构,方便组件之间的信息和数据交互;
2、程序间互通消息
3、提高效率
4、监听者模式
类型
- 普通广播
- 系统广播:开机、网络状态变化
- 有序广播
- 粘性广播(已失效)
- APP应用内广播
耗时操作
onReceive中超过10s为执行完毕会被视为无响应,不能执行耗时操作。
生命周期
接收到广播时创建,onReceive方法结束后销毁。
理解
BroadCastReceiver 是 Android 四大组件之一,主要用于接收系统或者 app 发送的广播事件。
内部通信实现机制
通过 Android 系统的 Binder 机制实现通信。
无序广播
完全异步,逻辑上可以被任何广播接收者接收到。优点是效率较高。缺点是一个接收者不能将处理结果传递给下一个接收者,并无法终止广播intent 的传播。
有序广播
按照被接收者的优先级顺序,在被接收者中依次传播。前面的接受者可以终止广播传播,后面的接受者无法收到。
可使用Context.sendOrderedBroadcast(intent, receiverPermission, resultReceiver, scheduler, initialCode, initialData, initialExtras)指定 resultReceiver 广播接收者。这个接收者我们可以认为是最终接收者,通常情况下如果比他优先级更高的接收者如果没有终止广播,那么他的 onReceive 会被执行两次,第一次是正常的按照优先级顺序执行,第二次是作为最终接收者接收。如果比他优先级高的接收者终止了广播,那么他依然能接收到广播。
ContentProvider
数据存储方式
- File
- SharedPreference
- ContentProvider
- SQLiteDataBase
- 网络存储
批量操作
创建ContentProviderOperation数组,使用ContentResolver.applyBatch()将其分派给内容提供程序。
ContentProvider、ContentResolver、ContentObserver
- ContentProvider:内容提供者,对外提供数据
- ContentResolver.notifyChange(uri)发出消息
- ContentRsolver:内容解析者,用于获取内容提供者提供的数据
- ContentObserver:内容监听器,可以监听数的改变状态
- ContentResolver.registerContentObserver:监听消息
数据共享
自定义一个类继承ContentProvider,覆写query、insert、update、delete 等方法。因为其是四大组件之一因此必须在 AndroidManifest 文件中进行注册。把自己的数据通过 uri 的形式共享出去
android 系统下 不同程序 数据默认是不能共享访问,需要去实现一个类去继承 ContentProvider
优势
ContentProvider屏蔽了数据存储的细节,内部实现对用户完全透明,用户只需关心操作数据的uri即可,可实现不同app之间共享。
与SQL的区别
SQL只能查询本应用下的数据库,ContentProvider可以去增删改查本地文件。
Intent
传递类型
- java基本数据类型
- String数组
- Serializable
- Parcelable
Serializable和Parcelable
性能
使用内存时,Parcelable比Serializable性能高。
- Serializable在序列化时会产生大量的临时变量,从而引起频繁的GC;
- Parcelable不能在数据存储在磁盘上的情况上使用。
实现
Serializable
继承Serializable类;
Parcelable
在类中添加一个静态成员变量CREATOR继承Parcelable.Creator接口。
隐式、显式
显式:调用Intent.setComponent()或Intent.setClass()方法明确指定了组件名的Intent位显式意图,明确指定了Intent应该传递给哪个组件。多用于应用程序内部传递消息。
隐式:没有明确指定组件名的Intent,通过IntentFilter实现,系统会根据设置的action、category、Uri找到最适合的组件处理。广泛用于不用应用程序间传递信息。
Intent和IntentFilter
Intent
指定消息内容、明确的目的地。
IntentFilter
包括action(动作)、data(数据)、uri(uri)、category(附加信息)
ListView
更新
使用ListView的adapter的notifyDataSetChanged(),重新绘制ListView。
分页加载
设置ListView的滚动监听
setOnScrollListener(new OnScrollListener{…}),在监听器中调用onScrollStateChanged()和onScroll()。
滑动状态改变的状态:
- 手指按下移动:SCROLL_STATE_TOUCH_SCROLL;
- 惯性滚动:SCROLL_STATE_FLING;
- 静止:SCROLL_STATE_IDLE
对不同状态的处理
分批加载数据,只考虑静止状态。
上拉加载、下拉刷新
实现OnScrollListener 接口重写onScrollStateChanged 和onScroll方法,使用onscroll方法实现”滑动“后处理检查是否还有新的记录,如果有,调用 addFooterView,添加记录到adapter, adapter调notifyDataSetChanged 更新数据;如果没有记录了,把自定义的mFooterView去掉。使用onScrollStateChanged可以检测是否滚到最后一行且停止滚动然后执行加载。
优化图片
处理图片的方式
- 使用Options保存图片大小,不能保存到内存。
- 对图片进行边界压缩;
- 使用WeakReference代替强引用、SoftReference、 WeakHashMap 等的来存储图片信息。
- 在 getView 中做图片转换时,产生的中间变量一定及时释放。
异步加载图片思路
- 先从内存缓存中获取图片显示;
- 获取不到的话从SD卡里获取;
- 都获取不到则从网络下载图片并保存到SD卡同时加入内存。
提高效率
当converView为空时,桶setTag()方法为每个View绑定一个存放控件的ViewHolder对象;当converView不为空,重复利用已创建的view时,使用getTag()获取绑定的ViewHolder对象,避免了findViewById对控件的层层查询。
- 复用ConverView,使用历史的view;
- 自定义ViewHolder,减少findViewById的次数;
- 异步加载数据,分页加载数据;
- 使用WeakRefrence引用ImageView对象。
ScrollView嵌套ListView
直接添加会导致listView显示不全,只显示一条信息,因为两个控件滚动冲突。
解决方法
通过listView的item数量去计算listView的显示高度,使其完整展示。自定义ListView,重载onMeasure()方法,设置全部显示。
ListView为什么会出现图片错位
listView缓存convertView,不可见的图片被再次显示。
setEmptyView
ListView显示数据时,若数据为空,设置setEmptyView可显示提示视图。
Fragment
生命周期
- onAttach(Context context):关联fragment与acticity时调用,仅调用一次。
- onCreate():创建;
- View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState):绘制fragment,返回绘制布局的根视图;
- onActivityCreate():activity的onCreate执行完会调用;
- onStart():对用户可见时调用,前提是activity已started;
- onResume():和用户可交互时调用,前提是activity已resumed;
- onPause():和用户不可交互时调用;
- onStop():不可见时调用;
- onDestoryView():移除Fragment相关视图层级时调用;
- onDestory():清除;
- onDetach():fragment和activity解除关联。
不重新实例化切换fragment
切换时hide(A),add(B),再次切换时,hide(B),A.show()。
优点
1、将activity分离为多个可重用的组件,每个组件有各自的生命周期和UI;
2、方便创建动态灵活的UI设计;
3、独立模块,与activity绑定,可以动态运行移除、加入、交换;
4、提供新的方式在不同设备上统一UI;
5、解决activity间切换不流畅;
6、性能更好;
7、可嵌套。
压栈和出栈
Fragment的事务管理器内部维持了一个双向链表结构,记录每次add和replace操作,点击back时自动退栈。
replace和add
- add 的方法添加Fragment,每个 Fragment 只能添加一次,因此如果要想达到切换效果需要通过 Fragment 的的 hide 和 show 方法结合者使用。将要显示的 show 出来,将其他 hide起来。这个过程 Fragment 的生命周期没有变化。
- replace 切换 Fragment,每次都会执行上一个 Fragment 的 onDestroyView,新 Fragment的 onCreateView、onStart、onResume 方法。
传值
activity向fragment
将值放入bundle中,在activity中创建fragment,调用setArguments()传递到fragment中,fragment调用getArguments()可以得到bundle。
fragment向activity
- 在activity中调用getFragmentManager()得到fragmentManager,调用findFragmentByTag(tag)或findFragmentById(id)。
- 通过回调的方法,定义一个接口,在fragment调用,在activity实现这个接口。
ViewPager对生命周期的影响
ViewPager为了防止滑动出现卡顿,提供缓存机制,默认情况下ViewPager会创建并缓存当前页面左右两边的页面(如Fragment)。此时左右两个Fragment都会执行从onAttach->….->onResume的生命周期,明明Fragment没有显示却已经到onResume了,在某些情况下会出现问题。比如数据的加载时机、判断Fragment是否可见等。
性能优化
性能分析
- ddms
- Traceview:android特有的数据采集和分析工具,主要用于分析android中应用程序的瓶颈;
- heap:检查内存泄漏;
- allocation tracker:内存分配跟踪工具。
内存泄漏
android虚拟机基于寄存器的Dalvik,它的内存空间是有限的,一般是16M~24M,如果内存占用超过一定值就会出现OutOfMemory错误。
线程导致内存溢出
原因
线程生命周期不可控。
Thread只有在run函数不结束时才出现内存泄漏,而AsyncTask内部使用了ThreadPoolExcutor,生命周期不确定,应用程序无法控制,更容易出现内存泄漏。
解决方案
- 将线程的内部类改为静态内部类;
- 在线程内部采用弱引用保存Context引用。
OOM
- 图片过大
- 界面切换
- 查询数据库未关闭游标
- 构造Adapter时没有使用缓存的converView;
- Bitmap对象不再使用时调用recycle释放内存
处理图片过大导致的OOM
- 等比例缩小图片
- 软引用,及时使用recyle()操作
- 使用加载图片框架处理图片,如ImageLoader、BitmapUtils
捕获未捕获的异常
- 自定义一个Application,实现UncaughtExceptionHandler
- 覆写UncaughExcetionHandler的onCreate和uncayghtException
- 在AndroidManifest配置Application
ANR
应用程序响应不灵敏,系统向用户显示“应用程序无响应”对话框。
分类
- KeyDispatchTimeout(5s):按键或触摸事件在特定时间内无响应;
- BroadcastTimeout(10s):特定时间内无法处理;
- ServiceTimeout(20s):特定时间内无法处理;
原因
- 当前事件没有得到处理;
- 当前事件正在处理,但没有及时完成耗时工作。
多线程通信
进程
- 管道(pipe):半双工通信方式,数据单项流通。适用父子进程;
- 有名管道(namedpipe):半双工通信方式,允许不应援关系进程键通信;
- 信号量(semophore):计数器,控制多个进程对共享资源的访问。充当锁;
- 消息队列(messageQueue):消息链表,存放在内核中由消息标识符标识,克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。
- 共享内存(shared memory):映射一段能被其他进程所访问的内存。最快的IPC方式,通常与其他通信机制配合使用;
- 套接字(socket):可用于不同进程之间的通信。
线程
- 互斥锁:以排他方式方式数据结构被并发修改;
- 读写锁:允许多个线程同时读共享数据,互斥写;
- 条件变量:阻塞进程,直到特定条件为true,需要与互斥锁一起使用;
- 信号量:无名线程信号量、命名线程信号量;
- 信号:类似进程的信号。
android常用线程通信
- 共享内存
- 文件、数据库
- handler
- java方法(wait、notify、notifyAll)
Dalvik、linux、线程
Dalvik虚拟机运行在Linux操作系统至上;
Linux操作系统并没有纯粹的线程概念,只要进程间共享一个地址空间,就可以视为同一进程的两个线程。
虚拟机的进程和线程都是和目标机器本地操作系统的进程和线程一一对应的,可以使本地操作系统来调度进程和线程。
合理使用内存
- 资源回收
- 减少静态变量使用
- 大图片处理
- 动态注册监听
- 减少使用动画
- 注意程序逻辑,及时关闭控件
屏幕适配
适应各种屏幕尺寸、屏幕分辨率和屏幕像素密度
适配方式
- 支持各种屏幕尺寸,使用warp_content,match_parent,weight确保布局灵活性。
- 使用相对布局,禁用绝对布局。
- 使用.9图片
- ConstraintLayout
- dimens.xml
AIDL
Android interface definition Language(安卓接口定义语言)
作用
帮助我们发布以及调用远程服务,实现跨进程通信。
使用
将服务的AIDL放到对应的src目录,工程的gen目录会生成相应的接口类
通过bindService(Intent, ServiceConnect, int)绑定远程服务,需要复写bindService中ServiceConnect的onServiceConnented(CompontName, IBinder),参数中的IBinder就是AIDL中定义的接口。
通过IBinder获取到的对象,其实是系统产生的代理对象,作为一个中间件实现了进程间通信。
工作流程
编译器可以通过AIDL生成一段代码,通过预定义的接口达到两个进程内部通信进程跨界对象访问的目的。
- 引入AIDL相关类
- 调用AIDL产生的class。
可处理数据
基本数据类型、String、Bundle派生类。
动画
类型
- 帧动画
- 补间动画
- 属性动画
组合动画
使用AnimatorSet可以将动画组合在一起使用。
- play():播放当前动画
- with():将现有动画与传入动画同时执行
- after():现有动画插入到传入动画之后执行
- before():现有动画插入到传入动画之前执行
插值器
分类
- AccelerateDecelerateInterpolator :在动画开始与结束的地方速率改变比较慢,在中间的时候加速
- AccelerateInterpolator:在动画开始的地方速率改变比较慢,然后开始速率变化加快
- LinearInterpolator:以常量速率改变
- AnticipateInterpolator:开始的时候向后然后向前甩
- PathInterpolator:动画执行的效果按贝塞尔曲线
- anticipateOvershootInterpolator:开始的时候向后然后向前甩一定值后返回最后的值
- CycleInterpolator:动画循环播放特定的次数,速率改变沿着正弦曲线
- OvershootInterpolator:向前甩一定值后再回到原来位置
- BounceInterpolator:动画结束的时候有弹起效果
自定义插值器
实现Interpolator接口,定义getInterpolation方法。
修改Activity进入和退出动画
- 定义Activity的主题
- 通过覆写Activity的overridePendingTransition方法
事件处理
handler
充当子线程与主线程之间的桥梁。
声明在Activity中,覆写handlerMessage方法,在子线程调用handler.sendMessage()后就户在主线程中运行。
主线程中默认调用Looper.prepare(),在Looper中创建MessageQueue并把Looper绑定到当前线程中,调用sendMessage时把Message添加到Looper创建的MessageQueue队列中。
主线程默认执行Looper.looper(),该方法从Looper的成员变量MessageQueue中取出Message。
Looper对象要存在ThreadLocal中,为什么不能公用一个呢,或者每个线程持有一个呢?
- 线程安全:使用公用的Looper在进行线程同步处理时不能保证线程安全,需要加同步锁,降低运行效率,易产生ANR;
- 节约内存:每个线程持有一个会产生大量Looper实例,造成很大的内存开销,使用ThreadLocal为每个使用Looper的线程提供独立的变量副本,实际上只用从线程池中初始化少量的Looper对象即可。
ExecutorService threadPool = Executors.newFixedThreadPool(8);
用线程池的好处就是线程复用,如上的代码,只会实例出8个线程,而ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每个线程持有一个Looper对象,也就会初始化出8个Looper对象,很明显,用ThreadLocal节省了内存。
onTouch、onTouchEvent
都是在View的dispatchTouchEvent中调用的,onTouch优先于onTouchEvent执行。
如果onTouch返回true将事件消费,则onTouchEvent不会再执行。
onTouch执行条件:1、onTouchListener的值不能为空;2、当前点击的控件必须是enable的。
子线程中是否可以new handler
不能,会抛出异常
子线程不会主动调用Looper.prepere(),无法直接得到Looper对象;而主线程会主动调用Looper.prepare()。
子线程消息在主线程更新的其他方式
- runOnUiThread
- View.post(Runnable r)
为什么不建议在子线程中访问UI
android的UI访问是没有加锁的,多个线程可以同时访问更新同一个UI控件,线程不安全,在多线程模式中易发生错误,在UI线程中控制UI可以视为加锁。
OkHttp
1.Okhttp 基本实现原理
OkHttp 主要是通过 5 个拦截器和 3 个双端队列(2 个异步队列,1 个同步队列)工作。内部实现通过一个责任链模式完成,将网络请求的各个阶段封装到各个链条中,实现了各层的解耦。
OkHttp 的底层是通过 Socket 发送 HTTP 请求与接受响应,但是 OkHttp 实现了连接池的概念,即对于同一主机的多个请求,可以公用一个 Socket 连接,而不是每次发送完 HTTP 请求就关闭底层的 Socket,这样就实现了连接池的概念。而 OkHttp 对 Socket 的读写操作使用的 OkIo 库进行了一层封装。
执行流程:
通过构建者构建出OkHttpClient对象,再通过newCall方法获得RealCall请求对象.
通过RealCall发起同步或异步请求,而决定是异步还是同步请求的是由线程分发器dispatcher来决定.
当发起同步请求时会将请求加入到同步队列中依次执行,所以会阻塞UI线程,需要开启子线程执行.
当发起异步请求时会创建一个线程池,并且判断请求队列是否大于最大请求队列64,请求主机数是否大于5,如果大于请求添加到异步等待队列中,否则添加到异步执行队列,并执行任务.
2.Okhttp 网络缓存如何实现?
OKHttp 默认只支持 get 请求的缓存。
第一次拿到响应后根据头信息决定是否缓存。
下次请求时判断是否存在本地缓存,是否需要使用对比缓存、封装请求头信息等等。
如果缓存失效或者需要对比缓存则发出网络请求,否则使用本地缓存。
3.Okhttp 网络连接怎么实现复用?
HttpEngine 在发起请求之前,会先调用nextConnection()来获取一个Connection对象,如果可以从ConnectionPool中获取一个Connection对象,就不会新建,如果无法获取,就会调用createnextConnection()来新建一个Connection对象,这就是 Okhttp 多路复用的核心,不像之前的网络框架,无论有没有,都会新建Connection对象。
4.Dispatcher 的功能是什么?
Dispatcher中文是分发器的意思,和拦截器不同的是分发器不做事件处理,只做事件流向。他负责将每一次Requst进行分发,压栈到自己的线程池,并通过调用者自己不同的方式进行异步和同步处理。 通俗的讲就是主要维护任务队列的作用。
- 记录同步任务、异步任务及等待执行的异步任务。
- 调度线程池管理异步任务。
- 发起/取消网络请求 API:execute、enqueue、cancel。
Dispatcher 类,该类中维护了三个双端队列(Deque):
- readyAsyncCalls:准备运行的异步请求
- runningAsyncCalls:正在运行的异步请求
- runningSyncCalls:正在运行的同步请求
OkHttp 设置了默认的最大并发请求量 maxRequests = 64 和单个 Host 主机支持的最大并发量 maxRequestsPerHost = 5
5.addInterceptor 与 addNetworkInterceptor 的区别?
二者通常的叫法为应用拦截器和网络拦截器,从整个责任链路来看,应用拦截器是最先执行的拦截器,也就是用户自己设置request属性后的原始请求,而网络拦截器位于ConnectInterceptor和CallServerInterceptor之间,此时网络链路已经准备好,只等待发送请求数据。
1、首先,应用拦截器在RetryAndFollowUpInterceptor和CacheInterceptor之前,所以一旦发生错误重试或者网络重定向,网络拦截器可能执行多次,因为相当于进行了二次请求,但是应用拦截器永远只会触发一次。另外如果在CacheInterceptor中命中了缓存就不需要走网络请求了,因此会存在短路网络拦截器的情况。
2、其次,如上文提到除了CallServerInterceptor,每个拦截器都应该至少调用一次realChain.proceed方法。实际上在应用拦截器这层可以多次调用proceed方法(本地异常重试)或者不调用proceed方法(中断),但是网络拦截器这层连接已经准备好,可且仅可调用一次proceed方法。
3、最后,从使用场景看,应用拦截器因为只会调用一次,通常用于统计客户端的网络请求发起情况;而网络拦截器一次调用代表了一定会发起一次网络通信,因此通常可用于统计网络链路上传输的数据。
6、Okhttp 拦截器的作用是什么?
应用拦截器
拿到的是原始请求,可以添加一些自定义header、通用参数、参数加密、网关接入等等。
1、RetryAndFollowUpInterceptor 处理错误重试和重定向
2、BridgeInterceptor 应用层和网络层的桥接拦截器,主要工作是为请求添加cookie、添加固定的header,比如Host、Content-Length、Content-Type、User-Agent等等,然后保存响应结果的cookie,如果响应使用gzip压缩过,则还需要进行解压。
3、CacheInterceptor 缓存拦截器,如果命中缓存则不会发起网络请求。
4、ConnectInterceptor 连接拦截器,内部会维护一个连接池,负责连接复用、创建连接(三次握手等等)、释放连接以及创建连接上的socket流。网络拦截器
用户自定义拦截器,通常用于监控网络层的数据传输。
CallServerInterceptor 请求拦截器,在前置准备工作完成后,真正发起了网络请求。
7、Okhttp 有哪些优势?
- 支持 http2,对一台机器的所有请求共享同一个 Socket
- 内置连接池,支持连接复用,减少延迟
- 支持透明的 gzip 压缩响应体
- 响应缓存可以完全避免网络重复请求
- 请求失败时自动重试主机的其他 ip,自动重定向
- 丰富的 API,可扩展性好
8、response.body().string() 为什么只能调用一次?
我们可能习惯在获取到Response对象后,先response.body().string()打印一遍 Log,再进行数据解析,却发现第二次直接抛异常,其实直接跟源码进去看就发现,通过source拿到字节流以后,直接调用closeQuietly()方法关闭了,这样第二次再去通过source读取就直接流已关闭的异常了。
public final String string() throws IOException {
BufferedSource source = source();
try {
Charset charset = Util.bomAwareCharset(source, charset());
return source.readString(charset);
} finally {
//这里讲resource给悄悄close了
Util.closeQuietly(source);
}
}
解决方案:
1.内存缓存一份response.body().string();
2.自定义拦截器处理 Log。
9、Okhttp 运用了哪些设计模式?
Okhttp 运用了六种设计模式:
- 构造者模式(OkhttpClient,Request 等各种对象的创建)
- 工厂模式(在 Call 接口中,有一个内部工厂 Factory 接口。)
- 单例模式(Platform 类,已经使用 Okhttp 时使用单例)
- 策略模式(在 CacheInterceptor 中,在响应数据的选择中使用了策略模式,选择缓存数据还是选择网络访问。)
- 责任链模式(拦截器的链式调用)
- 享元模式(Dispatcher 的线程池中,不限量的线程池实现了对象复用)
其余
开发框架
- EventBus(时间处理)
- JPush(推送)
- 百度地图
- Gson(解析json)
- zxing(二维码扫描)
- Glide(图片处理)
- okHttp
三级缓存
- 缓存加载
- 本地文件加载
- 网络加载
清除缓存
- 清除内存的缓存
- 数据库、SD
推送
- 及时主动性
- 针对目的性
- 便捷高效性
json好处
- 易读性
- 高效率
项目流程
- 立项
- 需求
- 开发
- 测试
- 上线
- 维护
- 升级
自定义View关键词
基础:坐标系、角度弧度、颜色
进阶:Canvas、path、贝塞尔曲线、矩阵、时间分发处理、测量
自定义View
组合控件、自绘控件、继承控件
组件化
- 组件化将通用模块独立出来,统一管理,以提高复用,将页面拆分为粒度更小的组件,组件内部出了包含UI实现,还可以包含数据层和逻辑层
- 每个组件度可以独立编译、加快编译速度、独立打包。
- 每个工程内部的修改,不会影响其他工程。
业务库工程可以快速拆分出来,集成到其他App中。- 迭代频繁的业务模块采用组件方式,业务线研发可以互不干扰、提升协作效率,并控制产品质量,加强稳定性。
- 并行开发,团队成员只关注自己的开发的小模块,降低耦合性,后期维护方便等。
插件化
在一个大的项目里面,为了明确的分工,往往不同的团队负责不同的插件APP,这样分工更加明确。各个模块封装成不同的插件APK,不同模块可以单独编译,提高了开发效率。
- 解决了上述的方法数超过限制的问题。可以通过上线新的插件来解决线上的BUG,达到“热修复”的效果。
- 减小了宿主APK的体积。
打包
1、通过AAPT工具进行资源文件的打包,生成R.java文件;
2、通过AIDL工具处理AIDL文件,生成相应Java文件;
3、通过Javac工具编译项目源码,生成Class文件;
4、通过DX工具将所有的Class文件转换成DEX文件,将Java字节码转换成Dalvik字节码,压缩常量池以及清除冗余信息。
5、通过ApkBuilder工具将资源文件、DEX文件打包成APK文件;
6、利用KeyStore对生成的APK文件签名;
7、(正式版APK)使用ZipAlign工具进行对其处理,将APK文件中所有的资源文件举例文件的起始距离偏移4字节的整数倍,这样通过内存映射访问的APK文件的速度会更快。
安装流程
1、复制APK到/data/app目录,解压扫描安装包;
2、资源管理器解析APK里的资源文件;
3、解析AndroidManifest文件,在/data/data/目录下创建对应的应用数据目录;
4、对dex文件进行优化,并保存在delvik-cache目录下;
5、将AndroidManifest文件解析出的四大组件信息注册到PackageManagerService中;
6、安装完成后,发送广播。
防止反编译(提高反编译难度)
由于apk是Android虚拟机加载的,加密apk后Dalvik无法识别apk。
处理方法
- 关键代码使用jni调用本地代码,或使用c或c++编写;
- 混淆java代码;
- 使用第三方加固。
v1、v2签名结果
v1
在v1中只对未压缩的文件内容进行验证,在APK签名之后可以进行很多修改;
v2
v2签名验证了归档中所有字节,而不是单独的ZIP条目。若构建过程中有任何定制任务,v2签名可能会失效,导致APK与Android7.0及以上版本不兼容。
对称加密、非对称加密
对称加密
加密、解密使用同一个key,使用DES算法;
非对称加密
加密、解密使用不同的key,使用RSA算法。ssh和ssl是典型的非对称加密。
进程保活方案
- 利用系统广播拉活
- 利用系统Service机制拉活
- 利用Native进程拉活
- 利用JobScheduler机制拉活
- 利用账号同步机制拉活
Binder机制
Linux中通信机制:1、管道;2、文件共享;3、Socket;4、信号量;
优势:
- 只需拷贝一次;
- 基于cs架构,易用性高;
- 为每个APP分配UID,同时支持实名和匿名。
URI、URL
URI
统一资源标识符,用来唯一的标识一个资源。
组成部分:1、访问资源的命名机制;2、存放资源的主机名;3、资源自身的名称,由路径表示,着重强调于资源。
URL
统一资源定位器,是一种具体的URI,既可以用来标识一个资源,还指明了如何locate这个资源。
组成部分:1、协议;2、存放该资源的主机ip地址;3、主机资源的具体地址。
TLS/SSL握手
主要交换了数字证书、三个随机数、加密通信协议。
- ClientHello:发送支持的协议版本、对话密钥随机数、加密方式、压缩方法;
- ServerHello:确认协议版本、加密方式、服务器证书;
- ClientRe:检验证书合法性,使用公钥加密,将握手信息加密发送;
- ServerRe:用私钥取出密码,密码解密握手信息,使用密码加密握手信息发送给浏览器。