1.性能优化
卡顿的主要两大要素
a.数据处理:一般分为三种情况,数据处理在UI线程,二是数据处理占用CPU较高导致主线程拿不到时间片,三是内存增加导致GC频繁,从而引起卡顿
b.界面绘制:绘制的布局嵌套层级太深、页面复杂元素太多还有刷新的不合理。一般出现在启动后初始化界面和跳转页面的绘制上。
布局优化
首先要理解android系统绘制时的流程,是对View进行测量。通过遍历View的个数来一个个进行绘制。如果一个布局的View数过高就会影响测量的速度,
所以首先写布局时不要嵌套太深,从而提高UI的渲染效率
布局复用,多使用标签重用layout;提高显示速度,也可以减少层级,使用标签替换父布局;使用wrap_content会增加measure的计算成本,去掉一些
控件无用的属性
绘制优化
过渡绘制时值在屏幕上的某个像素在同一帧的时间内呗绘制了多次。在多层次重叠的UI结构中,如果不可见的UI也会在做绘制操作,就会导致某些像素
区域被绘制了多次,从而浪费多余的CPU和GPU资源
布局的优化上。移除xml中非必须的背景,移除Window默认的背景、根据需求显示占位背景图片
自定义View优化
使用canvas.clipRect()帮助系统识别那些可见的区域,只有在这个区域内才会被绘制
启动优化
把一些耗时的操作放在异步去处理,第三放SDK的初始化(例如推送、统计、分享、广告等等,初始化有些会耗时的),中间要注意地板版系统耗时会更
严重
电量优化
计算优化:算法、for循环优化、switch..case代替if..else、避开浮点运算
避免Wake Lock使用不当
4 安装包瘦身
安装包的组成结构
assets文件夹。存放一些配置文件、资源文件,assets不会生成对应的id,要通过AssetsManager类的接口获取。
res 是resource的缩写,这个目录存放资源文件,会自动生生对应的id并映射到R文件中,访问直接使用资源ID
META-INF 保存应用的签名信息,签名信息可以验证APK文件的完整性。
AndroidManifest.xml 这个文件用来描述Android应用的配置信息,一些组件的注册信息、可使用权限
classes.dex Dalvik字节码程序,让Dalvik虚拟机可执行,一般情况下应用在打包时通过AndroidSDK的dx工具讲java字节码转换成Dalvik字节码
resources.arsc 记录资源文件和资源文件id之间的映射关系,用来根据资源id寻找资源
减少安装包大小
代码混淆 使用IDE自带的proGuard代码混淆工具,它包括压缩、优化、混淆等功能。资源优化。比如删除无用的资源,对图片压缩处理。
google开源库zopfil。避免重复依赖或无用的第三方库一些第三方库里面有依赖eventbus图片加载。使用微信的文件混淆工具-AndResGuard。
冷启动与热启动
冷启动 在启动应用时,系统中没有改应用的进程,这是系统会创建一个新的进程分配给该应用
热启动 在启动应用时,系统有改进程,例如返回退到桌面,home键返回到桌面
两者区别
冷启动,系统中没有该应用的进程,需要创建一个新的进程分配给应用,所有会创建和初始化Application,再创建和初始化MainActivity
里面包括测量、布局、和绘制。热启动从已有的进程中来启动,不会创建和初始化Application,直接创建和初始化Mainactivity包括一系列的测
量、布局和绘制。最后显示在手机界面上
冷启动优化
减少在Application和第一个Activity的onCreat()方法的工作量;不要让Application参与业务的操作;不要在Application进行耗时操作
Android中的四大组件以及应用场景
Activity: 在Android应用中负责与用户交互的组件
Service:常用与为其他组件提供后台服务或者监控其他组件的运行状态。经常来执行一些耗时操作
BroadcastReceiver:用于监听应用程序中的其他组件
ContentProvider:Android应用程序之间实现实时数据交换
Activity的生命周期 onCreat Activity开始创建用户还看不到界面
onStart 创建完成,已经能看到Activity但是不能进行交互,此时Activity还没有焦点
onResume Actitivity获得焦点,可以和用户进行交互
onPause Activity失去焦点但是还处于可见状态
onStop Activity在为销毁做准备,处于不可见状态
onDestory Activity 销毁被回收掉
异常下的声明周期:
在Actitivity配置发生改变或者资源不足(如屏幕旋转),当前Activity会销毁,并且在onStop之前回调onSaveInstanceState保存数据,在
重新创建Activity的时候在onStart之后调用onRestoreInstanceState恢复数据。其中的Bundle数据会传到onCreat(不一定有数据)和
onRestoreInstanceState(一定有数据)
防止屏幕旋转的时候重建,在manifest添加配置 configChanges:"orientation"
Fragment的生命周期
对应Activity的状态表示
Activity State Fragment Callbacks
Created onAttach()
onCreat()
onCreatView()
onActivcityCreat()
Started onStart()
Resumed onResume()
Paused onPause()
Stopped onStop()
Detoryed onDestroyView()
onDestroy()
onDetach()
Activity的启动模式
1.standard:每次启动都会创建一个新的Activity实例放入任务栈;
2.singleTop:如果Activity自己启动自己,相当于接下来要启动的Activity已经处于栈顶了,则不会创建该Activity实例
会调用onNewIntent;
3.singleTask:如果接下来要启动的Activity在任务栈中已经存在该实例,则不会创建该Activity实例,只会把此Activity放入栈顶,
并调用onNewIntent;
4.singleInstance:应用1的任务栈中创建了某个Activity实例,如果应用2也要激活该Activity,则不需要创建,两应用共享该Activity
Android引入4大组件的意义
四大组件的本质是为了实现移动或者嵌入式设备上的MVC架构,他们之间有时候是一种相互依存的关系,有时候又是一种补充关系,
引入广播可以方便几大组件的信息和数据交互
程序间互通消息(在自己应用程序监听来电、电量变化)
设计模式上反转控制的一种应用,类似监听者模式
Android中touch事件的传递机制
1.touch事件传递有dispatchTouchEvent、onTouchEvent、onIntercepterTouchEvent
2.Touch事件相关的类有View、ViewGroup、Activity
3.Touch事件封装在MotionEvent对象,该对象封装了手势按下、移动和松开等动作
4.Touch事件通常从Activity的dispatchTouchEvent触发,只要没被消费,会一直往下传递,到最底层的View
5.如果Touch事件传递的每个View都不消费事件,那么Touch事件会反向向上传递,最终由Activity的onTouchEvent处理
6.onIntercepterTouchEvent为ViewGroup特有,可以拦截事件
7.Down事件来时,如果View没有消费该事件,那么后续的move、up都不会给它
事件分发中的onTouch和onTouchEvent有什么区别,又该如何使用
这两个方法都是在View的 dispatchTouchEvent中调用的,onTouch优先于onTouchEvent执行。如果onTouch方法中通过返回
ture消费掉,onTouchEvent将不会执行
Android事件分发机制
基本会遵从Activity=>ViewGroup=>View的顺序进行事件分发,然后通过调用onTouchEvent()方法进行事件的处理
一般情况下,事件列都是从用户ACTION_DOWN的那一刻产生的
distatchTouchEvnet()
onTouchEvnet()
onInterceptTouchEvnet()
Actuivity的事件分发机制
distatchTouchEvent()是负责事件分发的,当点击事件产生后,事件会首先传递到当前的Activity,这会调用Activity的
disTatchTouchEvent()方法。
源码中
根据传递进来的MotionEvent对象
ev(MotionEvent).getAction()==MotionEvent.ACTION_DOWN
这里可以看出 事件开始都是从down开始
接着调用onUserInteraction();
getWindow().superDispatchTouchEvent(ev)如果返回ture则
Activity.dispatchTouchEvnet()也返回true,,停止事件传递
否则返回onTouchEven(ev)
再看下onUserInterAction()方法,源码中此方法是空的,注解里面是实现屏保功能。
再来看看第二个 if 语句,getWindow().superDispatchTouchEvent(),getWindow() 明显是获取 Window,
由于 Window 是一个抽象类,所以我们能拿到其子类 PhoneWindow,直接调用了 DecorView 的 superDispatchTrackballEvent() 方法。
DecorView 继承于 FrameLayout,作为顶层 View,是所有界面的父类。而 FrameLayout 作为 ViewGroup 的子类,
所以直接调用了 ViewGroup 的 dispatchTouchEvent()。
接着来看ViewGroup的dispatchTouchEvent()可以发现
源码中定义了个boolen值intercept来表示是否拦截事件
中间使用onInterceptTouchEvent(ev)对intercept进行赋值,大多数情况下onInterceptTouchEvent()返回false,
但我们可以通过重写onInterceptEnvent(ev)来改变它的返回值。接着看intercept做了什么处理
里面的代码涉及稍微多
但是可以看到的是里面有个for循环,通过倒序遍历ViewGroup下面子View,然后一个一个判断点击区域是否是
该子View的布局区域
接下来的事就交到View层了,ViewGroup说到底还是一个View。
首先还是distatchTouchEvent()
必须满足三个条件都为真,才会返回true
1.mTouchListener不为null,也就是调用了setOnTouchLisenter()
2.(mViewFlags&ENABLED_MASK)==ENABLED
该条件是判断当前点击的控件是否非enable,但基于基本的view都是enable的,所以这个条件都是返回的true
3.li.mOntouchListener.onTouch(this,event)
改方法是调用setOnTouchListener()时必须覆盖onTouch()的返回值
从这里可以看出onTouch方法优先级高于onTouchEvent(event)方法
再来看onTouchEvent()
要View的CIICKABLE和LONG_CLICKABLE有一个为true为true,那么onTouchEvent()就会返回true消耗这个事件。
最后在ACTION_UP事件中会调用performclick()方法。
描述Handle机制
1.Looper:
一个线程可以产生一个Looper对象。由他来管理此线程里的MessageQueue(消息队列)
2.Handler:
你可以构造Handler对象与Looper沟通,以便push新消息到MessageQueue里,或者接到Looper从Message取出所送来的消息
3.Meeage Queue(消息队列)用来存放线程放入的消息
4.线程:UIThread通常就是main thread,而Android启动时会替它建立一个MessageQueue。
Handler持有UI主线程消息队列MessageQueue和循环Looper的引用,子线程可以通过Handler将消息发送到UI线程的消息队列
MessageQueue中。
自定义View的基本流程
1.自定义View的属性 编写attr.xml
2.在Layout布局文件中引用,同时引用命名空间
3.在View的构造方法中获取我们自定义的属性,在自定义控件中进行读取(构造方法拿到attr.xml文件值)
4.重写onMesure
5.重写onDraw(在onDraw方法里面不要进行耗时操作,也不要创建Bitmap 会造成卡顿)
说说 LruCache 底层原理
LruCache使用一个LinkeHashMap简单的实现内存的缓存,没有软引用,都是强引用
如果添加的数据大于设置的最大值,就删除最先缓存的数据来调整内存,maxSize是通过构造方法初始化的值
,他表示这个缓存能缓存的最大值是多少
size在添加和移除缓存都被更新至,他通过safeSizeOf这个方法更新值,safeSizeOf默认返回1,但一般我们会根据
maxSize重写这个方法,比如maxSize代表kb的话,那么就以kb为单位返回该项所占的内存大小
注意LruChache是不接受null的key和value会报异常
除异常情况下,首先会判断size是否超过maxSize,如果超过了就取最先插入的缓存,如果不为就删除,并把size减去该
项所占的大小,这个操作将一直循环下去,直到size比maxSize小或者缓存为空
HashMap
HashMap是非安全的线程,和HashTable一样都实现了Map接口
1.HashMap几乎可以等价于HashTable,除了HashMao是非synchronized的,并可以接受null的key和value
2.由于HashMap是非线程安全的,多个线程是不能共享HashMap的,因此也导致在单线程环境下HashTable
的效率要相对于低一些
3.HashMap不能保证随着时间推移Map中的元素次序不变
ArrayList、LinkedList和Vector 的区别
ArrayList 本质上是一个可改变大小的数组.当元素加入时,其大小将会动态地增长.内部的元素可以直接通过get与set方法进行访问.
元素顺序存储 ,随机访问很快,删除非头尾元素慢,新增元素慢而且费资源 ,较适用于无频繁增删的情况 ,比数组效率低,如果不是需要
可变数组,可考虑使用数组 ,非线程安全.
LinkedList 是一个双链表,在添加和删除元素时具有比ArrayList更好的性能.但在get与set方面弱于ArrayList. 适用于 :没有大规
模的随机读取,大量的增加/删除操作.随机访问很慢,增删操作很快,不耗费多余资源 ,允许null元素,非线程安全.
Vector (类似于ArrayList)但其是同步的,开销就比ArrayList要大。如果你的程序本身是线程安全的,那么使用ArrayList是更好
的选择。 Vector和ArrayList在更多元素添加进来时会请求更大的空间。Vector每次请求其大小的双倍空间,而ArrayList每次对size增长50%.
数据类型的了解
Java的两大数据类型
(一)内置数据类型(基本数据类型)
1.六种数字类型(byte 8,shot 16,int 32,long 64,float 32,double 64)+void
2.一种字符类型char 16位Unicode字符
3.一种布尔类型boolen 1位
(二)引用数据类型
引用类型变量的由类的构造函数创建,可以使用它们访问所引用的对象。这些变量在声明时被指定一个特定的类型,变量一旦声明,类型就
不能改变
对象、数组都是引用数据类型,所有引用类型的默认值都是null
基本数据类型只能按值传递,而封装类按引用传递
void无返回值类型,座位伪类型对应类的对象,也被认为是基本数据类型
Android设计模式
面向对象的六大原则
1.单一职责原则
所谓职责是指类变化的原因。如果一个类有多余一个的动机呗改变,那么这个类就具有一个的职责。而单一职责原则就是指一个类或者模块
应该只有一个改变的原因。即一个类只负责一项职责将一组相关性很高的函数、数据封装到一个类中
2.开闭原则
对于扩展是开放的,这意味着模块的行为是可以扩展的。当应用的需求改变时,我们可以对模块进行扩展,使其具有满足那些改变的新行为。
对于修改是关闭的,对模块行为进行扩展时,不必改动模块的源代码
3.里氏替换原则
使用抽象和多态将设计中静态结构改为动态结构,维持设计的封闭性。任何基类可以出现的地方,子类一定可以出现。
代码中将一个基类替换成它的子类对象,程序不会产生任何错误和异常,反过来则不成立。在设计中尽量使用基类类型来对对象进行定义。在
运行时再确定其子类类型,用子类对象替换父类对象。
4.依赖倒置原则
高层次模块不应该依赖低层次的模块,他们都应该依赖于抽象。抽象不应该依赖于具体实现,具体实现应该依赖于抽象。简单说就是要求对
对抽象进行设计,不要对实现进行编辑设计。
5.接口隔离原则
一个类对另一个类的依赖应该建立在最小的接口上。其原则是非常庞大而且臃肿的,要拆分成更小的具体的接口
6.迪米特原则
又叫做最少知识原则,就是说一个对象应当对其他对象有尽可能少的了解。通俗的讲,一个类应该对自己需要耦合或调用的类知道的最少
谈谈你在工作中是怎样解决一个Bug
异常附近打日志信息,了解方法里面执行的顺序和数据的情况,如果是正好在这附近基本上问题差不多可以解决,
也有可能是上层方法导致的,这时也可以通过上层方法debug观察为什么在异常附近会造成数据异常,方法顺序不对,一般常见的
对象写错,本来是A方法,马胡写成B方法。还有调用系统的API可能不太数据,误以为是自己想要的方法,但实际上不是,再就是
缺乏一些条件判断,类似非空
String、StringBuffer和StringBuilder的区别
概念:
1、用来处理字符串常用的类有3种:String、StringBuffer和StringBuilder
2、三者之间的区别:
都是final类,都不允许被继承;
String类长度是不可变的,StringBuffer和StringBuilder类长度是可以改变的;
StringBuffer类是线程安全的,StringBuilder不是线程安全的;
String 和 StringBuffer:
1、String类型和StringBuffer类型的主要性能区别:String是不可变的对象,因此每次在对String类进行改变的时候都会生成一个新的string对象,然后将指针指向新的string对象,所以经常要改变字符串长度的话不要使用string,因为每次生成对象都会对系统性能产生影响,特别是当内存中引用的对象多了以后,JVM的GC就会开始工作,性能就会降低;
2、使用StringBuffer类时,每次都会对StringBuffer对象本身进行操作,而不是生成新的对象并改变对象引用,所以多数情况下推荐使用StringBuffer,特别是字符串对象经常要改变的情况;
3、在某些情况下,String对象的字符串拼接其实是被Java Compiler编译成了StringBuffer对象的拼接,所以这些时候String对象的速度并不会比StringBuffer对象慢
StringBuilder
StringBuilder是5.0新增的,此类提供一个与 StringBuffer 兼容的 API,但不保证同步。该类被设计用作 StringBuffer 的一个简易替换,用在字符串缓冲区被单个线程使用的时候(这种情况很普遍)。如果可能,建议优先采用该类,因为在大多数实现中,它比 StringBuffer 要快。两者的方法基本相同
使用策略
1、基本原则:如果要操作少量的数据,用String ;单线程操作大量数据,用StringBuilder ;多线程操作大量数据,用StringBuffer。
2、不要使用String类的”+”来进行频繁的拼接,因为那样的性能极差的,应该使用StringBuffer或StringBuilder类,这在Java的优化上是一条比较重要的原则,例如:
String result = "";
for (String s : hugeArray) {
result = result + s;
}
// 使用StringBuilder
StringBuilder sb = new StringBuilder();
for (String s : hugeArray) {
sb.append(s);
}
String result = sb.toString();
当出现上面的情况时,显然我们要采用第二种方法,因为第一种方法,每次循环都会创建一个String result用于保存结果,除此之外二者基本相同
3、 StringBuilder一般使用在方法内部来完成类似”+”功能,因为是线程不安全的,所以用完以后可以丢弃。StringBuffer主要用在全局变量中
4、相同情况下使用 StirngBuilder 相比使用 StringBuffer 仅能获得 10%~15% 左右的性能提升,但却要冒多线程不安全的风险。而在现实的模块化编程中,负责某一模块的程序员不一定能清晰地判断该模块是否会放入多线程的环境中运行,因此:除非确定系统的瓶颈是在 StringBuffer 上,并且确定你的模块不会运行在多线程模式下,才可以采用StringBuilder;否则还是用StringBuffer
如何线程安全的使用 HashMap
其实也就是 HashMap,Hashtable,ConcurrentHashMap 和 synchronized Map 的原理和区别。当时有些紧张只是简单说了下HashMap不是线程安全的;Hashtable 线程安全,但效率低,因为是 Hashtable 是使用 synchronized 的,所有线程竞争同一把锁;而 ConcurrentHashMap 不仅线程安全而且效率高,因为它包含一个 segment 数组,将数据分段存储,给每一段数据配一把锁,也就是所谓的锁分段技术。
为什么线程不安全
个人觉得 HashMap 在并发时可能出现的问题主要是两方面,首先如果多个线程同时使用put方法添加元素,而且假设正好存在两个 put 的 key 发生了碰撞(根据 hash 值计算的 bucket 一样),那么根据 HashMap 的实现,这两个 key 会添加到数组的同一个位置,这样最终就会发生其中一个线程的 put 的数据被覆盖。第二就是如果多个线程同时检测到元素个数超过数组大小* loadFactor ,这样就会发生多个线程同时对 Node 数组进行扩容,都在重新计算元素位置以及复制数据,但是最终只有一个线程扩容后的数组会赋给 table,也就是说其他线程的都会丢失,并且各自线程 put 的数据也丢失。
关于 HashMap 线程不安全这一点,《Java并发编程的艺术》一书中是这样说的:
HashMap 在并发执行 put 操作时会引起死循环,导致 CPU 利用率接近100%。因为多线程会导致 HashMap 的 Node 链表形成环形数据结构,一旦形成环形数据结构,Node 的 next 节点永远不为空,就会在获取 Node 时产生死循环。
哇塞,听上去si不si好神奇,居然会产生死循环。。。。 google 了一下,才知道死循环并不是发生在 put 操作时,而是发生在扩容时。
如何线程安全的使用HashMap
了解了 HashMap 为什么线程不安全,那现在看看如何线程安全的使用 HashMap。这个无非就是以下三种方式:
Hashtable:HashTable 源码中是使用 synchronized 来保证线程安全的
ConcurrentHashMap:ConcurrentHashMap (以下简称CHM)是 JUC 包中的一个类,Spring 的源码中有很多使用 CHM 的地方。 CHM 摒弃了 Segment(锁段)的概念,而是启用了一种全新的方式实现,利用 CAS 算法
Synchronized Map:从源码中可以看出调用 synchronizedMap() 方法后会返回一个 SynchronizedMap 类的对象,而在 SynchronizedMap 类中使用了 synchronized 同步关键字来保证对 Map 的操作是线程安全的。