您现在的位置是:首页 >其他 >安卓闲谈吹水网站首页其他
安卓闲谈吹水
一、熟练掌握 Java 语言,面向对象分析设计能力,反射原理,自定义注解及泛型,多次采用设计模式重构项目
首先我们先了解什么是对象。
1.对象是由我们自己定义的类来创建出来的。
2.对象实际上就是类的具体实现。
(对象是类的一个实例,有状态和行为。)
(对象是实体,需要被创建)
那么什么是类?
1.在Java中把创建对象的"模板",称之为类(class)。
(它描述一类对象的行为和状态)
(类是规范,根据类的定义来创造对象)
什么是面向对象?
面向对象是一种直观而且程序结构简单的程序设计方法。
(以类的方式组织代码(模板),以对象的形式封装数据(具体化)。)
面向对象包含了面向对象的分析(OOA)、面向对象的设计(OOD)、面向对象的编程(OOP)。
面向对象的特性?
1)抽象:抽象是将一类对象的共同特征总结出来构造类的过程,包括数据抽象和行为抽象两方面。
2)继承:继承是从已有类得到继承信息创建新类的过程。
3)封装:通常认为封装是把数据和操作数据的方法绑定起来,对数据的访问只能通过已定义的接口。
4)多态性:多态性是指允许不同子类型的对象对同一消息作出不同的响应。
什么是面向对象分析设计能力?
OOA,面向对象分析(Object Oriented Analysis ) 的目的是获得对应问题的理解。
理解的目的是确定系统功能和性能要求。面向对象分析技术可以将系统的行为和信息的关系表示为迭代构造函数。
OOD,面向对象的设计 Object Oriented Design 的含义是设计分析模型和实现相应的源代码。
反射原理是什么?
Reflection(反射)
是指在运行状态中,对于任意一个类都能够知道这个类所有的属性和方法;
并且对于任意一个对象,都能够调用它的任意一个方法;
这种动态获取信息以及动态调用对象方法的功能我们也吧他称他为JAVA语言的反射机制。
反射使用的步骤如下
1.获取想要操作的类的Class对象,这是反射的核心,通过Class对象我们可以任意调用类的方法。
2.调用 Class 类中的方法,既就是反射的使用阶段。
3.使用反射 API 来操作这些信息。
反射实现原理主要分下几步:
第一步:首先调用了 java.lang.Class 的静态方法,获取类信息
主要是先获取 ClassLoader, 然后调用 native方法,获取信息。
class类信息获取到之后开始实例化,有两种(一:无参构造函数,二:有参构造函数)
第二步(无参构造函数): 调用 newInstance() 的实现方式
权限检测,如果不通过直接抛出异常;
查找无参构造器,并将其缓存起来;
调用具体方法的无参构造方法,生成实例并返回
第二步(有参构造函数):获取所有的构造器主要步骤
先尝试从缓存中获取
如果缓存没有,则从jvm中重新获取,并存入缓存,缓存使用软引用进行保存,保证内存可用
jvm获取 — getConstructor0() 为获取匹配的构造方器
先获取所有的constructors, 然后通过进行参数类型比较
找到匹配后,通过 ReflectionFactory copy一份constructor返回
否则抛出 NoSuchMethodException;
方法调用:
第一步,先获取 Method
获取所有方法列表(获取所有构造器的方法很相似,都是先从缓存中获取方法,如果没有,则从jvm中获取)
根据方法名称和方法列表,选出符合要求的方法
如果没有找到相应方法,抛出异常,否则返回对应方法
第二步,根据方法名和参数类型过滤指定方法返回(最优匹配或者精准匹配)
第三步,调用 method.invoke() 方法
获取反射中的Class对象有三种方法:
1.(Class.forName 静态方法) Class.forName(“类的路径”);当你知道该类的全路径名时,你可以使用该方法获取 Class 类对象。
Class clz = Class.forName("java.lang.String");
2.(类的.class 方法) 类名.class。这种方法只适合在编译前就知道操作的 Class。
Class clz = String.class;
3.(实例对象的 getClass() 方法)对象名.getClass()。
String str = new String("Hello");
Class clz = str.getClass();
反射创建类对象主要有两种方式:
Class的newInstance()
Constructor的newInstance()
反射的优缺点是什么?
优点:
(1)能够运行时动态获取类的实例,大大提高系统的灵活性和扩展性。
(2)与Java动态编译相结合,可以实现无比强大的功能
缺点:
(1)使用反射的性能较低
(2)使用反射相对来说不安全
(3)破坏了类的封装性,可以通过反射获取这个类的私有方法和属性
什么是自定义注解?
1.什么是注解
“定义:注解(Annotation),也叫元数据。一种代码级别的说明。
它是JDK1.5及以后版本引入的一个特性,与类、接口、枚举是在同一个层次。
它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释。
2.自定义注解
注解其实就是一种标记,可以在程序代码中的关键节点(类、方法、变量、参数、包)上打上这些标记,
然后程序在编译时或运行时可以检测到这些标记从而执行一些特殊操作。
自定义注解使用的基本流程:
1.定义注解——相当于定义标记;
2.配置注解——把标记打在需要用到的程序代码中;
3.解析注解——在编译期或运行时检测到标记,并进行特殊操作。
(注解的生命周期主要包含三个阶段:Java源文件阶段,编译到class文件阶段, 运行期阶段)
什么是泛型?
泛型是 JDK5 的一个新特性,是将类型明确的事情延后到创建对象或调用方法时,再去明确的特殊的类型;
使用泛型注意事项:
在泛型中不能使用基本数据类型,如果需要使用基本数据类型,那么就使用基本数据类型对应的包装类型。
byte----> Byte, short---> Short ,int----> Integer,long----> Long
double ----> Double ,float -----> Float,boolean-----Boolean,char-------》 Character
泛型的好处:
编译时,可以检查添加数据的类型,提高了安全性
减少了类型转化的次数,提高效率
泛型类要注意的事项:
1. 在类上自定义泛型的具体数据类型是在使用该类的时候创建对象时候确定的。
2. 如果一个类在类上已经声明了自定义泛型,如果使用该类创建对象 的时候没有指定 泛型的具体数据类型,那么默认为Object类型
3.在类上自定义泛型不能作用于静态的方法,如果静态的方法需要使用自定义泛型,那么需要在方法上自己声明使用。(静态方法中不能使用类的泛型))
如何自定义泛型?
什么是设计模式?
设计模式,即DesignPatterns,是指在软件设计中,被反复使用的一种代码设计经验。
使用设计模式的目的是为了可重用代码,提高代码的可扩展性和可维护性。
Java设计模式类型
根据模式是用来完成什么工作可以划分以下三类。
创建型模式: 用于描述“怎样创建对象”,它的主要特点是“将对象的创建与使 用分离”包含以下几种。
单例、原型、工厂方法、抽象工厂、建造者 5 种创建型模式。
结构型模式: 用于描述如何将类或对象按某种布局组成更大的结构,包含以下几种。
代理、 适配器、桥接、装饰、外观、享元、组合 7 种结构型模式。
行为型模式: 用于描述类或对象之间怎样相互协作共同完成单个对象都无法单独 完成的任务,以及怎样分配职责包含以下几种。
模板方法、策略、命令、职责链、状态、 观察者、中介者、迭代器、访问者、备忘录、解释器 11 种行为型模式。
== == == == == == == == == == == == == == == == == == == == == == == == == == == == == =
二、熟练掌握 JVM 原理,反射,动态代理以及对 ClassLoader 热修复有比较深的理解;
什么是JVM?
JVM是Java Virtual Machine(Java虚拟机)的缩写,JVM是一种用于计算设备的规范,
它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。
Java 虚拟机 (JVM)是提供运行时环境来驱动 Java 代码或应用程序的引擎。它将 Java 字节码转换为机器语言。
JVM 是 Java 运行环境 (JRE) 的一部分。在其他编程语言中,编译器为特定系统生成机器代码。
但是,Java编译器为称为Java 虚拟机的虚拟机生成代码。
JRE/JDK/JVM是什么关系?
JRE(JavaRuntimeEnvironment,Java运行环境)也就是Java平台。
JDK(Java Development Kit)是程序开发者用来来编译、调试java程序用的开发工具包。
JVM(JavaVirtualMachine,Java虚拟机)是JRE的一部分。Java语言最重要的特点就是跨平台运行。使用JVM就是为了支持与操作系统无关,实现跨平台。
什么是反射(Reflection )?
主要是指程序可以访问、检测和修改它本身状态或行为的一种能力。
JAVA机制反射是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;
对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
== == == == == == == == == == == == == == == == == == == == == == == == == == == == == =
三、熟悉常见数据结构和算法;
== == == == == == == == == == == == == == == == == == == == == == == == == == == == == =
四、熟悉 UI ,精通自定义 View ,能利用事件分发原理解决 UI 交互问题;
什么是自定义View?
自定义View,顾名思义就是现有的官方提供的View(控件)已经无法满足我们的日常看法的需要,需要我们自己去定义实现一个View。
View的绘制流程
它对应了View中的三个抽象方法
onMeasure: 测量View的宽高
onLayout: 计算View的位置并布局
onDraw: 绘制View
学习自定义View的三个关键点:
布局(指自定义View在哪个位置显示,包括测量和布局两方面,对应了onMeasure和onLayout方法)
绘制(绘制是让自定义View显示在屏幕上,对应了onDraw方法)
触摸反馈(比较高级的概念,它对应了自定义View的行为)
如何自定义View。通常有以下三种方法:
自定义组合View
继承系统控件或布局(系统View控件:如TextView,系统ViewGroup布局:如LinearLayout)
直接继承View/ViewGroup
自定义组合View
这种方法非常特殊,因为它不需要重写相关方法就可以直接使用,因此开发起来非常方便。
但是缺点也非常明显,它只能够通过现有的View(系统View)进行组合,如果我们需要自定义的View是形状非常不规则,
无法通过现有View直接组合得出的话,这种方法是无法满足要求的。
1. 自定义属性 (在values文件夹下,新建一个attrs.xml文件,并且自定义相关属性)
2. 自定义布局 (在layout文件夹,新建一个布局文件layout_custom_titlebar,并根据需要进行自定义布局)
3.实现自定义View(通过继承一个系统Layout父布局,并且将自定义View的布局和属性进行关联。再根据需要,编写一些功能代码)
4. 使用自定义View(用法非常简单,在需要使用的layout布局中,将自定义的View导入,并完善相关属性。最后在Java代码中进行调用即可)
自定义View的绘制
自定义绘制的方式是重写绘制方法 onDraw()
绘制的关键是 Canvas 的使用
Canvas 的绘制类方法: drawXXX() (关键参数:Paint)
Canvas 的辅助类方法:范围裁切和几何变换
可以使用不同的绘制方法来控制遮盖关系
自定义绘制的上手非常容易:提前创建好 Paint 对象,重写 onDraw(),把绘制代码写在 onDraw() 里面,就是自定义绘制最基本的实现。
Paint paint = new Paint();
canvas.drawCircle(300, 300, 200, paint); // 绘制一个圆
drawPath(Path path, Paint paint)( 该方法用于绘制自定义图形)
自定义View的布局
自定义View的布局实际上分为两个阶段,测量阶段和布局阶段.
在这个过程中,我们需要重写布局过程的相关方法:
测量阶段:onMeasure
布局阶段:onLayout
1. 重写onMeasure()来修改已有View的尺寸
重写onMeasure(),并调用super.onMeasure()触发原先的测量
用getMeasuredWIdth()和getMeasuredHeight()取得之前测得的尺寸,利用这两个尺寸来计算出最终尺寸
使用setMeasuredDimension()保存尺寸
2. 重写onMeasure()来全新计算自定义View的尺寸
重写onMeasure()把尺寸计算出来
把计算出来的结果用resolveSize()过滤一遍
3. 重写onMeasure()和onLayout()来全新计算自定义View的内部布局
(1) 重写onMeasure()来计算内部布局
计算子View的尺寸 调用每个子View的measure(),让子View自我测量
关键:1)宽度和高度的MeasureSpec的计算(EXACTLY(固定值) AT_MOST(尺寸上限)UNSPECIFIED(没有固定限制): 无限大)
2)结合开发者的要求(layout_xxx)和自己地可用空间(自己的尺寸上限 - 已用空间)
计算子View的位置并保存子View的位置和尺寸 根据子View给出的尺寸,得出子View的位置,并保存它们的位置和尺寸
计算自己的尺寸并保存 根据子View的位置和尺寸计算出自己的尺寸,并用setMeasuredDimension()保存
(2)重写onLayout()来摆放子View
自定义View的触摸反馈
自定义触摸反馈的关键:
重写 onTouchEvent(),在里面写上你的触摸反馈算法,并返回 true(关键是 ACTION_DOWN 事件时返回 true)。
如果是会发生触摸冲突的 ViewGroup,还需要重写 onInterceptTouchEvent(),在事件流开始时返回 false,
并在确认接管事件流时返回一次 true,以实现对事件的拦截。
当子 View 临时需要阻止父 View 拦截事件流时,可以调用父 View 的 requestDisallowInterceptTouchEvent() ,
通知父 View 在当前事件流中不再尝试通过 onInterceptTouchEvent() 来拦截。
事件分发原理。
Activity上的事件分发和Activity PhoneView DecorView ViewGroup view 密不可分
事件分发主要有以下几个方法:
1. dispatchTouchEvent(event):用于进行点击事件的分发
2. onInterceptTouchEvent(event):用于进行点击事件的拦截 注:只有 ViewGroup才有
3. onTouchEvent(event):用于处理点击事件
总结如下:
1. 所有的dispatchTouchEvent当为true的时候则直接消费(),为false的时候则传递给上一层的onTouchEvent方法,Activity例外false它也会消费掉,当为super方法的时候则向下一层的dispatchTouchEvent传递,注意:ViewGroup由于有onInterceptTouchEvent,他会先传递到onInterceptTouchEvent!!!
2.onInterceptTouchEvent(ViewGroup特有)为super/false时候可以向下一层传递到dispatchTouchEvent,当为true的时候,则表示要自己消化,则会传递到自身的onTouchEvent方法。
3.所有的onTouchEvent方法,当返回为true的时候表示自己要消费(结束),当为super/false的话则传递给上一层的onTouchEvent方法。
对ACTION_MOVE 和ACTION_UP事件的处理可以总结为
ACTION_DOWN事件在哪个控件消费了(return true), 那么ACTION_MOVE和ACTION_UP就会从上往下(通过dispatchTouchEvent)
做事件分发往下传,就只会传到这个控件,不会继续往下传,通俗理解就是ACTION_DOWN哪里结束,ACTION_MOVE 和ACTION_UP就传递到哪里结束。
View事件方法执行顺序
onTouchListener > onTouchEvent > onLongClickListener > onClickListener
事件分发的由来
安卓的View是树形结构的,View可能会重叠在一起,当我们点击的地方有多个View都可以响应的时候,
这个点击事件应该给谁呢?为了解决这一个问题,就有了事件分发机制。
事件分发的 "事件" 是指什么?
点击事件 (Touch事件)
事件分发的本质
将点击事件(MotionEvent)传递到某个具体的View & 处理的整个过程。(即 事件传递的过程 = 分发过程。)
事件在哪些对象之间进行传递?
Activity、ViewGroup、View。
事件分发的顺序
即 事件传递的顺序:Activity -> ViewGroup -> View
(即:1个点击事件发生后,事件先传到Activity、再传到ViewGroup、最终再传到View)
事件分发过程由哪些方法协作完成?
dispatchTouchEvent()、onInterceptTouchEvent() 和 onTouchEvent()
事件分发机制流程详细分析
主要包括:Activity事件分发机制、ViewGroup事件分发机制、View事件分发机制
Activity事件分发机制
事件分发机制,首先会将点击事件传递到Activity中,具体是执行dispatchTouchEvent()进行事件分发。
主要包括:dispatchTouchEvent()、onTouchEvent()
Activity.dispatchTouchEvent()>Window().superDispatchTouchEvent()>
DecorView.superDispatchTouchEvent()>ViewGroup.dispatchTouchEvent()>
Activity.onTouchEvent()当一个点击事件未被Activity下任何一个View接收/处理时,就会调用该方法
ViewGroup事件分发机制
从上面Activity的事件分发机制可知,在Activity.dispatchTouchEvent()实现了将事件从Activity->ViewGroup的传递,ViewGroup的事件分发机制从dispatchTouchEvent()开始。
核心要点
事件分发原理: 责任链模式,事件层层传递,直到被消费。
View 的 dispatchTouchEvent主要用于调度自身的监听器和 onTouchEvent。
View的事件的调度顺序是 onTouchListener > onTouchEvent > onLongClickListener > onClickListener。
不论View自身是否注册点击事件,只要 View 是可点击的就会消费事件。
事件是否被消费由返回值决定,true 表示消费,false 表示不消费,与是否使用了事件无关。
ViewGroup 中可能有多个 ChildView 时,将事件分配给包含点击位置的 ChildView。
ViewGroup 和 ChildView 同时注册了事件监听器(onClick等),由 ChildView 消费。
一次触摸流程中产生事件应被同一 View 消费,全部接收或者全部拒绝。
只要接受 ACTION_DOWN 就意味着接受所有的事件,拒绝 ACTION_DOWN 则不会收到后续内容。
如果当前正在处理的事件被上层 View 拦截,会收到一个 ACTION_CANCEL,后续事件不会再传递过来。
== == == == == == == == == == == == == == == == == == == == == == == == == == == == == =
五、研究 FrameWrok 层源码并能解决实际问题,尤其了解 AMS 的原理;
== == == == == == == == == == == == == == == == == == == == == == == == == == == == == =
六、熟系 UI 界面设计和优化实际经验丰富;
== == == == == == == == == == == == == == == == == == == == == == == == == == == == == =
七、对内存优化,用户体验优化有实际调优经验;
进行内存优化首先要知道的是Android 内存存在什么问题,哪里存在问题。
内存抖动
内存抖动即内存频繁分配和回收导致内存不稳定。频繁创建对象,导致内存不足或者产生内存碎片,内存碎片即内存不连续,
有内存空洞, 某两个正在使用的内存中间有一个间隔, 这个间隔虽然也被算在可用内存里面,
但实际上因为它过小, 当我们申请内存的时候,经常是需要申请一定量的连续内存,
而这些碎片小内存不符合要求,是不能拿来使用的。频繁GC会导致卡顿,随后不连续的内存片无法被分配,可分配的内存减少,便最终可能导致OOM。
简单来说就是:在程序需要对象的时候,在堆当中分配出来一块空间,使用完毕以后, GC 帮我们清理掉这片内存空间,如果频繁的一直持续上述操作,就会引起内存抖动。
内存抖动常见场景:
1)集合类存在不对称机制
集合类如果仅仅有添加元素的机制,而没有相应删除元素机制,这样就会造成内存被占用,
如果这个类是全局性变量(比如类中有静态属性,全局性的map等即有静态引用或final一直指向它)。
那么没有相应删除机制,很可能导致集合所占内存只增不减。
解决办法:在使用集合类时,增加删除元素机制,并适当调用减少集合所占内存。
2)不正确使用单例模式
不正确使用单例模式,也会引起内存泄漏单例对象在初始化后将在JVM的整个生命周期存在(以静态变量方式),
如果单例对象持有外部对象的引用,那么这个外部对象就会一直占用着内存,可能导致内存泄漏(取决于这外部对象是否一致有用)。
解决办法:单例对象中避免含有不是一直都有用的外部对象引用。
3)不正确使用Android组件 或 特殊集合对象
BraodcastReceiver ,ContentObserver ,fileObserver ,Cursor,Callback 等在 Activity onDestory 或者某类生命周期结束之后一定要 unregistere 或者 close 掉,
否则这个Activity类会被system强引用,不会被回收。
不要直接对Activity进行直接引用作为成员变量,如果不得不这么做,调用 private WeakPeferense mActivity 来做,
相同的,对与 Service 等其他有自己生命周期的对象来说,直接引用都需要考虑是否会存在内存泄露的可能。
4)Handler 使用问题
只要 Handler 发送的 Message 尚未被处理,则该 Message 及发送它的 Handler 对象将被线程 MessageQueue 一直持有。
由于 Handler 属于 TLS(Thread Local Storage)变量,生命周期和 Activity 是不一致的。
因此这种实现方式一般很难保证跟 view 或者 Activity 的生命周期保持一致,故很容易导致无法正确释放。
如上所述,Handler使用要特别小心,否则很可能内存泄漏。
解决办法:在view 或者Activity生命周期结束前,确保Handler已没有未处理的消息(特别是延时消息)。
5)Thread 内存泄漏
线程也是造成内存泄露的一个重要源头,线程产生内存泄露的主要原因在于线程生命周期不可控,比如线程是 Activity的内部类,
则线程对象中保存了 Activity 的一个引用,当线程的 run函数耗时较长没有结束时,线程对象是不会被销毁的,因此它所引用的老的Activity就出现了内存泄漏问题。
解决办法:
1. 简化线程run函数执行的任务,使他在Activity生命周期结束前,任务运行完。
2. 为Thread增加撤销机制,当Activity生命周期结束时,将Thread的耗时任务撤销(推荐)。
6)一些不良代码造成的内存压力
有些代码并不造成内存泄漏,但是他们是对没使用的内存没进行有效及时的释放,或是没有有效的利用已有的对象而是频繁的申请新内存。
(1) Bitmap 没调用recycle()
Bitmap 对象在不使用时,我们应该先调用recycle()释放内存,然后才置空,因为加载bitmap对象的内存空间,
一部分是java的,一部分是c的(因为Bitmap分配的底层是通过jni调用的,Android的Bitmap底层是使用skia图形库实现,skia是用c实现的)。
这个recycle()函数就是针对c部分的内存释放。
(2)构造Adapter时,没有使用缓存的convertView。
解决办法:使用静态holdview的方式构造Adapter。
内存泄漏
所谓内存泄漏即是本该被回收的内存垃圾没有被回收。
与内存抖动的区别是:
内存抖动是指:在短时间内频繁发生申请内存 & 回收内存的操作,即频繁GC
== == == == == == == == == == == == == == == == == == == == == == == == == == == == == =
八、熟悉 tcp / ip , http 协议,熟悉socket 通信,具备相关性能调优能力;
== == == == == == == == == == == == == == == == == == == == == == == == == == == == == =
九、有实际写过 JNI 代码与 NDK 底层代码封装的经验;
== == == == == == == == == == == == == == == == == == == == == == == == == == == == == =
十、熟悉 C / C ++, Kotlin , Dart 等语言;
== == == == == == == == == == == == == == == == == == == == == == == == == == == == == =
十一、有 MVP / MVVM 架构搭建项目的实际开发经验;
== == == == == == == == == == == == == == == == == == == == == == == == == == == == == =
十二、对模块化,组件化开发架构有深入的研发经验;
== == == == == == == == == == == == == == == == == == == == == == == == == == == == == =
十三、研究小程序开发,研究过 Flutter Dart 等开发;