美文网首页
Android基础复习

Android基础复习

作者: 瓦雷 | 来源:发表于2018-01-16 15:41 被阅读61次

    博文
    博文

    一、Activity

    1.什么是activity?

    Activity一个应用程序的组件,它提供一个屏幕来与用户交互,以便做一些诸如打电话、发邮件和看地图之类的事情,原话如下:
    An [Activity](http://developer.android.com/reference/android/app/Activity.html) is an application component that provides a screen with which users can interact in order to do something, such as dial the phone, take a photo, send an email, or view a map.

    2.activity四种状态

    running / paused /stopped / killed

    running:处于活动状态,用户可以点击屏幕,屏幕会做出相应的响应,处于
    activity栈顶的一个状态。

    paused:失去焦点的状态,或者是被一个非全屏的activity所占据,又或是被一个
    透明的activity放置再栈顶,只是失去了和用户交互的能力,所有的状态信息和成
    员变量都还在,除非在内存非常紧张的情况下会被回收。

    stopped:一个activity被另一个activity完全覆盖的时候,被覆盖的activtiy的就会
    处于stopped状态,所有的状态信息和成员变量都还在,除非在内存非常紧张的
    情况下会被回收。

    killed:activity已经被系统回收,所有保存的状态信息和成员变量都将不存在了。

    3.activity生命周期
    Activity生命周期.gif
    • Activity启动➡️ onCreate()➡️ onStart()➡️ onResume()

      onCreate():activity被创建的时候调用,生命周期的第一个调用方法,创建
      activity的时候一定要重写该方法,可以做一些初始化的操作,比如布局资源的加
      载或者一些数据的加载。

      onStart():表明此时activity正在启动,已经处于用户可见的状态,当然此时还没
      有处于前台的显示,就是说此时用户还不能与用户进行交互操作;总结就是一个
      可以看见但是无法触摸的状态。

      onResume():已经在前台进行展示,可以与用户进行交互了。

    • 点击Home键回到手机桌面(Activity不可见):onPause()➡️ onstop()

      onPause():activity处于一个暂停的状态,可见但是不可交互

      onstop():整个activity表明已经完全停止,或者完全被覆盖的状态,完全不可
      见,处于后台的状态,如果处于内存紧张的状态下就有可能会被系统回收掉。

    • 当我们再次回到原Activity的时候:onRestart()➡️ onStart()➡️ onResume()

      onStart():表示activity正在重新启动,是由不可见状态变为可见状态的时候调用

    • 退出Activity时:onPause()➡️ onStop()➡️ onDestory()
      onDestory():当前activity正在销毁,是生命周期中的最后一个方法,可以做一
      些回收工作以及资源的释放
    4.进程优先级

    可见进程:处于可见但是用户不可点击的进程

    前台进程:处于前台正在与用户交互的,或者前台activity绑定的service

    后台进程:前台进程点击了home键回到桌面后,这个进程就会转变为后台进
    程,不会立马被kill掉,会根据内存的情况来做回收。

    服务进程:在后台开启的service服务

    空进程:五个进程中优先级最低,是一个没有活跃的组件,只是出于缓存的目的
    而保留

    二、Activity任务栈

    一个用来管理Activity的容器,后进先出的栈管理模式,一个应用中可以有多个任
    务栈,也可以只有一个任务栈,这得根据启动模式来决定。

    三、Activity启动模式

    1.standard

    每次启动activity都会重新创建一个activity的实例,然后将它加到任务栈中,不会
    去考虑任务栈是否有该activity的实例,默认的情况就是此种启动模式

    2.singleTop(栈顶复用模式)

    如果需要创建的activity实例刚好处于任务栈的栈顶,那么就不会再去创建一个新
    的activity实例了,而是复用这个栈顶的activity。

    3.singleTask(栈内复用模式)

    是一个单例模式,当需要创建一个activity的时候,会去检测整个任务栈中是否存
    在该activity实例,如果存在,会将其置于任务栈的栈顶,并将处于该activity以上
    的activity都移除销毁,这时会回调一个onNewIntent()方法。

    • 补充
      <activity android:name=".MainActivity" android:launchMode="singleTask" android:taskAffinity="com.demo.singletask"/>
      默认情况下taskAffinity的值就是包名,taskAffinity相同就说明是在同一个栈中,TaskId即一样
    4. singleInstance

    singleTask的升级版,整个系统中只有一个实例,启动一singleInstanceActivity
    时,系统会创建一个新的任务栈,并且这个任务栈只有他一个Activity,一般用于
    多个应用之间的跳转。

    • singleTask与singleTask的却别:
      一个系统中可能存在多个singleTask Activity实例,
      一个系统中只能存在一个singleInstance Activity实例。

    四、scheme跳转协议

    android中的scheme是一种页面内跳转协议,是一种非常好的实现机制,通过定义自己的scheme协议,可以非常方便的跳转app中的各个页面;通过scheme协议,服务器可以定制化的告诉app跳转哪个页面,可以通过消息栏定义化跳转页面,可以通过H5页面跳转页面等。

    二、Fragment

    1.Fragment为什么被称为第五大组件

    首先在使用上是不属于其他四大组件的,它有自己的生命周期,同时它可以灵活
    动态的加载到activity中去,同时它并不是像activity一样完全独立的,它必须依附
    于activity,而且还需要加载到activity中去。

    2.Fragment加载到Activity中的两种方式

    静态加载:只需要把Fragment当成一个普通UI控件放到界面Layout中就完事了

    动态加载:得先通过FragmentManage来获取FragmentTransaction(事务),然后使用add或者replace的方式加载fragment,最后commit提交事物。

    3.FragmentPagerAdapter与FragmentStatePagerAdapter的区别

    FragmentPagerAdapter适用于页面较少的情况下,在源码中destoryItem()函数中的最后一行可以看到,调用的是detach方法,该方法的作用是将fragment与activity的关联取消掉,并没有做内存回收处理,所以会更加的消耗内存


    FragmentPagerAdapter_destoryItem.png

    FragmentStatePagerAdapter适用于页面较多的情况下,查看源码可发现调用的是remove的方法,会进行内存回收处理。


    FragmentStatePagerAdapter_destoryItem.png
    4.commit和commitAllowingStateLoss区别

    commit:该方法必须在存储状态之前调用,也就是onSaveInstanceState(),如果在存储状态之后调用就会报出:
    Can not perform this action after onSaveInstanceState错误信息

    commitAllowingStateLoss:允许在存储状态之后再调用,但是这是危险的,因为如果activity需要从其状态恢复,那么提交就会丢失,因此,只有在用户可以意外的更改UI状态的情况下,才可以使用该提交。

    以下是部分的核心源码:

    commit与commitAllowingStateLoss源码对比.png
    5.Fragment的生命周期

    下面列出两张图,第一张可用于面试记忆,第二张可以详细的摸索清楚activity与
    fragment生命周期的走向。

    Fragment生命周期.png fragment与activity生命周期关联图.png

    onAttach:将fragment与activity关联
    onCreate:初次创建fragment时调用,只是用来创建fragment,此时的activity并没有创建完成
    onCreateView:系统在fragment首次绘制界面的时候调用
    onViewCreated:fragment的界面已经绘制完毕。

    Activity - onCreate:初始化activity中的布局

    onActivityCreate:activity渲染绘制完毕

    Activity - onStart:activity可见但不可交互操作
    onStart:fragment可见但不可交互操作

    Activity - onResume:activity已经在前台进行展示,可以与用户进行交互了
    onResume:fragment已经在前台进行展示,可以与用户进行交互了

    onPause:fragment处于一个暂停的状态,可见但是不可交互
    Activity - onPause:activity处于一个暂停的状态,可见但是不可交互

    onStop:fragment不可见
    Activity - onStop:activity不可见

    onDestoryView:与onCreateView关联,fragment即将结束会被保存
    onDestory:销毁Fragment,通常按Back键退出或者Fragment被回收时调用此方法
    onDetach:会将fragment从activity中移除,和remove()不同,此时fragment的状态依然由FragmentManager维护

    Activity - onDesroty:activity销毁

    3.Fragment之间的通信

    在Fragment中获取Activity中的方法:getActivity
    在Activity中获取Fragment中的方法:接口回调
    在Fragment中获取Fragment中的方法:先使用getActivity获取到activity,然后使
    用getFragmentById

    三、Service

    1.service是什么

    service(服务)是一个可以在用户后台执行长时间操作,而没有用户界面的应用组件
    注意:service是运行在主线程中的,绝对是不能做耗时操作的。
    请勿将服务与子线程混为一谈

    2.两种启动service的方式
    • startService
      1、定义一个类继承Service
      2、在Manifest.xml文件中配置该service
      3、使用Context中的startService(Intent)方法启动该Service
      4、不使用时,使用stopService(Intent)方法停止该服务
      5、onCreate()方法只会调用一次, onStartCommand()方法可以重复调用
      6、服务一旦启动,服务即可在后台无限期的运行,即便启动服务的组件被销毁
      了也不会受影响,除非手动关闭,已启动的服务通常是执行单一操作,而且不会
      将结果返回给调用方

    • bindService
      1、创建BindService服务端,继承自service并在类中,创建一个实现IBinder接
      口的实例对象并提供公共方法给客户端使用
      2、从onBind()回调方法返回此Binder实例
      3、在客户端中,从onServiceConncted()回调方法接收Binder,并使用提供的方法调用绑定服务
      4、当应用组件通过调用 bindService() 绑定到服务时,服务即处于“绑定”状态。
      绑定服务提供了一个客户端-服务器接口,允许组件与服务进行交互、发送请求、
      获取结果,甚至是利用进程间通信 (IPC) 跨进程执行这些操作。 仅当与另一个应
      用组件绑定时,绑定服务才会运行。 多个组件可以同时绑定到该服务,但全部取
      消绑定后,该服务即会被销毁

    最后附上一张官方生命周期图:


    service生命周期图.png

    参考博文

    四、Broadcast receiver

    1.广播定义

    在Android中,Broadcast是一种广泛运用在应用程序之间传输信息的机制,我们
    要发送的广播内容是一个Intent,这个Itent中可以携带我们需要传输的数据。

    2.广播的使用场景

    1、同一app具有多个进程的不同组件之间的消息通信
    2、不同app之间的组件之间消息通信

    3.广播种类

    1、普通广播(Normal Broadcast):Context.sendBroadcast
    2、系统广播(System Broadcast):Android中内置了多个系统广播,只要涉及到手
    机的基本操作(如开机、网络状态变化、拍照等等),都会发出相应的广播
    2、有序广播(Ordered Broadcast):Context.sendOrderedBroadcast
    3、本地广播(Local Broadcast):只在app内传播

    4.广播的实现

    1、静态注册:在AndroidManifest中注册就可以,注册完成就一直运行,如果它
    所依赖的activity销毁了,仍然能够接受广播,甚至app 进程杀死仍然能够接受广

    2、动态注册:跟随activity生命周期,在代码中调用Context.registerReceiver方法

    // 选择在Activity生命周期方法中的onResume()中注册
    @Override
      protected void onResume(){
          super.onResume();
    
        // 1. 实例化BroadcastReceiver子类 &  IntentFilter
         mBroadcastReceiver mBroadcastReceiver = new mBroadcastReceiver();
         IntentFilter intentFilter = new IntentFilter();
    
        // 2. 设置接收广播的类型
        intentFilter.addAction(android.net.conn.CONNECTIVITY_CHANGE);
    
        // 3. 动态注册:调用Context的registerReceiver()方法
         registerReceiver(mBroadcastReceiver, intentFilter);
     }
    
    
    // 注册广播后,要在相应位置记得销毁广播
    // 即在onPause() 中unregisterReceiver(mBroadcastReceiver)
    // 当此Activity实例化时,会动态将MyBroadcastReceiver注册到系统中
    // 当此Activity销毁时,动态注册的MyBroadcastReceiver将不再接收到相应的广播。
     @Override
     protected void onPause() {
         super.onPause();
          //销毁在onResume()方法中的广播
         unregisterReceiver(mBroadcastReceiver);
         }
    }
    

    ⚠️特别注意

    动态广播最好在Activity 的 onResume()注册、onPause()注销。
    原因:
    对于动态广播,有注册就必然得有注销,否则会导致内存泄露
    重复注册、重复注销也不允许

    5.广播内部实现机制

    1、自定义广播接受者BroadcastReceiver,并复写onRecvice()方法;
    2、接收者通过Binder机制向AMS(Activity Manager Service)进行注册;
    3、广播发送着通过Binder机制向AMS发送广播;
    4、AMS查找符合相应条件(IntentFilter/Permission等)的BrocastReceiver,将广播发送到BrocasrReceiver(一般是Activity)相应的循环队列中;
    5、消息循环执行拿到该广播,回调BrocastReceiver的onRecerve()方法

    6.LocalBroadcastManager详解

    1.LocalBroadcastManager高效的原因主要是它的内部是通过Handler实现的,
    sendBrocast()方法含义并非和我们平时所用一样,它的sendBrocast()其实是通过
    handler发送一个Message实现的

    2.既然它内部是通过Handler来实现广播的发送,那么相比与系统使用Binder实现
    自然是更加高效了,同时使用Handler来实现,别的应用无法向我们的应用发送
    广播,而我们应用内发送的广播也不会离开我们的广播

    有序广播与无序广播的区别

    1.无序广播
    通过Context.sendBroadcast()方法来发送,它是完全异步的。
    所有的receivers(接收器)的执行顺序不确定,因此所有的receivers(接收器)接收broadcast的顺序不确定。
    这种方式效率更高,但是BroadcastReceiver无法使用setResult系列、getResult系列及abortbroadcast(中止)系列API。
    广播不能被终止,数据不能被修改。
    2.有序广播
    有序广播,即从优先级别最高的广播接收器开始接收,接收完了如果没有丢弃,就下传给下一个次高优先级别的广播接收器进行处理,依次类推,直到最后。如果多个应用程序设置的优先级别相同,则谁先注册的广播,谁就可以优先接收到广播。通过Context.sendorderBroadCast()方法来发送,sendOrderedBroadcast(intent,
    receiverPermission, resultReceiver, scheduler, initialCode, initialData, initialExtras);,其中的参数resultReceiver,可以自己重写一个类,作为一个最终的receive 最后都能够接收到广播,最终的receiver 不需要再清单文件里面配置,initialData可以作为传输的数据
    广播可以被终止,数据传输过程中可以被修改。

    参考博文
    进程保活

    五、Webview

    1.WebView安全漏洞

    Android API level 16以及之前的版本存在远程代码执行安全漏洞,该漏洞源于程
    序没有正确限制使用addJavascriptInterface方法,远程攻击者可通过使用
    Java Reflection API 利用该漏洞执行任意java对象的方法

    2.WebView的性能优化

    如果WebView是使用xml进行加载的往往在关闭Activity的时候WebView的资源并不会随着Activity的销毁而销毁。

    网上推荐的方法解决大体分为2种:
    一种是使用代码动态的创建WebView,对传入WebView中的Context使用弱引用,
    动态添加WebView的意思是在布局创建一个ViewGroup用来放置
    WebView,Activity创建时add进来,在Activity停止时remove掉

    第二种是为WebView新开一个进程,不过这样的话使用起来会比较麻烦,效果还
    是挺简单粗暴的,据说QQ微信也在使用这种方式:参考

    六、Binder

    一、Linux内核的基础知识

    1.进程隔离/虚拟地址控件

    在操作系统中,为了保护某些进程之间的互不干扰,设计了一个叫进程隔离的技术,就是为了避免进程A可以去操作进程B而实现的;进程的隔离实现用到了虚拟地址空间,就是说进程A的虚拟地址空间和进程B的虚拟空间是不一样的,这样,就防止了进程A的数据可以写到进程B中;所以操作系统中不同的进程之间数据是不共享的,如果需要进程间的数据通信,就需要某种进程间通信的机制来完成,在android中就是使用Binder这个机制来完成的。

    不同进程间的引用叫做句柄,相同进程间的应用叫做指针,也就是对进程空间中虚拟地址的引用

    2.系统调用

    对内核有保护机制,对于应用程序只可以访问某些许可的资源,不许可的资源是不可以访问的。

    3.Binder驱动

    在android系统中,是运行在内核之中的;负责各个用户进程通过Binder通信的内核来交互的模块叫Binder驱动

    二、Binder通信机制介绍

    1.为什么使用Binder

    1)Android使用的Linux内核拥有着非常多的跨进程通信机制
    2)性能:在移动设备上(性能受限制的设备,比如要省电),广泛地使用跨进
    程通信对通信机制的性能有严格的要求,Binder相对出传统的Socket方式,更加
    高效。Binder数据拷贝只需要一次,而管道、消息队列、Socket都需要2次,共
    享内存方式一次内存拷贝都不需要,但实现方式又比较复杂。
    3)安全:传统的进程通信方式对于通信双方的身份并没有做出严格的验证,比
    如Socket通信ip地址是客户端手动填入,很容易进行伪造,而Binder机制从协议
    本身就支持对通信双方做身份校检,因而大大提升了安全性

    2.Binder通信模型

    Binder通信图.png

    客户端进程只不过是持有了我们一个服务端的一个代理

    3.到底什么是Binder??
    1)通常意义下,binder指的是一种通信机制
    2)对于Server进程来说,Binder指的是Binder本地对象;
    对于Client对象来说,Binder指的是Binder代理对象
    3)对于传输过程而言,Binder是可以进行跨进程传递的对象

    博文参考
    博文参考
    博文参考

    七、Handler

    1、处理过程:
    从handler中获取一个消息对象,把数据封装到消息对象中,通过handler的
    send…方法把消息push到MessageQueue队列中。
    Looper对象会轮询MessageQueue队列,把消息对象取出。
    通过dispatchMessage分发给Handler,再回调用Handler实现的handleMessage
    方法处理消息

    [图片上传中...(Handler流程图.png-523e3e-1515822798479-0)] Handler流程图.png

    2、Handler中为什么要使用ThreadLocal来获取Lopper呢?
    因为在不同线程访问同一ThreadLocal时,不管是set方法还是get方法对
    ThreadLocal所做的读写操作仅限与各自线程内部,这样就可以使每一个线程有
    单独唯一的Lopper。
    ThreadLocal博文

    3、Handler引起的内存泄漏以及解决办法
    原因:在java中,非静态的内部类和匿名内部类都会隐式的持有一个外部类的引
    用,所以原因就是匿名内部类持有外部类的引用,导致外部Activity无法释放

    解决办法:
    1、最直接的思路就是避免使用非静态内部类。使用Handler的时候,放在一个新建的文件中来继承Handler或者使用静态的内部类来替代。静态内部类不会隐含的持有外部类的引用,因此这个activity也就不会出现内存泄漏问题。
    2、如果你需要在Handler内部调用外部Activity的方法,你可以让这个Handler持有这个Activity的弱引用,这样便不会出现内存泄漏的问题了。
    3、另外,对于匿名类Runnable,我们同样可以设置成静态的,因为静态内部类不会持有外部类的引用。
    4、注意:如果使用Handler发送循环消息,最好是在Activity的OnDestroy方法中调用mLeakHandler.removeCallbacksAndMessages(null);移除消息。(这不是解决内存泄漏的方法)
    博文

    八、AnsycTask

    1.Asynctask是什么?
    它是Android提供的一个抽象类,他本质上就是一个封装了线程池和handler的异
    步框架,主要是来执行异步任务的,由于它内部继承了handler,所以他可以在工
    作线程和UI线程中随意切换。

    注意:Asynctask能够让你避免使用线程类thread和handler直接处理后台操作,
    他可以把运算好的结果交给UI 线程来显示,不过Asynctask只能做一些耗时较短
    的操作,如果我们要做耗时较长的操作,我们还是尽量使用线程池。

    2.Asynctask使用
    它主要有三个重要参数,五个重要方法

    • 三个参数,三种泛型分别代表:
    public abstract class AsyncTask<Params, Progress, Result> { }
    
    第一个:“启动任务执行的输入参数”。
    第二个:“后台任务执行的进度”。
    第三个:“后台计算结果的类型”。
    
    • 五个方法:
    第一种:execute(params…params):执行一个异步任务,需要我们在代码中调用
    此方法,触发异步任务的执行(必须在UI线程中调用)
    
    第二种:onPreExecute(),在execute(Params… params)被调用后立即执行,一
    般用来在执行后台任务前对UI做一些标记(也可以理解为初始化数据)。
    
    第三种:doInBackground(Params… params),在onPreExecute()完成后立即执
    行,用于执行较为费时的操作,此方法将接收输入参数和返回计算结果。在执行
    过程中可以调用publishProgress(Progress… values)来更新进度信息。
    
    第四种:onProgressUpdate(Progress… values),在调用
    publishProgress(Progress… values)时,此方法被执行,直接将进度信息更新到
    UI组件上。
    
    第五种:onPostExecute(Result result),当后台操作结束时,此方法将会被调
    用,计算结果将做为参数传递到此方法中,直接将结果显示到UI组件上。
    

    3.Asynctask的内部原理

    1、Asynctask的本质是一个静态的线程池,Asynctask派生出的子类可以显现不
    同的异步任务,这些任务都是提交到静态的线程池中执行。
    
    2、线程池中的工作线程执行doinbackground(mparams)方法来执行异步操作。
    
    3、当任务状态改变后,工作线程会向UI线程发送消息,Asynctask内部的
    internalHandler相应这些消息,并调用相关的回调。
    

    4.Asynctask的注意事项

    一、内存泄露。和handler造成的问题一样。非静态内部类持有外部类的匿名引
    用,导致外部activity像被内存回收,由于非静态内部类还持有外部类的引用,导
    致外部类不能被系统回收,造成内存泄露。
    
    解决方法: 
    1、谁为静态内部类。 
    2、非静态内部类使用弱引用。 
    3、在destroy方法中finish。
    
    5.为什么AsyncTask只会执行一次
    AsyncTask定义了一个mStatus变量,表示异步任务的运行状态,分别是PENDING、RUNNING、FINISHED,当只有PENDING状态时,AsyncTask才会执行,这样也就保证了AsyncTask只会被执行一次
    
    二、生命周期
    
    如果在外部类被销毁的时候,没有执行onCancelled方法,这有可能让我们的
    activity在销毁之前导致崩溃。因为Asynctask正在处理某些view。但是activity不
    存在了,所以就会崩溃。
    
    三、结果丢失 
    假如说,activity被系统回收或者横竖屏切换,导致activity被重新创建,而之前运
    行的Asynctask会持有之前activity的引用,但是这个引用已经无效,所以这个时
    候,你调用onpostExecute()方法去更新界面就会失败,因为结果已经丢失。
    

    5.为什么AsyncTask只能执行一次?
    应该说同一时间段内,只执行一次,防止对同一件事进行操作,造成的混乱。我
    们知道异步的线程执行的顺序很大程度上是不确定的,有可能先执行的最后执行
    完成,后执行的反而先完成。如果在同一时间段内,执行多个线程这里就会出现
    混乱。

    6.什么是并行,什么是串行,两者的区别是什么
    在android1.6版本之前,Asynctask都是串行,也就是他会把所有任务,一串一串
    的放在线程池中。但是在1.6到2.3的版本中,它改成了并行。为了维护系统的稳
    定,2.3版本后又改回了串行,但是还是可以执行并行的,建议使用串行

    来自博文
    推荐

    九、HandlerThread

    1.由来
    当我们需要做一个耗时操作时,自然而然会启动一个子线程去执行,当子线程执
    行完成之后,就会自动关闭掉;如果过了一会我们又想执行一个耗时操作呢,那
    就又得重新开启一个子线程,这样重复的来回开启是非常消耗性能的;而在
    android中就为我们提供了HandlerThrea来完成这样的操作。

    2.特点

    • HandlerThread本质上是一个线程类,它继承了Thread;
    • HandlerThread有自己内部的Lopper对象,可以进行looper循环;
    • HandlerThread中是不可以处理界面更新的,还是得交给Handler处理;
    • 优点是不会堵塞,减少了对性能的消耗;缺点是不能同时进行多任务
      的处理,需要等待进行处理,处理效率比较低;
    • 与线程池重并发不同,HandlerThread是一个串行队列,HandlerThread背后只
      有一个线程

    推荐博文

    十、intentService

    1.IntentService是什么
    IntentService是继承并处理异步请求的一个类,在IntentService内有一个工作线程来处理耗时操作,启动IntentService的方式和启动传统的Service一样,同时,当任务执行完后,IntentService会自动停止,而不需要我们手动去控制或stopSelf()。另外,可以启动IntentService多次,而每一个耗时操作会以工作队列的方式在IntentService的onHandlerIntent回调方法中执行,并且,每次只会执行一个工作线程,执行完第一个再执行第二个。

    2.IntentService使用方法
    创建IntentService时,只需要实现onHandlerIntent方法和构造方法,onHandlerIntent为异步方法,可以执行耗时操作。

    十一、View绘制

    首先是measure(测量) 其次 layout(布局) 最后 draw(绘制)

    1.measure

    测量中首先得知道测量规则,MeasureSpec这个类

    MeasureSpec这个类包括测量模式(specModel)
    和测量模式下的规格大小(specSize)

    MeasureSpec表示形式是32位的int值
    前两位是specModel 后30位是specSize

    我们都知道SpecMode的尺子类型有很多,不同的尺子有不同的功能,而SpecSize刻度是固定的一种,所以SpecMode又分为三种模式

    UNSPECIFIED:父容器不对View有任何大小的限制,这种情况一般用于系统内部,表示一种测量状态
    EXACTLY:父容器检测出View所需要的精确大小,这时候View的值就是SpecSize
    AT_MOST:父容器指定了一个可用大小即SpecSize,View的大小不能大于这个值

    measure是final修饰的,需要重写的方式是onMeasure()

    measure.png
    2.layout
    3.draw
    4.Android视图工作机制中的重绘

    一、invalidate()和requestLayout()

    invalidate()和requestLayout(),常用于View重绘和更新,其主要区别如下

    invalidate方法只会执行onDraw方法
    requestLayout方法只会执行onMeasure方法和onLayout方法,并不会执行onDraw方法。
    所以当我们进行View更新时,若仅View的显示内容发生改变且新显示内容不影响View的大小、位置,则只需调用invalidate方法;若View宽高、位置发生改变且显示内容不变,只需调用requestLayout方法;若两者均发生改变,则需调用两者,按照View的绘制流程,推荐先调用requestLayout方法再调用invalidate方法

    二、invalidate()和postInvalidate()

    invalidate方法用于UI线程中重新绘制视图
    postInvalidate方法用于非UI线程中重新绘制视图,省去使用handler

    博文

    十二、事件分发

    1、简要的谈谈Android的事件分发机制?

    当点击事件发生时,首先Activity将TouchEvent传递给Window,再从Window传递给顶层View。TouchEvent会最先到达最顶层 view 的 dispatchTouchEvent ,然后由 dispatchTouchEvent 方法进行分发,如果dispatchTouchEvent返回true ,则整个事件将会被销毁,如果dispatchTouchEvent返回 false ,则交给上层view的 onTouchEvent 方法来开始处理这个事件,如果 interceptTouchEvent 返回 true ,也就是拦截掉了,则交给自身的 onTouchEvent 来处理,如果 interceptTouchEvent 返回 false ,那么事件将继续传递给子 view ,由子 view 的 dispatchTouchEvent 再来开始这个事件的分发。如果事件传递到某一层的子 view 的 onTouchEvent 上了,且这个方法返回了 false ,那么这个事件会从这个 view 往上传递,都是 onTouchEvent 来接收。而如果传递到最顶view的 onTouchEvent 也返回 false 的话,这个事件就会消失,直到onTouchEvent返回true为止。

    2、为什么View有dispatchTouchEvent方法?

    因为View可以注册很多事件的监听器,如长按、滑动、点击等,它也需要一个管理者来分发

    3、ViewGroup中可能有很多个子View,如何判断应该分配给哪一个?

    根据源码可知,它会分配给在点击范围内的子View

    4、当点击时,子View重叠应该如何分配?

    一般分配给最上层的子View,这是由于安卓的渲染机制导致的

    博文

    1.简介

    Android事件分发机制的发生在View与View之间或者ViewGroup与View之间具有镶嵌的视图上,而且视图上必须为点击可用。当一个点击事件产生后,它的传递过程遵循如下顺序:Activity->Window->View,即事件先传递给Activity,再到Window,再到顶级View,才开始我们的事件分发

    2.概念

    Android事件分发机制主要由三个重要的方法共同完成的

    dispatchTouchEvent:用于进行点击事件的分发
    onInterceptTouchEvent:用于进行点击事件的拦截
    onTouchEvent:用于处理点击事件
    这里需要注意的是View中是没有onInterceptTouchEvent()方法的

    dispatchTouchEvent

    • return true:表示该View内部消化掉了所有事件
    • return false:表示事件在本层不再继续进行分发,并交由上层控件的onTouchEvent方法进行消费
    • return super.dispatchTouchEvent(ev):默认事件将分发给本层的事件拦截onInterceptTouchEvent方法进行处理

    onInterceptTouchEvent

    • return true:表示将事件进行拦截,并将拦截到的事件交由本层控件的onTouchEvent进行处理
    • return false:表示不对事件进行拦截,事件得以成功分发到子View
    • return super.onInterceptTouchEvent(ev):默认表示不拦截该事件,并将事件传递给下一层View的dispatchTouchEvent

    onTouchEvent

    • return true:表示onTouchEvent处理完事件后消费了此次事件
    • return fasle:表示不响应事件,那么该事件将会不断向上层View的onTouchEvent方法传递,直到某个View的onTouchEvent方法返回true
    • return super.dispatchTouchEvent(ev):表示不响应事件,结果与return false一样

    十三、ListView

    参考

    十四、构建

    十五、Okhttp

    1.Okhttp简单使用

    //首先创建一个Handler
    private Handler mHandler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                switch (msg.what) {
                        imageView.setImageBitmap((Bitmap) msg.obj);
                        break;
                }
            }
    };
    
    //然后使用OkHttp发送网络请求,并将结果通过Handler发送到主线程
    public void sendRequestByOkHttp() {
            //创建Request对象,并传入请求地址
            Request request = new Request.Builder().url( "http://tnfs.tngou.net/img/ext/160321/e57d5816cb72d7486aa6dbf19a7d0c6c.jpg").build();
            //构造Call对象--其实是AsyncCall对象
            Call call = client.newCall(request);
            //调用Call.enqueue方法进行异步请求
            call.enqueue(new Callback() {
                @Override
                public void onFailure(Call call, IOException e) {
                    //网络请求失败
                    Log.d("lenve", "onFailure: " + e.getMessage());
                }
    
                @Override
                public void onResponse(Call call, Response response) throws IOException {
                    //网络请求成功,将请求的图片信息显示的ImageView控件上
                    Bitmap bitmap = BitmapFactory.decodeStream(   response.body().byteStream());
                    Message msg = mHandler.obtainMessage();
                    msg.what = 1;
                    msg.obj = bitmap;
                    mHandler.sendMessage(msg);
                }
            });
        }
    

    2.如何使用OkHttp进行异步网络请求,并根据请求结果刷新UI

    3.可否介绍一下OkHttp的整个异步请求流程

    4.OkHttp对于网络请求都有哪些优化,如何实现的

    5.OkHttp框架中都用到了哪些设计模式

    OkHttp的底层是通过Java的Socket发送HTTP请求与接受响应的(这也好理解,HTTP就是基于TCP协议的),但是OkHttp实现了连接池的概念,即对于同一主机的多个请求,其实可以公用一个Socket连接,而不是每次发送完HTTP请求就关闭底层的Socket,这样就实现了连接池的概念。而OkHttp对Socket的读写操作使用的OkIo库进行了一层封装。

    博文

    十六、retrofit

    retrofit的原理其实就是这样,拦截到方法、参数,再根据我们在方法上的注解,去拼接为一个正常的Okhttp请求,然后执行。

    十七、volley

    1.首先我们初始化一个RequestQueue,因为初始化RequestQueue非常的消耗性能,所以只需要初始化一次即可
    2.在newRequestQueue中我们会发现,如果Android版本大于2.3会调用HttpUrlConnection的HurlStack,如果Android版本小于2.3会调用一个HttpClient的HttpClickStack
    3.接下来Volley会创建RequestQueue,并调用他的start方法
    4.接着会创建1个缓存调度线程(CacheDispatcher)和4个网络调度线程(netDispatcher),并分别调用他们的start方法,所以会默认开启5个后台线程,线程优先级都为10
    5.然后分别去轮询各自的消息队列
    6.Volley是如何把请求的数据回调回主线程中的?

    使用Handler.postRunnable(Runnable)方法回调回主线程中进行处理,ExecutorDelivery的构造方法中可以看到这段代码


    volley工作流程.png

    博文
    推荐博文

    十八、ButterKnife

    1.使用
    • 绑定一个view
    • 给一个view添加点击事件
    • 给多个view添加点击事件
    • 给ListView 设置 setItemClickListener
    2.原理

    ⚠️ 首先我们应该避免一个误区,butterknife中并不是用的反射的原理来实现的,
    因为用反射的话性能会很差

    自己用反射实现原理:通过反射获得类中所有注解的属性,并且获得注解当中资
    源中Layout的值,最后通过反射findViewById获取到这个View,并赋值给Activity
    当中的属性。
    很大的缺点是在运行时会大量的使用到反射,而反射其实会影响app的性能,特
    别是运行时的性能,容易造成卡顿;又会产生很多的临时变量,临时变量又会引
    起垃圾回收,在UI优化中,大量频繁的变量的垃圾回收会造成UI卡顿,UI卡顿也
    是一种性能的标志之一。

    而我们的butterknife没有使用这样的方法。butterknife使用的是java的注解处理技
    术,也就是在代码编译的时候,编译成字节码的时候,它就处理好了注解操作

    注意:java的注解处理技术是在编译阶段执行的,它的原理是通过读入我们的源
    代码,解析注解然后生产新的java代码,而新生产的java代码当中,最后被编译
    成java字节码,由于注解解释器它是不能改变读入的java类的。这就是butterknife的注解原理。

    梳理ButterKnife原理流程

    • 开始它会扫描java代码中所有的ButterKnife注解
    • ButterKnifeProcssor ---><className>$$ViewBinder
    • 调用bind方法加载生成的ViewBinder类
    3.绑定view的时候,为什么不能private和static??

    因为性能问题,如图我们我们把这个view设置成private,那么这个框架他就不能
    通过注解的方式来获取了,只能通过反射来获取,此时不管你的性能有多快,都
    会影响性能。这是必须注意并且避免的。这也就是和其他注解方式不同的一点。

    来自博文

    十九、Glide加载图片

    Glide原理的核心是为bitmap维护一个对象池。对象池的主要目的是通过减少大对象内存的分配以重用来提高性能
    glide与picasso对比
    glide原理

    二十、ANR

    1.什么是ANR?

    Application Not Responding

    在主线程中做耗时的操作,导致长时间无法对用户的操作做出响应,Activity最长
    为5秒,BrocastReceiver最长为10秒,Service最长为20秒。

    2.原因:应用程序的响应性是由Activity Manager和Window Manager系统服务监视的,当它监测到组件在相应的响应的时间没有响应就会弹出ARN的提示。
    • 哪些操作是在主线程中呢?
      Activity中所有生命周期的回调都是在主线程中。

      Service默认是执行在主线程中的。

      BrocastReceiver中的onReceve()回调执行在主线程中。

      没有使用子线程的Lopper的Handler的handlerMessage,post(Runner)是执行在
      主线程中的。

      AsyncTask中除了doInBackground,其他方法都是执行在主线程中的。

    3.处理:
    • 使用AnsycTask处理耗时IO操作
    • 使用Thread或者HandlerThread提高优先级
    • 使用Handler来处理工作线程的耗时任务
    • Activity的onCreate()和onResume()回调中尽量避免耗时代码

    来自博文

    二十一、OOM

    1.什么是oom?

    当前占用的内存加上我们申请的内存资源超过了Dalvik虚拟机的最大内存限制就
    会抛出的out of memory 异常。

    博文

    二十二、bitmap

    1.recycle
    2.LRU

    LRUCache原理
    LruCache中维护了一个集合LinkedHashMap,该LinkedHashMap是以访问顺序(accessOrder为true,其余非构造函数此值全为false)排序的。当调用put()方法时,就会在集合中添加元素,并调用trimToSize()判断缓存是否已满,如果满了就用LinkedHashMap的迭代器删除队尾元素,即近期最少访问的元素。当调用get()方法访问缓存对象时,就会调用LinkedHashMap的get()方法获得对应集合元素,同时会更新该元素到队头。
    LinkedHashMap内部是使用双向循环链表来存储数据的。也就是每一个元素都持有他上一个元素的地址和下一个元素的地址。

    3.计算inSampleSize
    4.缩略图
    5.三级缓存

    二十三、UI卡顿问题

    1.UI卡顿原理

    60fps --> 16ms
    60fps:1秒60桢,1000/16
    16ms:android中每隔16ms会进行一个渲染,16ms以内完成不了就会导致界面
    的卡顿

    每次dalvik虚拟机进行GC的时候,所有的线程都会暂停,所以说在16ms内正在
    渲染的时候正好遇到了大量的GC操作,就会导致渲染时间不够,从而导致UI卡顿.

    overdraw:过度绘制,在屏幕上,它的某个像素在同一桢的时间内绘制了很多
    次,经常出现在多层次的UI结构中。可以在手机中的GPU选项中进行观察,有蓝
    色、淡绿、淡红、红色、深红色,减少红色尽量出现蓝色。

    2.UI卡顿原因分析

    1.人为的在UI线程中做轻微的耗时操作,导致UI线程卡顿;
    2.布局Layout过于复杂,无法在16ms内完成渲染;
    3.同一时间动画执行的次数过多,导致CPU或GPU负载过重;
    4.View过度绘制,导致某些像素在同一帧时间内被绘制多次,从而使CPU或GPU
    负载过重;
    5.View频繁的触发measure、layout,导致measure、layout累计耗时过多及整个View频繁的重新渲染;
    6.内存频繁触发GC过多(同一帧中频繁创建内存),导致暂时阻塞渲染操作;
    7.冗余资源及逻辑等导致加载和执行缓慢;
    8.臭名昭著的ANR;

    3.总结

    1.布局优化;尽量使用include、merge、ViewStub标签,尽量不存在冗余嵌套及过于复杂布局(譬如10层就会直接异常),尽量使用GONE替换INVISIBLE,使用weight后尽量将width和heigh设置为0dp减少运算,Item存在非常复杂的嵌套时考虑使用自定义Item View来取代,减少measure与layout次数等。

    2.列表及Adapter优化;尽量复用getView方法中的相关View,不重复获取实例导致卡顿,列表尽量在滑动过程中不进行UI元素刷新等。

    3.背景和图片等内存分配优化;尽量减少不必要的背景设置,图片尽量压缩处理显示,尽量避免频繁内存抖动等问题出现。

    4.自定义View等绘图与布局优化;尽量避免在draw、measure、layout中做过于耗时及耗内存操作,尤其是draw方法中,尽量减少draw、measure、layout等执行次数。

    5.避免ANR,不要在UI线程中做耗时操作,遵守ANR规避守则,譬如多次数据库操作等

    来自博文

    二十四、内存泄漏

    1.java内存泄漏基础知识

    该被释放的对象没有被释放,一直被某个或者某个实例所持有,导致不能垃圾回收

    博文

    二十五、内存管理

    博文

    二十六、冷启动优化

    1.什么是冷启动
    2.冷启动流程
    3.如何对冷启动的时间进行优化

    博文

    二十七、其他优化

    1.android中不用静态变量存储数据
    2.关于sp的安全问题
    3.内存对象序列化
    4.避免ui线程中做繁重的操作

    博文

    二十七、mvc/mvp/mvvm

    博文1
    博文2
    博文3

    二十八、插件化

    二十九、热修复
    原理
    三十、进程保活

    1.Android进程的优先级
    • 前台进程
    • 可见进程
    • 服务进程
    • 后台进程
      *空进程
    2.Android进程的回收策略
    • Low momery killer:通过一些比较复杂的评分机制,对进程进行打分,然后将分数高的进程判定为bad进程,杀死并释放内存。

    • OOM_ODJ:判读进程优先级

    3.进程保活方案
    • 利用系统广播进行拉活
    • 利用系统的Service机制拉活
    • 利用native进程拉活(5.0以后无法实现)
    • 利用 JobScheuler机制拉活(5.0以后新加)
    • 利用账号同步机制拉活

    博文
    保活

    三十一、UIL

    三十二、Lint

    1.什么是Android lint 检查

    Android Lint 是一个静态代码分析工具,它能够对你的Android项目中潜在的bug、可优化的代码、安全性、性能、可用性、可访问性、国际化等进行检查。

    2.工作流程

    他有个lint tool 工具,它会把我们的Android源代码和lint xml配置文件打包成一个文件之后,输出一个lint output ,并展现出具体哪行代码有问题,我们可以定位到具体问题所在,改写代码,保证上线时的代码质量

    3.配置lint

    1.配置lint.xml
    2.java代码和xml布局文件如何忽略lint检查
    3.自定义lint
    1)原因

    三十三、Kotlin

    1.kotlin到底是什么
    • 是一种基于JVM的编程语言
    • 是对java的一种扩展
    • kotlin支持函数式编程
    • kotlin类与java类能相互调用
    2.kotlin的环境搭建
    • Android studio安装kotlin插件
    • 编写kotlinActivity测试
    • kotlin会自动配置kotlin

    三十四、LruCache

    LruCache源码分析
    DiskLruCache

    LruCache是一个针对内存层面的缓存,基于LRU算法(最近最少使用)实现的。
    内部主要是使用LinkHashMap来存储数据,LinkHashMap是继承HashMap的,主要是重写了get方法,当获取数据的时候会把自己添加到节点的头部,保证处于链表的最前面;不常用的数据就会排在最后面,当缓存的数据满了之后,一般是申请进程空间的1/8,就会清楚掉排在最后的这些数据,trimToSize()方法中执行

    public V get(Object key) {
            //使用HashMap的getEntry方法,验证了我们所说的查询效率为O(1)
            LinkedHashMapEntry<K,V> e = (LinkedHashMapEntry<K,V>)getEntry(key);
            if (e == null)
                return null;
            e.recordAccess(this);//在找到节点后调用节点recordAccess方法 往下看
            return e.value;
        }
    
            //LinkedHashMapEntry
            void recordAccess(HashMap<K,V> m) {
                LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;
                if (lm.accessOrder) {//accessOrder该值默认为false 但是  在LruCache中设置为true
                    lm.modCount++;
                    remove();//该方法将本身节点移除链表
                    addBefore(lm.header);//将自己添加到节点头部 保证最近使用的节点位于链表前边
                }
            }
    

    三十五、算法

    八大算法

    相关文章

      网友评论

          本文标题:Android基础复习

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