# Android 2019 总结

文章目录

  • Android 2019 总结
    • 手机卡顿
      • 屏幕刷新机制
      • 卡顿原因
      • 修改
    • 优化
      • 内存泄露
        • 内存泄露原因(一个长生命周期的对象持有了一个短生命周期的对象)
        • 内存泄露检查方案
      • 内存抖动
        • 原因
      • 启动优化
      • RecyclerView优化
      • Handler Looper Message 关系是什么?
      • Messagequeue 的数据结构是什么?为什么要用这个数据结构?
      • 如何在子线程中创建 Handler?
      • Handler post 方法原理?
    • 启动相关
      • 启动模式以及使用场景?
        • singleInstance
        • singleTask
        • singleTop
        • standard
      • onNewIntent()和onConfigurationChanged()
        • onNewIntent
        • onConfigurationChanged()
      • Activity 到底是如何启动的
    • Fragment相关
      • Fragment 生命周期和 Activity 对比
      • Fragment 之间如何进行通信
      • Fragment的startActivityForResult
    • Service
    • BroadcastReceiver
    • 抽象类和接口的区别
    • 事件分发
      • 分析
    • 热更新
      • 了解的热更新
      • 介绍热更新原理
        • classLoader
          • 常见的classLoader类型
    • 组件化
      • 目前比较成熟的组件化方案
      • 介绍组件化原理
        • 概念介绍
          • 模块
          • 组件
        • 主要需要解决的问题
        • 问题解决
    • 插件化
      • 目前比较成熟的插件化方案
      • 介绍插件化原理
    • 线程池
      • 线程
        • 创建线程的三种方式
        • 三种方法的比较
          • 继承和实现的区别
          • Runnable和Callable的区别
      • 简介线程池
        • 重要参数
        • 线程池种类
      • 线程有哪些状态,哪些锁,各种锁的区别
        • 常用方法:
        • 六种状态
        • 关于Synchronized
        • Synchronized种类
      • synchronized 修饰实例方法和修饰静态方法有啥不一样。
    • Glide
      • Glide和Fresco的区别
        • Glide
        • Fresco
      • 自己封装一个图片加载库
    • kotlin
    • 进程间通信
    • ListView与RecyclerView的对比
    • Rxjava
    • 屏幕适配(换肤)
    • socket
      • 定义
      • 使用
        • 客户端
        • 服务端
      • 原理分类
      • Socket与Http的对比
        • 区别

Android 2019 总结

手机卡顿

屏幕刷新机制

View 的 requestLayout 会调到ViewRootImpl 的 requestLayout方法,然后通过 scheduleTraversals 方法向Choreographer 提交一个绘制任务,然后再通过DisplayEventReceiver向底层请求vsync信号,当vsync信号来的时候,会通过JNI回调回来,通过Handler往主线程消息队列post一个异步任务,最终是ViewRootImpl去执行那个绘制任务,调用performTraversals方法,里面是View的三个方法的回调。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LlcEezYh-1579358399384)(./1571622381383.png)]

卡顿原因

那么有两个地方会造成掉帧,一个是主线程有其它耗时操作,导致doFrame没有机会在vsync信号发出之后16毫秒内调用,对应下图的3;还有一个就是当前doFrame方法耗时,绘制太久,下一个vsync信号来的时候这一帧还没画完,造成掉帧,对应下图的2。1是正常的

修改

  1. 基于消息队列的代表BlockCanary原理:Looper轮循的时候,每次从消息队列取出一条消息,如果logging不为空,就会调用 logging.println,我们可以通过设置Printer,计算Looper两次获取消息的时间差,如果时间太长就说明Handler处理时间过长,直接把堆栈信息打印出来,就可以定位到耗时代码。
  2. 插入空消息到消息队列:通过一个监控线程,每隔1秒向主线程消息队列的头部插入一条空消息。假设1秒后这个消息并没有被主线程消费掉,说明阻塞消息运行的时间在0~1秒之间。换句话说,如果我们需要监控3秒卡顿,那在第4次轮询中,头部消息依然没有被消费的话,就可以确定主线程出现了一次3秒以上的卡顿。
  3. 插桩:在方法入口和出口加入耗时监控的代码:
public void test(){
    long startTime = System.currentTimeMillis();
    doSomething();
    long methodTime = System.currentTimeMillis() - startTime;//计算方法耗时
}

优化

内存泄露

内存泄露原因(一个长生命周期的对象持有了一个短生命周期的对象)

  1. 长生命周期对象持有Activity:
  • 内部类形式使用handler,用于发送消息或者是执行耗时任务,在任务尚未完成时关闭Activity,这时候就会由于Handler持有Ac而导致ac没有被回收
  • 内部类形式使用asyncTask也会导致内存泄露
  • 错误的单例对象持有ac
  1. 注册操作没有对应的反注册
  • 广播和service需要在onDestory进行反注册
  • eventBus,rxJava需要在onDestory清除
  1. webView使用不当
  • webView可以使用资源池进行复用
  • 在ondestory进行移除操作

内存泄露检查方案

  1. LeakCanary
  2. mat
  3. adb shell && Memory Usage

内存抖动

原因

  • 在短时间内有大量的对象被创建或者被回收的现象,主要是循环中大量创建、回收对象
  • 当系统内存不足,不断GC内存的时候,也有可能出现内存抖动情况

启动优化

  1. 闪屏页优化
  • 添加一个theme,设置背景图,待真正启动后在splashActivity的oncreate切换真正的theme,达到障眼法的效果
  1. MultipDex优化
  • 在Application的attachBaseContext方法里,启动另外一个进程的LoadDexActivity去异步执行MultiDex.install,显示loading,加载完再跳回原进程。
  1. 第三方库懒加载
  • 第三方库按需加载,不要一股脑都在application的oncreate()初始化,调用到的时候可以再初始化
  1. WebView优化
  • 资源离线
  • web端优化(合并请求,gzip压缩,Service Worker)
  • 数据预加载&Native预请求数据(shouldInterceptRequest)
  • webView缓存
  1. 线程优化
  • 使用线程池进行优化,不要直接new 线程
  1. 系统调用优化

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发送了一个MessageMessageQueue中,而Looper在构造方法里面会生成一个MessageQueue–一个单向链表的消息队列,而通过Looper.loop(),进行了一个死循环的取Message操作,如果有message就取出去,如果没有就阻塞着,因为用到了管道模式(pipe)所以死循环也不会造成anr(因为阻塞着)。

Messagequeue 的数据结构是什么?为什么要用这个数据结构?

Messagequeue的数据结构是单向链表

  1. 链表是单向的,增加删除节点简单,不会造成死循环,在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

只有当singleTasksingleTop成功生效的时候,即栈内有,且存在于栈顶(单指singleTop)的时候,这个时候系统不会重新创建,即不会走oncreate(),onresume()onstart(),指挥回调onNewIntent()。

onConfigurationChanged()

系统配置,常用的有配置页面旋转的时候不重新创建等。

Activity 到底是如何启动的

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NsBAsKVw-1579358399386)(./20180724151603814 1.png)]

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:这里的带动是指记忆,怎么记忆的带动,单指他们两同生共死的时候):
####创建时:

  1. Activity走onCreate(); fFragment走 onAttach()->onCreate()->onCreateView()->onViewCreate()
  2. Activity走onStart(); Fragment走 onStart()
  3. Activity走onResume(); Fragment走 onResume()
    ####销毁时:
  4. Fragment走onPause(),Activity走onPause()
  5. Fragment走onStop(),Activity走onStop()
    2.Fragment走onDestroyView()->onDestroy()->onDetach(),Activity走onDestroy()

Fragment 之间如何进行通信

  1. 广播通信
  2. eventBus通信
  3. 通过加载的activity通信,fragment定义接口,在activity中实现它。

Fragment的startActivityForResult

在Fragment中存在startActivityForResult()以及onActivityResult()方法,需要通过调用getActivity().setResult(、Fragment.REQUEST_CODE, intent)来设置返回。

  1. 用getActivity方法发起调用,只有父Activity的onActivityResult会调用,Fragment中的onActivityResult不会被调用
  2. 直接发起startActivityForResult调用,当前的Fragment的onActivityResult,和父Activity的onActivityResult都会调用
  3. 用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

  1. 动态注册广播不是常驻型广播,生命周期跟随activity的生命周期,注意要在activity的onDestory()里面移除BroadcastReceiver;而静态注册广播是常驻型广播。
  2. 当广播为有序广播时,优先级高的先接受(不区分静态注册动态注册);但是同优先级下,动态注册的广播优于静态注册的广播。

抽象类和接口的区别

类型 层次 继承 方法/属性
抽象类 对一整个类进行抽象 单继承 可以抽象,也可以不
接口 对类的某个行为进行抽象 子类可以实现多个接口,接口可以继承多个接口 必须是抽象

事件分发

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dQ2APO61-1579358399387)(./6332336-66a71231ab40cedf.png)]

事件分发主要涉及三个方法:

  1. dispatchTouchEvent():分发传递点击事件,当点击事件nn能够传递给当前View的时候,该方法就会被调用
  2. onInterceptTouchEvent() :该方法仅存在于ViewGroup中,
  3. 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      

分析

  1. 事件的分发是由:Activity—>PhoneWindow—>DecorView—>ViewGroup—>View 这样的顺序下发的
  2. 在ViewGroup中可以通过onInterceptTouchEvent对事件进行拦截,返回true表示拦截,不再向子view传递,返回false表示不拦截,默认返回false。
  3. 子view如果将传递的事件消费掉,那么父布局将无法收到任何消息

热更新

了解的热更新

介绍热更新原理

classLoader

常见的classLoader类型
  1. 启动类加载器(Bootstrap ClassLoader):
    用c++实现,是虚拟机的一部分。启动类加载器无法被 Java 程序直接引用,用户在编写自定义类加载器时,如果需要把加载请求委派给启动类加载器,直接使用 null 代替即可
  2. 扩展类加载器(Extension ClassLoader)
    这个加载器由sun.misc.Launcher $ExtClassLoader实现,它负责加载<JAVA_HOME>\lib\ext目录中的,或者被java.ext.dirs系统变量所指定的路径中的所有类库,开发者可以直接使用扩展类加载器。
  3. 应用程序类加载器(Application ClassLoader)
    这个类加载器由sun.misc.Launcher $App-ClassLoader实现。由于这个类加载器是ClassLoader中的getSystemClassLoader()方法的返回值,所以一般也称它为系统类加载器。它负责加载用户类路径(ClassPath)上所指定的类库,开发者可以直接使用这个类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。
    对于Android的ClassLoader,我们更多需要了解:
  4. DexClassLoader:可以加载任意目录下的dex/jar/apk/zip文件,比PathClassLoader更灵活,是实现热修复的重点。
  5. PathClassLoader:只能加载已经安装到Android系统中的apk文件(/data/app目录),是Android默认使用的类加载器。

组件化

目前比较成熟的组件化方案

玉刚说

介绍组件化原理

概念介绍

模块

模块指的是独立的业务模块,比如说“首页模块”,“个人中心模块”,一个模块里面可以包含多个组件。

组件

组件指的是单一的功能组件,如 [视频组件]、[支付组件] 等,每个组件都可以以一个单独的 module 开发,并且可以单独抽出来作为 SDK 对外发布使用。

主要需要解决的问题

  1. 每个组件都是一个完整的整体,所以组件开发过程中要满足单独运行及调试的要求,这样还可以提升开发过程中项目的编译速度。
  2. 数据传递与组件间方法的相互调用。
  3. 组件间界面跳转,不同组件之间不仅会有数据的传递,也会有相互的页面跳转。在组件化开发过程中如何在不相互依赖的情况下实现互相跳转?(解耦)
  4. 主项目不直接访问组件中具体类的情况下,如何获取组件中 Fragment 的实例并将组件中的 Fragment 实例添加到主项目的界面中?
  5. 组件开发完成后相互之间的集成调试如何实现?还有就是在集成调试阶段,依赖多个组件进行开发时,如果实现只依赖部分组件时可以编译通过?这样也会降低编译时间,提升效率。
  6. 组件解耦的目标以及如何实现代码隔离?不仅组件之间相互隔离,还有第五个问题中模块依赖组件时可以动态增删组件,这样就是模块不会对组件中特定的类进行操作,所以完全的隔绝模块对组件中类的使用会使解耦更加彻底,程序也更加健壮。

问题解决

  1. 每个单独的module都添加一个gradle.properties配置文件,通过在其中配置isRunAlone作为是否独立运行的标志,用于生成配置applicationID和AndroidManifest
  2. 添加一个依赖于base的componentbase组件,由它提供service,各个组件实现接口,通过它来实现解耦以及各个组件之间的通信
  3. module的application问题:提供application的interface,各个module的application都实现,把init方法和initdata方法放在对应的接口,由app级别的application通过反射去进行初始化。
  4. 页面组件跳转使用arouter
  5. 使用**runtimeOnly project(’:login’)**进行代码隔离:依赖项仅在运行时对模块及其消费者可用,编译期间依赖项的代码对其消费者时完全隔离的。
  6. 使用**resourcePrefix **配置组件的资源文件

插件化

目前比较成熟的插件化方案

介绍插件化原理

线程池

线程

创建线程的三种方式

1. 继承 thread
2. 实现Runnable接口
3. 实现Callable接口

三种方法的比较

继承和实现的区别

java是单继承多实现的语言,如果选择了继承,那么只能继承Thread,而选择实现的话,就相对灵活很多

Runnable和Callable的区别

Runnable实现的是run方法,而Callable实现的是call方法,
call() 方法有返回值还能抛出异常, run() 方法则没有没有返回值,也不能抛出异常。

简介线程池

线程池可以理解为 管理多个线程,通过线程复用,减少创建与销毁操作,控制线程池中的线程的并发数,避免线程争夺CPU资源造成阻塞,对线程进行管理。
简而言之,线程池的好处是:

1.  减少创建与销毁线程带来的性能消耗
2.  可控制最大并发线程数,避免过多资源竞争而导致系统内存消耗
3.  能更好地控制线程的开启与回收,并且能定时执行任务

重要参数

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nZZjgMv0-1579358399387)(./1577171161413.png)]

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()

线程有哪些状态,哪些锁,各种锁的区别

常用方法:

  1. start
  • start() 方法的作用是启动线程
  • 该方法只能调用一次,再次调用不仅无法让线程再次执行,还会抛出非法线程状态异常。
  1. run
  • run() 方法中放的是任务的具体逻辑,该方法由jvm调用,一般情况下开发者不需要直接调用该方法。
  • 如果你调用了 run() 方法,加上 JVM 也调用了一次,那这个方法就会执行两次
  1. join
  • 当调用join方法后,会进行阻塞,直到该线程任务执行结束,可以让线程顺序执行,如果线程 A 调用了线程 B 的 join() 方法,那线程 A 会进入等待状态,直到线程 B 运行结束。
  • join() 方法导致的等待状态是可以被中断的,所以调用这个方法需要捕获中断异常
  1. Thread.currentThread()
  • currentThread() 方法是一个静态方法,用于获取执行当前方法的线程,我们可以在任意方法中调用Thread.currentThread()获取当前线程,并设置它的名字和优先级等属性。
  1. Thread.yield()
  • yield() 方法是一个静态方法,用于使当前线程放弃对处理器的占用,相当于是降低线程优先级。调用该方法就像是是对线程调度器说:“如果其他线程要处理器资源,那就给它们,否则我继续用”。
  • 该方法不一定会让线程进入暂停状态。
  1. Thread.sleep(ms)
  • sleep(ms) 方法是一个静态方法,用于使当前线程在指定时间内休眠(暂停)

六种状态

  1. 新建
  2. 可运行
  • Object.notify()
  • Object.notifyAll()
  • LockSupport.unpark()
  1. 阻塞
  2. 等待
  • Object.wait()
  • LockSupport.park()
  • Thread.join()
  1. 限时等待
  • Thread.sleep(ms)
  • Thread.join(ms)
  • Object.wait(ms)
  • LockSupport.parkNonos(ns)
  • LockSupport.parkUntil(time)
  1. 终止

关于Synchronized

Synchronized是java的一个关键字,用于保证被Synchronized修饰的方法&代码,同一时刻最多只能被一个线程操作,其他线程必须等待该线程执行完后才能操作,从而达到保证线程安全,解决多线程中的并发同步问题(阻塞型并发)

Synchronized种类

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0zMZkkf8-1579358399388)(./1577174235970.png)]

/**
 * 对象锁
 */
    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的内存)
适用于需要高性能加载大量图片的场景

自己封装一个图片加载库

  1. 异步加载:至少两个线程池(缓存和网络)
  2. 线程切换:使用handler
  3. 缓存:LruCache,DiskLruCache
  4. 防止oom:软应用,LruCache,图片压缩,bit像素存储
  5. 内存泄漏:生命周期管理,ImageView正确引用
  6. 列表图片错位:imageView加tag

kotlin

进程间通信

ListView与RecyclerView的对比

特点 具体描述 相对于listView
1. View组件复用性高 缓存RecyclerView.ViewHolder(Adapter必须遵循ViewHolder的设计模式 ,强制实现ViewHolder,优化了性能);

2.四层缓存,可以支持自定义缓存逻辑,所有recyclerView公用一个缓存池
| 1. ListView缓存了View,需要实现getView(),实现复用的性能优化
2. 两层缓存,快速重用屏幕上的列表项itemView,而不需要重新createView和bindView;缓存离开屏幕的itemView,可以让即将进入屏幕的item复用|
| 样丰富 | 1,通过layoutManager可以实现不同的风格;
2, 可以快速实现增删动画和item分割线| 1,只能实现线性平铺布局,2,只提供了divider属性提供分割线 |

Rxjava

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Mlvhy3g3-1579358399388)(./1578550885902.png)]

屏幕适配(换肤)

socket

定义

  • 即套接字,是应用层与TCP/IP协议族通信的中间软件抽象层,表现为一个封装了TCP/IP协议族的编程接口(api)
  1. socket不是一种协议,而是一个编程调用接口(api),属于传输层,主要解决数据如何在网络中传输。
  2. 通过socket,我们才可以在android平台上通过tcp/ip协议进行开发
  3. 对用户来说,只需调用socket去组织数据,以符合指定的协议,即可通信。
  • 成对出现,一对套接字
Socket={(ip地址1:PORT端口号),(ip地址2:PORT端口号)}
  • 一个Socket实例 唯一代表一个主机上的一个应用程序的通信链路

使用

客户端

  1. 建立一个Socket实例
  2. 操作系统将为该Socket实例分配一个未被使用的本地端口号
  3. 操作系统将创建一个含本地、远程地址、端口号的套接字数据结构,且一直保存该数据结构到链接关闭
  4. 在创建Socket实例的构造函数正确返回前,进行
  5. TCP握手协议完成后,Socket实例对象将创建完成,否则跑出io异常。

服务端

  1. 创建一个ServerSocket实例
  2. 操作系统将为该ServerSocket实例创建一个底层数据结构,包含指定监听的端口号,包含指定监听的端口号,包含监听地址的通配符(*)
  3. 调用accept(),将进入阻塞状态,等待客户端的请求
  4. 当一个新的请求到来时,将为该链接创建一个新的套接字数据结构,包含请求源地址和端口,关联到ServerSocket实例的一个未完成的链接数据结构列表中。

原理分类

  1. 流套接字(StreamSocket):基于tcp协议,采用流的方式提供可靠的字节流服务
  2. 数据报套接字(DatagramSocket):基于udp协议,采用数据报文提供数据打包发送的服务

Socket与Http的对比

* Socket属于传输层,因为tcp/ip属于传输层,解决的是数据如何在网络中传输中的问题
* Http协议属于应用层,解决的是如何包装数据
* 由于二者属于不同层面,所以本来是没有可比性的,但是随着发展,http封装了下面几层的使用,所有才有了对比:

区别

  1. http采用了请求–响应方式
    • 即建立了网络连接后,当客户端向服务器发送请求后,服务器才向客户端返回数据
  2. Socket采用了服务器主动发送数据的方式
    • 即建立了网络连接后,服务器可主动发送消息给客户端,而不是等待客户端的请求

你可能感兴趣的:(Android)