[toc]
Android 2019 总结
手机卡顿
屏幕刷新机制
View 的 requestLayout 会调到ViewRootImpl 的 requestLayout方法,然后通过 scheduleTraversals 方法向Choreographer 提交一个绘制任务,然后再通过DisplayEventReceiver向底层请求vsync信号,当vsync信号来的时候,会通过JNI回调回来,通过Handler往主线程消息队列post一个异步任务,最终是ViewRootImpl去执行那个绘制任务,调用performTraversals方法,里面是View的三个方法的回调。
[图片上传失败...(image-c23b59-1579358489852)]
卡顿原因
那么有两个地方会造成掉帧,一个是主线程有其它耗时操作,导致doFrame没有机会在vsync信号发出之后16毫秒内调用,对应下图的3;还有一个就是当前doFrame方法耗时,绘制太久,下一个vsync信号来的时候这一帧还没画完,造成掉帧,对应下图的2。1是正常的
修改
- 基于消息队列的代表BlockCanary原理:Looper轮循的时候,每次从消息队列取出一条消息,如果logging不为空,就会调用 logging.println,我们可以通过设置Printer,计算Looper两次获取消息的时间差,如果时间太长就说明Handler处理时间过长,直接把堆栈信息打印出来,就可以定位到耗时代码。
- 插入空消息到消息队列:通过一个监控线程,每隔1秒向主线程消息队列的头部插入一条空消息。假设1秒后这个消息并没有被主线程消费掉,说明阻塞消息运行的时间在0~1秒之间。换句话说,如果我们需要监控3秒卡顿,那在第4次轮询中,头部消息依然没有被消费的话,就可以确定主线程出现了一次3秒以上的卡顿。
- 插桩:在方法入口和出口加入耗时监控的代码:
public void test(){
long startTime = System.currentTimeMillis();
doSomething();
long methodTime = System.currentTimeMillis() - startTime;//计算方法耗时
}
优化
内存泄露
内存泄露原因(一个长生命周期的对象持有了一个短生命周期的对象)
- 长生命周期对象持有Activity:
- 内部类形式使用handler,用于发送消息或者是执行耗时任务,在任务尚未完成时关闭Activity,这时候就会由于Handler持有Ac而导致ac没有被回收
- 内部类形式使用asyncTask也会导致内存泄露
- 错误的单例对象持有ac
- 注册操作没有对应的反注册
- 广播和service需要在onDestory进行反注册
- eventBus,rxJava需要在onDestory清除
- webView使用不当
- webView可以使用资源池进行复用
- 在ondestory进行移除操作
内存泄露检查方案
- LeakCanary
- mat
- adb shell && Memory Usage
内存抖动
原因
- 在短时间内有大量的对象被创建或者被回收的现象,主要是循环中大量创建、回收对象
- 当系统内存不足,不断GC内存的时候,也有可能出现内存抖动情况
启动优化
- 闪屏页优化
- 添加一个theme,设置背景图,待真正启动后在splashActivity的oncreate切换真正的theme,达到障眼法的效果
- MultipDex优化
- 在Application的attachBaseContext方法里,启动另外一个进程的LoadDexActivity去异步执行MultiDex.install,显示loading,加载完再跳回原进程。
- 第三方库懒加载
- 第三方库按需加载,不要一股脑都在application的oncreate()初始化,调用到的时候可以再初始化
- WebView优化
- 资源离线
- web端优化(合并请求,gzip压缩,Service Worker)
- 数据预加载&Native预请求数据(shouldInterceptRequest)
- webView缓存
- 线程优化
- 使用线程池进行优化,不要直接new 线程
- 系统调用优化
RecyclerView优化
- 减少布局嵌套,优化itemView
- 如果 Item 高度是固定的话,可以使用 RecyclerView.setHasFixedSize(true); 来避免 requestLayout 浪费资源
- 如果多个 RecycledView 的 Adapter 是一样的,比如嵌套的 RecyclerView 中存在一样的 Adapter,可以通过设置 RecyclerView.setRecycledViewPool(pool),来共用一个 RecycledViewPool。
消息机制
一个线程绑定一个Looper,一个Looper维护一个MessageQueue队列,而一个线程可以对应多个Handler
Handler Looper Message 关系是什么?
创建Handler的时候需要一个对应的Looper,所以在子线程中创建需要手动调用Looper.prepare(),而主线程因为在ActivityThread的main()中已经调用过了,所以不需要我们手动调用,而looper是通过sThreadLocal.get()返回的,所以每一个线程最多会维护一个Looper,Handler通过sendMessage发送了一个Message到MessageQueue中,而Looper在构造方法里面会生成一个MessageQueue--一个单向链表的消息队列,而通过Looper.loop(),进行了一个死循环的取Message操作,如果有message就取出去,如果没有就阻塞着,因为用到了管道模式(pipe)所以死循环也不会造成anr(因为阻塞着)。
Messagequeue 的数据结构是什么?为什么要用这个数据结构?
Messagequeue的数据结构是单向链表。
- 链表是单向的,增加删除节点简单,不会造成死循环,在MessQueue中并没有维护一个Message的集合,而是通过Message mMessages;这个变量,通过.next做到遍历循环。
如何在子线程中创建 Handler?
标准做法是应该
Looper.prepare();
create ..
Looper.loop();
Handler post 方法原理?
Handler的post方法,post了一个Runnable对象,之后通过
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}
把Runnaable包装成一个Message(塞进它的callback里面)返回,
而在Handler的dispatchMessage(Message msg)方法里做了对应处理,如果msg的callback不等于null,就调用handleCallback(msg),在方法里面直接run了callBack。同理还有View.Post()以及Activity.runOnUIThread()
/**
* Handle system messages here.
*/
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
private static void handleCallback(Message message) {
message.callback.run();
}
启动相关
启动模式以及使用场景?
singleInstance
打开一个新的栈里面打开一个Activity,常用于在service中启用Activity等
singleTask
就是指栈里只能有一个实例,当创建activity的时候,如果栈里已经存在实例了,那么它就会把他上面的activity全部都出栈,自己回到栈顶
singleTop
与singleTask类似,但是只有目标activity也位于栈顶的时候才会;否则就是正常的创建
standard
默认的创建方式,create几个就生成几个
onNewIntent()和onConfigurationChanged()
onNewIntent
只有当singleTask和singleTop成功生效的时候,即栈内有,且存在于栈顶(单指singleTop)的时候,这个时候系统不会重新创建,即不会走oncreate(),onresume()onstart(),指挥回调onNewIntent()。
onConfigurationChanged()
系统配置,常用的有配置页面旋转的时候不重新创建等。
Activity 到底是如何启动的
[图片上传失败...(image-cec966-1579358489852)]
Fragment相关
Fragment 生命周期和 Activity 对比
Activity的生命周期:
onCreate()->onStart()->onResume()->onPause()->onStop()->onDestroy()
Fragment的生命周期:
onAttach()->onCreate()->onCreateView()->onViewCreate()->onStart()->onResume()->onPause()->onStop()-onDestroyView()->onDestroy()->onDetach()
比如一个fragment加载在Activity里面,我们可以这么认为:创建时是activity的生命周期带动fragment的生命周期,
而销毁时是fragment的生命周期带动activity的生命周期,(Ps:这里的带动是指记忆,怎么记忆的带动,单指他们两同生共死的时候):
创建时:
- Activity走onCreate(); fFragment走 onAttach()->onCreate()->onCreateView()->onViewCreate()
- Activity走onStart(); Fragment走 onStart()
- Activity走onResume(); Fragment走 onResume()
销毁时:
- Fragment走onPause(),Activity走onPause()
- Fragment走onStop(),Activity走onStop()
2.Fragment走onDestroyView()->onDestroy()->onDetach(),Activity走onDestroy()
Fragment 之间如何进行通信
- 广播通信
- eventBus通信
- 通过加载的activity通信,fragment定义接口,在activity中实现它。
Fragment的startActivityForResult
在Fragment中存在startActivityForResult()以及onActivityResult()方法,需要通过调用getActivity().setResult(、Fragment.REQUEST_CODE, intent)来设置返回。
- 用getActivity方法发起调用,只有父Activity的onActivityResult会调用,Fragment中的onActivityResult不会被调用
- 直接发起startActivityForResult调用,当前的Fragment的onActivityResult,和父Activity的onActivityResult都会调用
- 用getParentFragment发起调用,则只有父Activity和父Fragment的onActivityResult会被调用,当前的Fragment的onActivityResult不会被调用。
这里2和3的前提是如果父activity中重写了onActivityResult,父Activity的onActivityResult中必须添加super.onActivityResult()
Fragment重叠问题
当屏幕旋转或者内存重启(Fragment以及容器activity被系统回收后再打开时重新初始化)会导致Fragment重叠问题,是因为activity本身重启的时候会恢复Fragment,然后创建Fragment的代码又会新建一个Fragment的原因。
解决方法:在onCreate方法中判断参数Bundle savedInstanceState,为空时初始化Fragment实例,然后在Fragment中通过onSaveInstanceState的方法恢复数据
Service
BroadcastReceiver
- 动态注册广播不是常驻型广播,生命周期跟随activity的生命周期,注意要在activity的onDestory()里面移除BroadcastReceiver;而静态注册广播是常驻型广播。
- 当广播为有序广播时,优先级高的先接受(不区分静态注册动态注册);但是同优先级下,动态注册的广播优于静态注册的广播。
抽象类和接口的区别
| 类型| 层次| 继承| 方法/属性|
| :-------- | --------:| :------: |:------: |:------: |
| 抽象类 | 对一整个类进行抽象 | 单继承|可以抽象,也可以不|
| 接口 | 对类的某个行为进行抽象| 子类可以实现多个接口,接口可以继承多个接口|必须是抽象|
事件分发
[图片上传失败...(image-2b153a-1579358489852)]
事件分发主要涉及三个方法:
- dispatchTouchEvent():分发传递点击事件,当点击事件nn能够传递给当前View的时候,该方法就会被调用
- onInterceptTouchEvent() :该方法仅存在于ViewGroup中,
- onTouchEvent():处理点击事件,在dispatchTouchEventn内部调用,判断是否拦截了某个事件,在ViewGroup的dispatchTouchEvent内部调用
源码讲解
--------------------------------------------------------
--------------Activity的事件分发------------------------
// 事件产生后调用入口
Activiry.dispatchTouchEvent()
--------------------------------------------------------
--------------以下为ViewGroup的事件分发------------------
//事件交给ViewGroup去处理,返回true说明事件被消费,无需执行Activity.onTouchEvent()
if(ViewGroup.dispatchTouchEvent()) {return true}
//disallowIntercept代表是否禁用拦截功能,默认是false,可通过调用requestDisallowInterceptTouchEvent()修改。
//onInterceptTouchEvent(ev)代表拦截器, !onInterceptTouchEvent(ev)值为true代表不拦截,false代表拦截。
//onInterceptTouchEvent()默认返回false,可通过复写该方法修改返回值。
if(disallowIntercept || !onInterceptTouchEvent(ev))
//当前ViewGroup没有拦截当前事件
//循环遍历当前ViewGroup的所有子View,通过x,y坐标找到被点击的View
//调用子View的dispatchTouchEvent()并依赖返回值返回ViewGroup.dispatchTouchEvent()方法
if (child.dispatchTouchEvent(ev)){ return true }
--------------------------------------------------------
--------------以下为View的事件分发------------------
//mOnTouchListener即为当前View是否setOnTouchListener()。
//(mViewFlags & ENABLED_MASK) == ENABLED 当前控件是否启用。
//mOnTouchListener.onTouch()值为复写的onTouch方法返回值是否为true。
//以上条件全部成立则返回true代表事件已消费
if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && mOnTouchListener.onTouch(this, event)){
return true
}
//以上条件有一个不成立则调用View.onTouchEvent()
return View.onTouchEvent();
//如果当前View可点击(CLICKABLE或LONG_CLICKABLE状态),则进入switch。
if (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
switch (event.getAction()) {
case ACTION_UP:
//如果当前为抬起事件则调用performClick()
performClick();
//如果mOnClickListener != null,mOnClickListener值通过View.setOnClickListener()赋值。
//由此可见onClick事件是通过ACTION_UP触发的,但是在此触发之前,onTouch事件已经触发,因此onTouch早于onClick。
if (mOnClickListener != null) {
mOnClickListener.onClick(this);
//返回到View的dispatchTouchEvent()方法中。
return true;
}
case ACTION_DOWN:
case ACTION_CANCEL:
case ACTION_MOVE:
}
//只要当前View是可点击的,则返回true;
return true;
}
// 若该控件不可点击,就一定返回false
return false;
//如果拦截了该事件或者用户点击到了空白处(未点到控件),则调用ViewGroup父类的的dispatchTouchEvent(),即View.dispatchTouchEvent()。
return super.dispatchTouchEvent(ev);
//没有控件处理,Activity自己处理
Activity.onTouchEvent()
//主要处理当前点击是否在边界外,true说明事件在边界外,即 消费事件。false则说明未消费。
PhoneWindow.shouldCloseOnTouch()?return ture:false
分析
- 事件的分发是由:Activity--->PhoneWindow--->DecorView--->ViewGroup--->View 这样的顺序下发的
- 在ViewGroup中可以通过onInterceptTouchEvent对事件进行拦截,返回true表示拦截,不再向子view传递,返回false表示不拦截,默认返回false。
- 子view如果将传递的事件消费掉,那么父布局将无法收到任何消息
热更新
了解的热更新
介绍热更新原理
classLoader
常见的classLoader类型
- 启动类加载器(Bootstrap ClassLoader):
用c++实现,是虚拟机的一部分。启动类加载器无法被 Java 程序直接引用,用户在编写自定义类加载器时,如果需要把加载请求委派给启动类加载器,直接使用 null 代替即可 - 扩展类加载器(Extension ClassLoader)
这个加载器由sun.misc.Launcher $ExtClassLoader实现,它负责加载<JAVA_HOME>\lib\ext目录中的,或者被java.ext.dirs系统变量所指定的路径中的所有类库,开发者可以直接使用扩展类加载器。 - 应用程序类加载器(Application ClassLoader)
这个类加载器由sun.misc.Launcher $App-ClassLoader实现。由于这个类加载器是ClassLoader中的getSystemClassLoader()方法的返回值,所以一般也称它为系统类加载器。它负责加载用户类路径(ClassPath)上所指定的类库,开发者可以直接使用这个类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。
对于Android的ClassLoader,我们更多需要了解: - DexClassLoader:可以加载任意目录下的dex/jar/apk/zip文件,比PathClassLoader更灵活,是实现热修复的重点。
- PathClassLoader:只能加载已经安装到Android系统中的apk文件(/data/app目录),是Android默认使用的类加载器。
组件化
目前比较成熟的组件化方案
玉刚说
介绍组件化原理
概念介绍
模块
模块指的是独立的业务模块,比如说“首页模块”,"个人中心模块",一个模块里面可以包含多个组件。
组件
组件指的是单一的功能组件,如 [视频组件]、[支付组件] 等,每个组件都可以以一个单独的 module 开发,并且可以单独抽出来作为 SDK 对外发布使用。
主要需要解决的问题
- 每个组件都是一个完整的整体,所以组件开发过程中要满足单独运行及调试的要求,这样还可以提升开发过程中项目的编译速度。
- 数据传递与组件间方法的相互调用。
- 组件间界面跳转,不同组件之间不仅会有数据的传递,也会有相互的页面跳转。在组件化开发过程中如何在不相互依赖的情况下实现互相跳转?(解耦)
- 主项目不直接访问组件中具体类的情况下,如何获取组件中 Fragment 的实例并将组件中的 Fragment 实例添加到主项目的界面中?
- 组件开发完成后相互之间的集成调试如何实现?还有就是在集成调试阶段,依赖多个组件进行开发时,如果实现只依赖部分组件时可以编译通过?这样也会降低编译时间,提升效率。
- 组件解耦的目标以及如何实现代码隔离?不仅组件之间相互隔离,还有第五个问题中模块依赖组件时可以动态增删组件,这样就是模块不会对组件中特定的类进行操作,所以完全的隔绝模块对组件中类的使用会使解耦更加彻底,程序也更加健壮。
问题解决
- 每个单独的module都添加一个gradle.properties配置文件,通过在其中配置isRunAlone作为是否独立运行的标志,用于生成配置applicationID和AndroidManifest
- 添加一个依赖于base的componentbase组件,由它提供service,各个组件实现接口,通过它来实现解耦以及各个组件之间的通信
- module的application问题:提供application的interface,各个module的application都实现,把init方法和initdata方法放在对应的接口,由app级别的application通过反射去进行初始化。
- 页面组件跳转使用arouter
- 使用runtimeOnly project(':login')进行代码隔离:依赖项仅在运行时对模块及其消费者可用,编译期间依赖项的代码对其消费者时完全隔离的。
- 使用**resourcePrefix **配置组件的资源文件
插件化
目前比较成熟的插件化方案
介绍插件化原理
线程池
线程
创建线程的三种方式
1. 继承 thread
2. 实现Runnable接口
3. 实现Callable接口
三种方法的比较
继承和实现的区别
java是单继承多实现的语言,如果选择了继承,那么只能继承Thread,而选择实现的话,就相对灵活很多
Runnable和Callable的区别
Runnable实现的是run方法,而Callable实现的是call方法,
call() 方法有返回值还能抛出异常, run() 方法则没有没有返回值,也不能抛出异常。
简介线程池
线程池可以理解为 管理多个线程,通过线程复用,减少创建与销毁操作,控制线程池中的线程的并发数,避免线程争夺CPU资源造成阻塞,对线程进行管理。
简而言之,线程池的好处是:
1. 减少创建与销毁线程带来的性能消耗
2. 可控制最大并发线程数,避免过多资源竞争而导致系统内存消耗
3. 能更好地控制线程的开启与回收,并且能定时执行任务
重要参数
[图片上传失败...(image-8c3eae-1579358489852)]
1. corePoolSize:核心线程数,如果运行的线程数少于corePoolSize,当有新的任务过来时会创建新的线程来执行这个任务,即使线程池中有其他空闲的线程
2. maximumPoolSize:线程池中允许的最大线程数
3. keepAliveTime:如果线程数多于核心线程数,那么这些多出来的线程如果空闲时间超过keepAliveTime将会被终止。
4. unit:keepAliveTime参数的时间单位。
5. workQueue:任务队列,通过线程池的execute方法会将任务Runnable存储在队列中。
6. threadFactory:线程工厂,用来创建新线程。
7. handler:添加任务出错时的策略捕获器,默认是ThreadPoolExecutor.AbortPolicy ,即添加任务出错就直接抛出异常 。
线程池种类
/// 创建一个并发数为3的线程池,即最大只有3个线程,当有的线程结束了,会创建出新的线程来进行补充
val executorService1 = Executors.newFixedThreadPool(3)
/// 创建一个单线程的线程池,即里面只有一个线程
val executorService2 = Executors.newSingleThreadExecutor()
/// 创建带有缓存的线程池,在执行任务中,如果线程池里面有可用的线程,就拿过来用,如果空闲了60s,那么空闲线程就会被移除
val executorService3 = Executors.newCachedThreadPool()
/// 创建定时和周期性的线程池
val executorService4 = Executors.newSingleThreadScheduledExecutor()
线程有哪些状态,哪些锁,各种锁的区别
常用方法:
- start
- start() 方法的作用是启动线程
- 该方法只能调用一次,再次调用不仅无法让线程再次执行,还会抛出非法线程状态异常。
- run
- run() 方法中放的是任务的具体逻辑,该方法由jvm调用,一般情况下开发者不需要直接调用该方法。
- 如果你调用了 run() 方法,加上 JVM 也调用了一次,那这个方法就会执行两次
- join
- 当调用join方法后,会进行阻塞,直到该线程任务执行结束,可以让线程顺序执行,如果线程 A 调用了线程 B 的 join() 方法,那线程 A 会进入等待状态,直到线程 B 运行结束。
- join() 方法导致的等待状态是可以被中断的,所以调用这个方法需要捕获中断异常
- Thread.currentThread()
- currentThread() 方法是一个静态方法,用于获取执行当前方法的线程,我们可以在任意方法中调用Thread.currentThread()获取当前线程,并设置它的名字和优先级等属性。
- Thread.yield()
- yield() 方法是一个静态方法,用于使当前线程放弃对处理器的占用,相当于是降低线程优先级。调用该方法就像是是对线程调度器说:“如果其他线程要处理器资源,那就给它们,否则我继续用”。
- 该方法不一定会让线程进入暂停状态。
- Thread.sleep(ms)
- sleep(ms) 方法是一个静态方法,用于使当前线程在指定时间内休眠(暂停)
六种状态
- 新建
- 可运行
- Object.notify()
- Object.notifyAll()
- LockSupport.unpark()
- 阻塞
- 等待
- Object.wait()
- LockSupport.park()
- Thread.join()
- 限时等待
- Thread.sleep(ms)
- Thread.join(ms)
- Object.wait(ms)
- LockSupport.parkNonos(ns)
- LockSupport.parkUntil(time)
- 终止
关于Synchronized
Synchronized是java的一个关键字,用于保证被Synchronized修饰的方法&代码,同一时刻最多只能被一个线程操作,其他线程必须等待该线程执行完后才能操作,从而达到保证线程安全,解决多线程中的并发同步问题(阻塞型并发)
Synchronized种类
[图片上传失败...(image-3ce19b-1579358489852)]
/**
* 对象锁
*/
public class Test{
// 对象锁:形式1(方法锁)
public synchronized void Method1(){
System.out.println("我是对象锁也是方法锁");
try{
Thread.sleep(500);
} catch (InterruptedException e){
e.printStackTrace();
}
}
// 对象锁:形式2(代码块形式)
public void Method2(){
synchronized (this){
System.out.println("我是对象锁");
try{
Thread.sleep(500);
} catch (InterruptedException e){
e.printStackTrace();
}
}
}
}
/**
* 方法锁(即对象锁中的形式1)
*/
public synchronized void Method1(){
System.out.println("我是对象锁也是方法锁");
try{
Thread.sleep(500);
} catch (InterruptedException e){
e.printStackTrace();
}
}
/**
* 类锁
*/
public class Test{
// 类锁:形式1 :锁静态方法
public static synchronized void Method1(){
System.out.println("我是类锁一号");
try{
Thread.sleep(500);
} catch (InterruptedException e){
e.printStackTrace();
}
}
// 类锁:形式2 :锁静态代码块
public void Method2(){
synchronized (Test.class){
System.out.println("我是类锁二号");
try{
Thread.sleep(500);
} catch (InterruptedException e){
e.printStackTrace();
}
}
}
}
synchronized 修饰实例方法和修饰静态方法有啥不一样。
1. 修饰 实例方法 / 代码块时,(同步)保护的是同一个对象方法的调用 & 当前实例对象
2. 修饰 静态方法 / 代码块时,(同步)保护的是 静态方法的调用 & class 类对象
Glide
Glide和Fresco的区别
Glide
1. 多种图片格式的缓存,适用于更多的内容表现形式(如Gif、WebP、缩略图、Video)
2. 生命周期集成(根据Activity或者Fragment的生命周期管理图片加载请求)
3. 高效处理Bitmap(bitmap的复用和主动回收,减少系统回收压力)
4. 高效的缓存策略,灵活(Picasso只会缓存原始尺寸的图片,Glide缓存的是多种规格),加载速度快且内存开销小(默认Bitmap格式的不同,使得内存开销是Picasso的一半)
Fresco
1. 最大的优势在于5.0以下(最低2.3)的bitmap加载。在5.0以下系统,Fresco将图片放到一个特别的内存区域(Ashmem区)
2. 大大减少OOM(在更底层的Native层对OOM进行处理,图片将不再占用App的内存)
适用于需要高性能加载大量图片的场景
自己封装一个图片加载库
- 异步加载:至少两个线程池(缓存和网络)
- 线程切换:使用handler
- 缓存:LruCache,DiskLruCache
- 防止oom:软应用,LruCache,图片压缩,bit像素存储
- 内存泄漏:生命周期管理,ImageView正确引用
- 列表图片错位:imageView加tag
kotlin
进程间通信
ListView与RecyclerView的对比
特点 | 具体描述 | 相对于listView |
---|---|---|
1. View组件复用性高 | 缓存RecyclerView.ViewHolder(Adapter必须遵循ViewHolder的设计模式 ,强制实现ViewHolder,优化了性能); |
2.四层缓存,可以支持自定义缓存逻辑,所有recyclerView公用一个缓存池
| 1. ListView缓存了View,需要实现getView(),实现复用的性能优化
- 两层缓存,快速重用屏幕上的列表项itemView,而不需要重新createView和bindView;缓存离开屏幕的itemView,可以让即将进入屏幕的item复用|
| 样丰富 | 1,通过layoutManager可以实现不同的风格;
2, 可以快速实现增删动画和item分割线| 1,只能实现线性平铺布局,2,只提供了divider属性提供分割线 |
Rxjava
[图片上传失败...(image-36e5e3-1579358489852)]
屏幕适配(换肤)
socket
定义
- 即套接字,是应用层与TCP/IP协议族通信的中间软件抽象层,表现为一个封装了TCP/IP协议族的编程接口(api)
- socket不是一种协议,而是一个编程调用接口(api),属于传输层,主要解决数据如何在网络中传输。
- 通过socket,我们才可以在android平台上通过tcp/ip协议进行开发
- 对用户来说,只需调用socket去组织数据,以符合指定的协议,即可通信。
- 成对出现,一对套接字
Socket={(ip地址1:PORT端口号),(ip地址2:PORT端口号)}
- 一个Socket实例 唯一代表一个主机上的一个应用程序的通信链路
使用
客户端
- 建立一个Socket实例
- 操作系统将为该Socket实例分配一个未被使用的本地端口号
- 操作系统将创建一个含本地、远程地址、端口号的套接字数据结构,且一直保存该数据结构到链接关闭
- 在创建Socket实例的构造函数正确返回前,进行
- TCP握手协议完成后,Socket实例对象将创建完成,否则跑出io异常。
服务端
- 创建一个ServerSocket实例
- 操作系统将为该ServerSocket实例创建一个底层数据结构,包含指定监听的端口号,包含指定监听的端口号,包含监听地址的通配符(*)
- 调用accept(),将进入阻塞状态,等待客户端的请求
- 当一个新的请求到来时,将为该链接创建一个新的套接字数据结构,包含请求源地址和端口,关联到ServerSocket实例的一个未完成的链接数据结构列表中。
原理分类
- 流套接字(StreamSocket):基于tcp协议,采用流的方式提供可靠的字节流服务
- 数据报套接字(DatagramSocket):基于udp协议,采用数据报文提供数据打包发送的服务
Socket与Http的对比
* Socket属于传输层,因为tcp/ip属于传输层,解决的是数据如何在网络中传输中的问题
* Http协议属于应用层,解决的是如何包装数据
* 由于二者属于不同层面,所以本来是没有可比性的,但是随着发展,http封装了下面几层的使用,所有才有了对比:
区别
- http采用了请求--响应方式
- 即建立了网络连接后,当客户端向服务器发送请求后,服务器才向客户端返回数据
- Socket采用了服务器主动发送数据的方式
- 即建立了网络连接后,服务器可主动发送消息给客户端,而不是等待客户端的请求