美文网首页
Android知识体系(2)

Android知识体系(2)

作者: 不会弹吉他的二郎腿 | 来源:发表于2022-08-03 11:07 被阅读0次

    Android知识体系(1)
    Android知识体系(2)

    十一.Handler机制

    1、定义&作用

    定义:Android中线程之间消息传递、异步通信的机制。
    作用:将工作线程的消息传递到UI主线程中,从而是实现工作线程对主线程的更新,避免线程操作的不安全。

    2、原理

    相关组件:
    · Handler:消息处理者,添加消息Message到MessageQueue中,再通过Looper循环取出消息。
    主要方法:handler.post()、handler.sendMessage()、handler.dispatchMessage()、handler.handleMessage()
    · Message:存储需要操作的数据
    · MessageQueue:存放消息的数据结构
    主要方法:queue.enqueueMessage()
    · Looper:消息循环
    主要方法:looper.prepare()、looper.loop()

    在主线程中创建Looper和MessageQueue,通过handler.sendMessage或者handler.post发送消息进入MessageQueue。
    Looper不断的循环从消息队列中取出消息,发送给消息创建者handler。handler接收消息,在handleMessage方法中处理。


    27C8A243-B380-4249-A873-FBEAC76DCA09.png

    相关资料文章:
    Android异步通信:手把手带你深入分析 Handler机制源码
    Android异步通信:这是一份Handler消息传递机制的使用教程
    Android 多线程:你的 Handler 内存泄露 了吗?

    十二.自定义view

    • 如图所示

      • img
    • View的绘制是从上往下一层层迭代下来的。DecorView-->ViewGroup(--->ViewGroup)-->View ,按照这个流程从上往下,依次measure(测量),layout(布局),draw(绘制)。

      • img
    • View分类:(1)单一视图 (2)视图组ViewGroup
      无论是measure过程、layout过程、draw过程都是从树的根节点开始(即树形结构的顶端),view的位置是相对于父view而言的。

    1、onMeasure 测量

    作用:决定view的大小

    • onMeasure 过程
      顶层ViewGroup->measure->onMeasure->measureChildren->子View->measure->onMeasure->测量完毕
      ViewGroup的测量过程需要重写onMeasure方法,根据布局的特性重写。

    • measureSpec测量规格
      measureSpec(测量规格 32位的int值) = mode(测量模式 高2位 31.32位) + size(具体大小 低30位)
      MeasureSpec.getMode()获取mode
      MeasureSpec.getSize()获取size
      MeasureSpec.makeMeasureSpec(size,mode)根据传入的size和mode返回对应的measureSpec

    • mode测量模式分为三类:
      (1)UNSPECIFIED:未指明大小,父视图不约束子视图
      (2)EXACTLY:明确大小,父视图为子视图明确指定一个确切的尺寸 使用match_parent或具体数值
      (3)AT_MOST:最大尺寸,父视图为子视图指定一个最大尺寸 wrap_content

    2、onLayout 布局

    作用:获取四个顶点,决定View的位置。

    • 布局过程
      布局过程也是自上而下,不同的是ViewGroup先调用onLayout让自己布局,然后再让子View布局,而onMeasure是先测量子View的大小再确定自身大小。
      (1).单视图不需要实现该方法,视图组需要实现onLayout()来对子view进行布局
      (2).对于单视图确定位置是在基类layout()中方法确定的,对于视图组自身位置也是layout()方法确定。layout主要用来确定子view的位置
      (3).layout方法中确定位置的方法是setFrame和setOpticalFrame
      (4).在视图组中,复写onLayout方法。在其中遍历子view,对于每个view依次调用layout->onLayout

    3、onDraw 绘制

    作用:显示内容

    • 绘制过程
      绘制背景、绘制内容、绘制子view、绘制装饰器
      draw -> drawBackground -> onDraw -> dispatchDraw -> onDrawScrollBars
      自定义View可重写onDraw以绘制不同内容

    4、invalidate/postInvalidate/requestLayout

    • invalidate\postInvalidate
      作用:都是调用onDraw方法达到重新绘制的目的
      区别:invalidate只能在主线程中调用,postInvalidate能在子线程中调用,postInvalidate内部使用了handler\message机制最终还是掉用invalidate方法。

    • requestLayout
      作用:调用onMeasure\onLayout重新测量和布局,有可能调用onDraw重新绘制。

    5、ViewRoot/DecorView

    • ViewRoot(实际是ViewRootImpl):连接WindowManagerService和DecorView(最层级的View)的桥梁。View的三大流程均是通过ViewRootImpl来实现的。
    • DecorView:顶级View,本身是一个FrameLayout。分为标题栏和内容栏,内容栏的id是R.android.id.content。Activity中的setContentView()方法最终是通过window.setContentView()
      添加到DecorView的内容栏中。

    6、View的绘制流程

    • View的绘制流程是从ViewRoot的performTraversals方法开始:
    • performTraversals会依次调用performMeasure\performLayout\performDraw,分别完成顶层View的测量、布局、绘制。
      过程中会对子View完成测量、布局、绘制。

    7、getMeasuredHeight 和 getHeight 方法的区别(同理getMeasuredWith/getWith)

    • 1、getMeasuredWidth是在onMeasure之后,getWidth是在onLayout之后。
      2、getMeasuredHeight方法返回的是测量后View的高度,与屏幕无关。getHeight返回的是屏幕显示的高度。当View没有超出屏幕时,他们的值
      是相等的,但当View超出屏幕显示时,getMeasuredHeight的值等于getHeight的值加上超出的高度。

    • 为什么有时候用getWidth()或者getMeasureWidth()得到0?
      View的绘制周期和Activity的生命周期不一致,所以在onCreate\onStart\onResume中调用方法都无法保证View测量、布局完成,所以获取的结果为0.

    相关资料文章:
    Android 绘制原理浅析【干货】

    十三.多线程编程

    1、为何有多线程?

    • 主线程(UI线程)
      • 在Android当中, 当应用启动的时候,系统会给应用分配一个进程,顺便一提,大部分应用都是单进程的,不过也可以通过设置来使不同组件运行在不同的进程中,
        在创建进程的同时会创建一个线程,应用的大部分操作都会在这个线程中运行。所以称为主线程,同时所有的UI控件相关的操作也要求在这个线程中操作,所以也称为UI线程。
    • 为何会有子线程
      • 因为所有的UI控件的操作都在UI线程中执行,如果在UI线程中执行耗时操作,例如网络请求等,就会阻塞UI线程,导致系统报ANR(Application Not Response)错误。
        因此对于耗时操作需要创建工作线程来执行而不能直接在UI线程中执行。这样就需要在应用中使用多线程,但是Android提供的UI工具包并不是线程安全的,也就是说不能直接在
        工作线程中访问UI控件,否则会导致不能预测的问题, 因此需要额外的机制来进行线程交互,主要是让其他线程可以访问UI线程。

    2、AsyncTask

    • 定义:轻量级的异步任务类,内部封装了线程池、handler

    • 缺点:1、使用的是默认的线程池
      2、与Activity的生命周期不一致,在执行完doInBackground后才完结。
      3、容易造成内存泄漏,AsyncTask持有Activity的引用。
      4、当Activity生命周期异常后,AsyncTask结果丢失
      目前AsyncTask已被遗弃,推荐使用协程

    3、ThreadPool

    • 线程池规则:

      • 当线程池中的核心线程数未达到最大时,启动一个核心线程去执行任务。
      • 如果核心线程数达到最大,任务会安排到任务队列中等待。
      • 核心线程达到最大、任务队列已满,启动一个非核心线程执行任务。
      • 核心线程最大、任务队列已满、非核心线程达到最大,线程池拒绝执行任务。
    • 优点:

      • 降低线程创建和销毁的系统开销
      • 线程复用,提高系统吞吐量
      • 执行大量异步任务时,提高性能
      • 提供了相关管理的API,使用方便
    • execute :
      service.execute(new Runnable() {
         public void run() {
             System.out.println("execute方式");
         }
      });
      
    • submit :
        Future<Integer> future = service.submit(new Callable<Integer>() {
        
            @Override
            public Integer call() throws Exception {
                System.out.println("submit方式");
                return 2;
            }
        });
        try {
            Integer number = future.get();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
      
    • 线程池关闭:
      • 调用线程池的shutdown()shutdownNow()方法来关闭线程池
      • shutdown原理:将线程池状态设置成SHUTDOWN状态,然后中断所有没有正在执行任务的线程。
      • shutdownNow原理:将线程池的状态设置成STOP状态,然后中断所有任务(包括正在执行的)的线程,并返回等待执行任务的列表。
      • 中断采用interrupt方法,所以无法响应中断的任务可能永远无法终止。
        但调用上述的两个关闭之一,isShutdown()方法返回值为true,当所有任务都已关闭,表示线程池关闭完成,则isTerminated()方法返回值为true。
        当需要立刻中断所有的线程,不一定需要执行完任务,可直接调用shutdownNow()方法。

    4、IntentService

    • 定义:可以在内部开启子线程执行耗时任务的服务。

    • 原理:继承至Service,内部封装了handler、looper、thread等用于子线程与主线程的交互。

    5、TreadLocal

    • 定义:
      存储一个数据,对指定的线程可见。

    • 原理:
      通过使用当前线程的TreadLocalMap对set(value)的value进行存储,key为当前的TreadLocal。get()方法通过当前TreadLocal为key,在当前线程的TreadLocalMap中取值。

    • ThreadLocalMap
      在Tread类中有TreadLocalMap的成员变量。TreadLocalMap是一种存储K-V的数据结构,内部使用的是哈希表结构。

    6、java内存分区和java内存模型(JMM)

    • 内存分区:
      ·程序计数器
      ·本地方法区
      - 存储与本地native方法交互的字节码
      ·虚拟机栈
      - 内部使用的 栈帧 结构:(1)局部变量表(2)操作数栈(3)动态连接(4)返回地址
      ·方法区
      - 存储类信息、元数据、常量池
      ·堆
      - 存储实例对象,GC作用的主要区域

    • JMM:
      规定所有的变量都存储在主内存中,每条线程还有自己的工作内存。线程中的工作内存保存了被线程使用的变量的主内存副本,线程对变量的操作都必须在工作内存中进行,
      不能直接读写主内存中的变量,不同线程之间也无法直接访问对方工作内存中的变量副本。线程间变量值的传递需要通过主内存来完成。

    7、volatile

    • 轻量级线程同步,保证操作数据的可见性、有序性,不保证原子性

    8、synchronized

    • 修饰普通方法、静态方法、代码块(this\object)

    9、ReentrantLock

    • reentrantLock 可重入锁
      对比Synchronized那有些区别和优点?
      ·在语法上Synchronized是原生语法提供。在用法上可修饰方法、代码块。而reentrantLock需配合try-catch使用,并且需要手动释放锁。
      ·reentrantLock 等待可中断,是乐观锁,而Synchronized是独占锁,悲观锁。
      ·reentrantLock 可以添加多个锁条件
      ·reentrantLock 公平锁,多个线程等待同一个锁时,必须按照申请锁的时间顺序获得锁。reentrantLock也可通过构造方法设置为非公平锁。

    10、Atomic原子类

    • 定义:适用于单个元素,能够保证一个基本数据类型、对象、或者数组的原子性。
      原子更新基本类型:AtomicInteger、AtomicBoolean、AtomicLong
      原子更新引用类型:AtomicReference、AtomicStampedReference、AtomicMarkableReference
      原子更新数组类型:AtomicLongArray、AtomicIntegerArray、AtomicReferenceArray
      原子更新对象属性:AtomicIntegerFieldUpdater、AtomicLongFieldUpdater、AtomicReferenceFieldUpdater
    • 实现原理:
      • CAS(compare and swap):比较并交换 compareAndSwap(v,n,e)

    相关资料文章:
    ThreadLocal原理其实很简单
    Java并发包中的Atomic原子类

    十四.跨进程通信(IPC inter-process communication)

    • linux系统中使用到的IPC机制有:管道、共享内存、socket、binder(android)等。

    1、Binder机制

    • Android系统为什么要选用Binder机制作为进程通信?
      答:
      ·binder数据传递只需要拷贝一次,效率较高
      ·安全性能高,通过给应用分配UID来鉴别应用的身份
      Android中的binder机制是一种高效率、安全性能高的进程通信方式

    • 实现原理:
      · 进程隔离:系统为确保自身的安全稳定,将系统内核空间和用户空间分离开来。用户空间的进程要进行交互需要通过内核空间来驱动整个过程。
      · C/S结构:Binder作为一个Service的实体,对象提供一系列的方法来实现服务端和客户端之间的请求,只要client拿到这个引用就可以进行通信。
      (binder对象是一个可以跨进程引用的对象,它的实体位于一个进程中,而它的引用却遍布系统的各个进程中)
      · 通信模型:
      · Server : 跨进程服务端,运行在某个进程,通过Binder驱动在ServiceManager中注册
      · Client : 跨进程客户端,运行在某个进程,通过Binder驱动获取ServiceManager中的服务
      · ServiceManager : 提供服务的注册和查询,运行在SystemServer进程
      · Binder驱动:前三者位于用户空间,binder驱动位于内核空间,其实现方式和驱动差不多,负责进程之间Binder通信的建立,Binder在进程中的传递,Binder引用计数管理,
      数据包在进程之间的传递和交互。
      · 内存映射:Memory Map,将用户空间的一块内存地址映射到内核空间,映射关系建立后,用户对这块内存的修改可以直接反应到内核空间中。
      减少了数据拷贝的次数,实现用户空间和内核空间的高效互动。

      (1)Binder驱动在内核空间中创建了一个数据接收缓存区
      (2)并在内核空间开辟了一个内核缓存区,建立内核缓存区和数据接收缓存区的映射关系,以及数据接收缓存区和用户空间地址的映射关系。
      (3)发送方进程通过copy_to_user函数将数据发送到内核缓存区,由于内核缓存区与数据接收区存在映射,而数据接收缓存区和用户空间地址映射,所以相当于把数据发送到
      接收方的用户空间。

    2、AIDL(android interface definition language) Android接口定义语言

    • 定义:Android接口定义语言,是一套模板代码,让某个service与多个应用程序组件之间跨进程通信。

    相关资料文章:
    Android Binder原理(一)

    十五.图片相关(bitmap加载、处理、缓存)

    1、Bitmap加载(本地、网络)

    • 原生方法加载网络图片
     private Bitmap returnBitmapFormUrl(String url) {
            long l1 = System.currentTimeMillis();
            Bitmap bitmap = null;
            HttpURLConnection connection = null;
            InputStream inputStream = null;
            URL myUrl = null;
            try {
                myUrl = new URL(url);
                connection = (HttpURLConnection) myUrl.openConnection();
                connection.setReadTimeout(6000);
                connection.setConnectTimeout(6000);
                connection.setDoInput(true);
                connection.connect();
                inputStream = connection.getInputStream();
                bitmap = BitmapFactory.decodeStream(inputStream);
            } catch (MalformedURLException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }finally {
                if(inputStream != null){
                    try {
                        inputStream.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                l1 = System.currentTimeMillis();
            }
            return bitmap;
        }
    
    • 使用三方插件Glide

    2、Bitmap处理(保存、压缩)

    • 保存图片至相册
    private void saveImageToFile(Context context, Bitmap bitmap) {
            File appDir = new File(Environment.getExternalStorageState(), "test");
            if (!appDir.exists()) {
                appDir.mkdir();
            }
            String fileName = System.currentTimeMillis() + ".jpg";
            File file = new File(appDir, fileName);
            try {
                FileOutputStream fos = new FileOutputStream(file);
                bitmap.compress(Bitmap.CompressFormat.JPEG, 100, fos);
                fos.flush();
                fos.close();
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                MediaStore.Images.Media.insertImage(context.getContentResolver(), file.getAbsolutePath(), fileName, null);
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            }
            Intent intent = new Intent();
            intent.setAction(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
            intent.setData(Uri.parse("file://"+file.getAbsolutePath()));
            context.sendBroadcast(intent);
        }
    
    • 图片压缩
      • Bitmap.compress() 质量压缩,不会对内存产生影响
      • BitmapFactory.Options.inSampleSize 内存压缩

    3、大图加载

    • BitmapFactory.decodeStream(): 获取图片

    • BitmapFactory.Option(): 设置图片相关参数(设置显示大小、缩放比例等)
      option.inSimpleSize = 2

    • BitmapRegionDecoder: 图片局部展示

    4、图片缓存

    • LurCache
      内部采用LinkedHashMap存储数据,其最重要的方法trimToSize是用来移除最少使用的缓存和使用最久的缓存,并添加最新的缓存到队列中。

    5、图片占用内存

    • getByteCount/getAllocationByteCount

    • with * height * 单个像素内存大小

    6、如何避免加载图片出现OOM?

    • 加载图片前,获取图片占用内存大小,决定是否压缩
    • 对比源图片宽高和控件宽高,决定是否缩放 BitmapFactory.Options的inJustDecodeBounds属性设置为true,解析一次图片获取图片宽高
    • 采用局部加载BitmapRegionDecoder
    • 多张图片的采用弱引用

    相关文章

      网友评论

          本文标题:Android知识体系(2)

          本文链接:https://www.haomeiwen.com/subject/oatsirtx.html