知识点1:

1.子线程为什么不允许访问ui因为android中的ui控件不是线程安全的。

2.为什么不给Ui加上锁的机制,第一点 会让ui访问的逻辑变得复杂,其次降低ui访问的效率。

3.List转化成String[]

     public static String[] toStringArray(List<String> strList) {String[] array = new String[strList.size()];strList.toArray(array);return array;}

4.webview中的常用方法

(1)goBack():后退。
(2)goForward():前进。
(3)loadUrl(String url):加载指定URL对应的网页
(4)boolean zoomIn():放大网页。
(5)boolean zoomOut():缩小网页。

5.解决ScrollView嵌套listview只显示一行item得问题
重写listview

    @Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{    heightMeasureSpec=MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE>>2,MeasureSpec.AT_MOST);super.onMeasure(widthMeasureSpec, heightMeasureSpec);}

6.activity切换时的overridePendingTransition动画效果:

第一个参数为第一个Activity离开时的动画,第二参数为所进入的Activity的动画效果
淡入淡出效果
Activity.overridePendingTransition(R.anim.fade, R.anim.hold);
放大淡出效果
overridePendingTransition(R.anim.my_scale_action,R.anim.my_alpha_action);
转动淡出效果
overridePendingTransition(R.anim.scale_rotate,R.anim.my_alpha_action);
转动淡出效果
overridePendingTransition(R.anim.scale_translate_rotate,R.anim.my_alpha_action);
左上角展开淡出效果
overridePendingTransition(R.anim.scale_translate,R.anim.my_alpha_action);
压缩变小淡出效果
overridePendingTransition(R.anim.hyperspace_in,R.anim.hyperspace_out);
右往左推出效果
overridePendingTransition(R.anim.push_left_in,R.anim.push_left_out);
下往上推出效果
overridePendingTransition(R.anim.push_up_in,R.anim.push_up_out);
左右交错效果
overridePendingTransition(R.anim.slide_left,R.anim.slide_right);
放大淡出效果
overridePendingTransition(R.anim.wave_scale,R.anim.my_alpha_action);
缩小效果
overridePendingTransition(R.anim.zoom_enter,R.anim.zoom_exit);
上下交错效果
overridePendingTransition(R.anim.slide_up_in,R.anim.slide_down_out);

7.app的版本时在build中的defaultConfig中的versionName中设置的。

知识点2:

1.获取手机屏幕的密度

 float xdpi = getResources().getDisplayMetrics().xdpi;float ydpi = getResources().getDisplayMetrics().ydpi;

2.androidstudio中全局替换字符串,在包的下面右边replace in path

3.广播在不通的包下面,接受要加上Intent.addFlags(0x01000000);

4.通过jks文件查看签名

keytool -list -v -keystore D:\companyjks\aaaa-storepass 123456

5.intent在页面之间跳转时只可以带上keyvalue形式的数据 ,可以把对象继承Serializablera(序列化)然后就可以直接讲对象传输过去。

6.Onstart和onstop是从Activity是否可见的角度去分析
OnResume和onPauser是从activity是否在前台的角度去分析的

7.adb shell ps 或者adb shell ps|grep 包名 可以查看当前的进程信息

进程名以:开头的进程属于私有进程 不以:开头的进程属于全局进程,其他应用可以通过  ShareUID的方式可以和他跑在同一个进程中。

8.序列化时应该手动指定serialVersionUID的值,如过不指定,系统会自动生成hashcode值,这样的话如果增加或者删除了变量,则反序列化会失败,指定了serialVersionUID的值之后就不会,但是类的结构大声改变之后,就算指定了serialVersionUID的值也不会反序列化成功。

9.动画:

(1)ScrollTo/ScrollBy:操作简单,适合对View内容的话哦的那个
(2)动画:操作简单,主要适用于没有交互的view和实现复杂的动画效果
(3)改变布局参数:操作稍微复杂,适用于有交互的view。

10.直接用数据线去安装apk

adb install -r 应用程序.Apk。

11.使用adb命令去启动一个Activity

Adb shell am start -n  com.adam.collection.test/com.adam.collection.test.ui.MainActivity。

12.在View提供的方法当中:

getTop():获取到的是View自身的顶边到父布局顶边的距离
getLeft():获取到的是View自身的左边到父布局左边的距离
getRight():获取到的是View自身的右边边到父布局左边的距离
getBotton():获取到的是View自身的底边到父布局顶边的距离

在MotionEvient提供的方法中:

getX():获取点击事件距离控件左边的距离,即视图坐标
getY():获取点击事件距离控件顶边的距离,即视图坐标
getRawX():获取点击事件距离整个屏幕左边的距离,即绝对坐标
getRawY():获取点击事件距离整个屏幕顶边的距离,即绝对坐标

13:Androuid布局优化:

1.避免重绘ui,性能浪费
2.优化布局层级,view树不要太高,层级嵌套不要太深
3.使用include复用公共的布局
4.使用viewStub 区别于Gone是他在初始化时不会去加载
5.hierarchyviewer工具去快速找到树中冗余的布局
6LinearLayout比RelativeLayout性能好,RelativeLayout的功能比较复杂,会花费更多的cpu时间,但是在嵌套的时候,RelativeLayout的性能优于LinearLayout。
7.merge标签 在嵌套时,如果include里面的布局跟外部一样,里面的布局可以用merge来替代,来去除多余重复的层级
8.在自定义绘制view的时候  在onDraw方法中避免执行大量的耗时操作,会造成页面的卡顿。
9.不要用静态变量去引用当前的 context会造成Activity泄露,内存无法释放。
10.Activity的对象被单例模式所持有,而单例模式的特点是其生命周期和Applicatio保持一致,因此Activity对象无法被及时释放。
11.属性动画:从Android3.0开始,Google提供了属性动画,其中有一类无限循环的动画,如果在Activity中播放此类动画且没有在onDestory中停止动画,那么动画会一直播放下去,尽管已经无法在界面上看到动画效果了,并且这个时候Activity的View会被动画持有,而View又持有了Activity,最终Activity无法释放。下面的动画是无限动画,会泄露当前Activity,解决方法是在Activity的onDestory中嗲用animator.cancel()来停止动画。

14:Android内存优化

Bitmap优化:使用适合分辨率和大小的图片,及时回收内存,使用图片缓存。

代码优化:

1.对常量使用static修饰符
2.使用静态方法,静态方法会比普通方法提高15%左右的访问速度
3.减少不必要的成员变量。这点在Androidlint工具上已经集成检测了,如果一个变量可以定义为局部变量,则会建议你不要定义为成员变量。
4.减少不必要的对象,使用基础类型会比使用对象更加节省资源,同时更应该避免频繁创建短作用域的变量
5.尽量不要使用枚举,少用迭代器,枚举占用的内存空间要比整型大
6.对Cursor,ReCeiver,Sensor,File等对象,要非常注意对他们的创建,回收与注册,解注册
7.避免使用ioc框架,ioc通常使用注解,反射来进行实现,虽然现在java对反射的效率已经进行了很好的优化,但大量使用反射依然会带来性能的下降
8.使用RenderScript,OpenGl来进行非常复杂的绘图操作
9.使用SurfaceView来代替view进行大量 频繁的绘图操作
//SurfaceView适合频繁更新,被动更新的试图,像股票曲线
//view适合自己主动去更新视图
10.尽量使用视图缓存,而不是每次都执行inflate方法解析视图。
11.常量请使用static final来修饰
15:内存泄露分析工具MAT

设计模式知识点:

单例模式:
1.在sInstance=new Singleton时有三条原子操作:
(1)给Singleton的实力分配内存。
(2)调用Singleton()的构造函数,初始化成员字段
(3)讲sInstance对象指向分配的内存空间(此时sInstance就不是null了)。
由于编译器乱序执行 可能 1,3,2的顺序去执行 调用3之后被拿走使用了就会出错
还很难复现,可以给sInstance加上一个Volatile关键字。虽然会影响性能,但是程序的正确性也很重要。
单例模式一般没有接口,扩展很困难,若要扩展,除了修改代码基本上没有第二种途径可以实现,单例对象如何持有Context,那么很容易引发内存泄漏,此时需要注意传递给单例对象的Context最好时Application Context 引用使用Activity的Context话会造成Activity无法释放。
工厂方法模式:
产品实现类实现抽象产品方法,工厂实现类实现工厂抽象方法,工厂实现铲平的生产
抽象工厂模式:
在工厂方法模式的基础上添加接口,把方法都放在抽象方法中,由子类去实现,他的优点时高层不需要去关心产品时怎么实现的,只需要找到工厂类让他去创建就可以了。(女娲造人)
模板方法模式
在抽象类中有基本方法跟模板方法,模板方式实现了对基本方法的调度,完成固定的逻辑,上层只需要去调用模板方法就可以了 (悍马模型)
建造者模式
将一个复杂对象的构建与他的表示分离,使得同样的构建过程可以创建不同的表示(造各种不同功能的车型 能启动能停止 能启动能鸣笛) 创建一个产品类,创建一个抽象建造者,具体建造者实现抽象建造者,会实现产品里面的逻辑 并且有个返回产品的方法,最后导演类 产生了一个建造者拿到里面返回的产品。
建造者适合相同的方法,不同的执行顺序,产生不同的事件结果。
代理模式:
让其他对象提供一种代理以控制对这个对象的访问(代练着给张三打游戏,执行者时代练着,升级的时张三)代理者拿着游戏者去执行操作。
普通代理时有一个代理来执行用户的操作,方法不知道代理后面是谁,而强制代理时方法通过用户来找找到代理,这个代理时用户提供的。
原型模式
浅拷贝是指clone方法拷贝了本对象,对是对象内部的数组,引用对象等都不拷贝,还是指向原生对象的内部元素地址。所以拷贝出来的修改的是原型中的数据。深拷贝对私有的类变量进行独立的拷贝,比如说对象内部的数组,所以在拷贝出来对象修改的是拷贝出来的数组,对原型中的数组数据并没有改变。
总结:浅拷贝只拷贝对象,操作的还是原来原型中的数据,深拷贝是把所有的都拷贝一份,是完全独立出来的。
中介者模式:
用一个中介对象封装一系列的对象交互,中介者使各对象不需要显示地相互作用,从而使其耦合松散,而且可以独立地改变他们之间的交互。在各个不同的类中间形成一个中介类,其他类中需要跟中介类交互就可以,减少类与类之间的耦合程度。
优点:减少类间的依赖,把原来的一对多的依赖变成了一对一的依赖,同事类只依赖中介者,减少依赖,降低耦合。
缺点:会导致中介者会膨胀得很大,而且逻辑复杂,同事类越多,中介者得逻辑就越复杂。
命令模式:
命令模式是一个高内聚得模式,将一个请求封装成一个对象。有一个接收者角色,一个命令角色,一个调用者角色,命令角色给个命令给调用者,调用者根据命令让接收者去完成对应得功能。
优点:调用者和接收者之间没有依赖关系,调用者实现功能只需要调用Command抽象类得execute方法就可以,不需要了解接收者是谁。
缺点:回到直Command的类膨胀问题。
责任链模式:
使多个对象都有机会处理请求,从而避免了请求的发送者和接收者之间的耦合关系,将这些对象连成一条链,并沿着这条链传递该请求,知道有对象处理他为止(三从四德)
优点:将请求和处理分开,请求者可以不用知道是谁处理的,处理者可以不用知道请求的全貌,两者解耦,提高系统的灵活性。
缺点:性能问题,每个请求都要从链头便利到链尾,特别是在链比较长的时候,性能是一个非常大的问题,二是调试不方便,环节比较多的时候,由于采用了类似递归的方式,调试的时候逻辑可能比较复杂。
装饰模式:
装饰模式是动态地给一个对象添加一些额外的职责,就增加功能来说,装饰及模式相比生成子类更为灵活(成绩单)
优点:装饰类和被装饰类不会互相耦合,低耦合,装饰模式是继承关系的一个替代方案,装饰模式可以动态地扩展一个实现类的功能,类似于代理模式,扩展性非常好。
缺点:多层的装饰是很复杂的,如果最里层的装饰出现了问题,需要一层一层你排查,工作量会很大,系统的复杂度会升高。
使用环境:需要扩展一个类的功能额,或给一个类增加附加功能。
需要动态地给一个对象增加功能,这些功能可以再动态地撤销
需要为一批的兄弟类进行改装或加装功能,首选装饰模式。
策略模式:
定义一组算法,将每个算法都封装起来,并且使他们之间可以互换。(诸葛亮三个锦囊)
优点:策略模式本身定义的,只要实现抽象策略,他就成为策略家族的一个成员,通过封装角色对其进行封装,保障对外可以自由切换。
策略家族对外提供的访问接口就使封装类,简化了操作,同事避免了有条件语句判断
扩展性好,符合OCP原则。(开闭原则)
缺点:每个策略都是一个类,服用的可能性小,类数量增多。
上层模块必须知道有哪些策略,然后才能决定使用哪一个,这个与迪米特法则相违背。
类膨胀,对外暴露。
适配器模式:
将一个类的接口变成客户端所期待的另一种接口,从而使原本因接口不匹配而无法再一起工作的两个类能够在一起工作。
优点:让两个没有任何关系的类在一起运行,增加了类的透明性,具体的实现都委托给了源角色,而这些对高层次的模块使透明的,也是它不需要关心的,提高了类的复用度,灵活性特别好。
适配器模式使为了扩张而使用的,不应该在设计的时候考虑用它。
迭代器模式:
迭代器模式目前是一个没落的模式,基本上没人会单独写一个迭代器,除非是产品性质的开发,(它提供一种方法访问一个容器对象中各个元素,而又不需暴露该对象的内部细节)
计量不要自己写迭代器模式,使用java提供的Iterator一般就能满足你的需求。
组合模式:
将对象组合成树形结构以表示“部分-整体”的层次结构,使得用户对单个对象和组合对象的使用具有一致性。
把一组相似的对象当作一个单一的对象,比如说一个部门 是一个对象,这个对象里面会有很多相似的对象。
观察者模式:
定义对象间一种一对多的依赖关系,使得每当一个对象改变状态,则所有依赖与他的对象都会得到通知并被自动更新。(李斯监控韩非子)
优点:观察者和被观察者之间是抽象耦合,不管增加观察者还是被观察者都非常容易扩展,而且在java中都已经实现的抽象层级的定义,在系统扩展方面更是得心应手,建立一套触发机制,观察者可以完美地实现这里的链条形式。
缺点:一个被观察者,多个观察者,开发效率和运行效率问题需要考虑,在java中消息的通知默认是顺序执行,一个观察者卡死,会影响整体的执行效率,可以采用异步。
门面模式:
要求一个子系统的外部与其内部的通信必须通过一个统一的对象进行,门面模式提供一个高层次的接口,使得子系统更易于使用。
门面模式注重”统一的对象“也就是提供一个访问一个子系统的接口,除了这个接口不允许有任何访问子系统的行为发生。
把子系统屏蔽掉,只暴露一个门面接口。
优点:外界只依赖门面对象,与子系统无关,提高了灵活性。安全性。
缺点:不符合开闭原则,对修改关闭,对扩展开放。
备忘录模式:
在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象回复到原先保存的状态。
访问者模式:
封装一些作用于某些数据结构中的个元素的操作,它可以在不改变数据结构的前提下定义作用于这些元素的新的操作。
状态模式:
当一个对象内在状态改变时允许其改变行为,这个对象看起来像改变了其类。状态模式的信心时分装,状态的变更引起了行为的变更。
优点:避免了过多的switch…case或者if…else语句的使用,避免劳累程序的复杂性,提高系统的可维护性
体现了开闭原则和单一职责原则,每个状态都是一个子类,你要增加状态就要增加子类,修改状态只修改一个子类,封装性好,符合门面模式
缺点:子类太多造成类膨胀。
解释器模式:
给定一门语言,定义它得文法得一种表示,并定义一个解释器,该解释器使用该表示来解释语言中的句子。
优点:解释器是一个简单语法分析工具,它最显著的优点就是扩展性,修改语法规则只要修改相应的非终结符表达式就可以了,若扩展语法,则只要增加非终结符类就可以了。
缺点:解释器模式会引起类膨胀,解释器模式采用递归调用方法,还有一个效率问题。
享元模式:
享元模式是池技术的重要实现方式,使用共享对象可有效地支持大量的细粒度的对象,没有的对象直接直接创建,有的对象直接拿出来用 例子中使用的是Hashmap。避免过多的对象造成内存溢出。
优点:可以减少应用程序创建的对象,降低程序内存占用,增强程序的性能。
缺点:系统的复杂性提高,需要分离出外部状态和内部状态,而且外部状态具有固化特性,不应该随内部状态改变而改变,否则导致系统的逻辑混乱。
桥梁模式:
将抽象和实现解耦,使得两者可以独立地变化,生产的工厂new 一个不同的类就可以生产不同的东西。
抽象角色引用实现角色,抽象角色的部分实现是由实现角色完成的。
优点:抽象和实现分离,解决继承的缺点而提出的设计模式,实现不受抽象的约束,不用再绑定再一个固定的抽象层次上。
优秀的扩充能力,对外暴露的接口层允许增加实现,增加抽象
客户不用关心细节的实现,它已经由抽象层通过聚合关系完成了封装。

深入理解java虚拟机

1.所有的对象实例以及数组都应当在堆上分配。

2.方法区与java堆一样,是各个线程共享的内存区域,他用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据。

3.运行时常量池时方法区的一部分,class文件中有一项信息时常量池表,用于存放编译期生成的各种字面量与符号引用,这部分讲在类加载后存放到方法区的运行时长常量池。

4.引用计数算法,通过计数器来判断对象是否失活,主流的java虚拟机里面没有选用应用计数算法来管理内存
原因:计数算法有很多例外情况要考虑,必须要配合大量额外处理才能保证正确地工作,譬如单纯的引用计数就很难解决对象之间相互循环引用的问题。

public class ReferenceCountingGC {
public Object instance = null;
private static final int _1MB = 1024 * 1024;
/**
* 这个成员属性的唯一意义就是占点内存,以便能在GC日志中看清楚是否有回收过
*/
private byte[] bigSize = new byte[2 * _1MB];
public static void testGC() {
ReferenceCountingGC objA = new ReferenceCountingGC();
ReferenceCountingGC objB = new ReferenceCountingGC();
objA.instance = objB;
objB.instance = objA;
objA = null;
objB = null;
// 假设在这行发生GC,objA和objB是否能被回收?
System.gc();
}
}

可达性分析算法

在主流的Java虚拟机中基本上采用的都是可达性分析算法来判断对象的存活。
这个算法是通过一些“GC Roots”的对象作为起点,从这些节点开始向下搜索,
搜索走过的路径称之为引用链,当一个对象到“GC Roots”没有任何引用链的时候,
也就是说这个对象到“GC Roots”不可达,证明这个对象是不能再被引用了
,因此就会被垃圾回收器判断为可以被回收的对象。在虚拟机中可以作为“GC Roots”的对象主要有四种:(1)虚拟机栈引用的对象(3)方法区常量引用的对象(4)本地方法栈中引用的对象
(有引用之后 GC Roots下面就会有引用链)

5.引用分为强引用,软引用,弱引用和虚引用四种,这4种引用强度依次逐渐减弱。

强引用不会被回收
软引用在内存不够的时候就会被回收
弱引用只能生存到下一次垃圾收集发生为止无论内存是否足够,都会回收掉弱引用关联的对象
为一个对象设置虚引用的目的是为了能在这个对象被收集器回收时收到一个系统通知。

6.任何一个对象的finalize()方法只会被系统自动调用一次。

7.方法区中的回收
(1)回收常量,只要这个常量并没有被任何地方引用,就可以回收
(2)回收类

①该类中所有的实例都已经被回收,也就是java堆中不存在该类及其任何派生子类的实例。
②加载该类的类加载器已经被回收,这个条件出给是经过精心设计的可替换类加载器的场景,
如osgi,jsp的重加载,否则通常是很难达成的
③该类对应得java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类得方法。

Android开发艺术探索

Activity:

1.Activity中的onStart和onStop是从Activity是否可见的角度来回调的
,而onResume和onPause是从Activity是否位于前台这个角度来回调的。
2.在新Activity启动之前,栈顶的Activity需要先onPause,新Activity才能启动。
3.我们不应该在onPause中做重量级操作,因为下一个Activity要在onPause之后才能oncreate,
onPause太耗时的话,会影响下一个Activity出来的体验,可以在onStop中做一些数据处理。
4.onSaveInstanceState来保存当前Activity的状态 这个方法在onStop之前,和onPause没有关系,
可以在之前,可能在之后。(Activity异常终止)。Bundle是实现Cloneable接口来保存数据。
恢复的是用户输入的数据,ListView滚动的位置等。
5.Activity的优先级, 前台》可见非前台(Activity弹出对话框)》后台
6.android:configChanges="orientation|screenSize"
7.问题:为什么设置了android:configChanges="orientation|screenSize"之后旋转后数
据依然存在????????
8.在singleTask模式下,多次启动一个Activity不会生成多个Activity,只是会暂停一下
,onPause -->onNewIntent-->onResume 循环执行这三个方法。

IPC机制:
1.Messenger通信:
2.AIDL:

View:

1.View中得四个属性,top\left\right\bottom都是相对于父容器来说得。
2.从Android3.0开始,View增加了额外得几个参数x,y,translationX和translationY,
其中x,y是view左上角得坐标,而translationX和translationY是view左上角相对于父容器得偏移量。
3.getX/getY返回得是相当于当前View左上角得x和y坐标
,而getRawX/getRawY但会得是相对于手机屏幕左上角得x和y坐标。
4.Touchslop 是系统能识别出得被认为是滑动得最小距离,ViewConfiguration.get(getContext()).getScaledTouchSlop()。
5.监听滑动相关的,建以在onTouchEvent中实现,如果是监听双击这种行为的话
,那么就使用GestureDetector
6.总结:scrollTo/scrollBy:操作简单哪,适合对view内容的滑动动画:操作简单,主要适用于没有交互的view和实现复杂的动画效果。改变布局参数:操作稍微复杂,适用于有交互的view.

四大组件的工作过程:

1.停止一个Service要灵活采用StopSerrvice和unBindService这两个方法才能完全停止一个Service组件。
2.FLAG_INCLUDE_STOPPED_PACKAGERS表示已经停止的应用,这时广播会发送给已经停止的应用。FLAG_EXCLUDE_STOPPED_PACKAGES表示不包含已经停止的应用,整个时候广播不会发送给已经停止的应用。
从Android3.1开始,系统为所有广播默认添加FLAG_EXCLUDE_STOPPED_PACKAGES属性
3.补充2,一个应用处于停止状态分为两种情况,第一种是应用安装后未运行,
第二种是应用被手动或者其他应用强停了,Android3.1中广播的这个特性同样会影响开机广播,
从Android3.1开始,处于停止状态的应用同样无法接受到开机广播,而在Android3.1之前,
处于停止状态的应用是可以收到开机广播的。

ContentProvider:

1.ContentProvider是一种内容共享型组件,通过Binder向其他应用提供数据。
在ContentProvider所在的进程启动时,ContentProvider会同时启动并发布到AMS中,需要注意的是,
这首歌时候ContentProveder的onCreate要先于Application的onCreate而执行,
这是四大组件中的一个少有的现象。原因:源码中就是这样的。
2.当一个应用启动时,入口方法为ActivityThread的main方法,main方法是一个静态方法,
在main方法中创建ActivityThread的实例并创建主线程的消息队列,
然后在ActivityThread的attach方法中远程调用AMS的attachApplication方法并将ApplicationThread对象提供给AMS,ApplicationThread是一个Binder对象,
它的Binder接口时IApplicationThread,它主要用于ActivityThread和AMS之间的通信,
在AMS的attachApplication方法中,会调用ApplicAtionThread的bindApplication方法
,这个过程同样是跨进程完成的,bindApplication的逻辑会经过ActivityThread中
mH Handler切换到ActivityThread中执行,具体的方法是handleBindApplication.
在handleBindApplication方法中,ActivityThread会创建Application对象并加载ContentProvider,注意:ActivityThead会先加载ContentProvider,然后再调用ApplicAtion的onCreate方法。
3.ContentProvider启动后,外界就可以通过它所提供的增删改查这四个接口来操作ContentProvider中的数据源,
即insert,delete,update和query四个方法。这四个方法都是通过Binder来调用的,
外界无法直接访问ContentProvider,它只能通过AMS根据Uri来获取对应的
ContentProvider的Binder接口IcontentProvider,然后再通过IConentProvider来访问COn'tentPrOvider中的数据源。
4.一般来说ContentProvider都应该是单实例的,Contentprovider到底是不是单实例,
这是由它的android:multiprocess属性来决定的,当android:multiprocess为false时,
ContentProvider为单实例,这也是默认值:当android:multiprocess为true时,
ContentProvider为多实例,这个时候再每个调用者的进程中都存在一个ContentProvider对象
,由于在实际的开发中,并未发现多实例的ContentProvider的具体使用场景,
官方文档中的解释时这样可以避免进程间通信的开销,但是这在实际开发中仍然缺少使用价值,
因此,我们可以简单地认为ContentProvider都时单实例的。
5.ContentProvider的启动过程:访问ContentProvider需要通过ContentResolver
,ContentResolver是一个抽象类,通过Context的getContentResolver方法获取实际上是
ApplicAtionContentResolver对象,ApplicAtionContenResolver类继承了ContentResolver
并实现了ContentResolver的抽象方法。当ContentProvider所在的进程未启动时,
第一次访问它时机会触发ContentProvider的创建,当然这也伴随着ContentProvideru所在进程的启动,
通过ContentProvider的四个方法的任何一个都可以触发ContentProvider的启动过程,
这里选择query方法。
6.ContentProvider的query方法中,首先会去获得IContentProvider对象
,不管是通过acquireUnstableProvider方法还是直接通过acquireProvider方法,
他们的本质都时一样的,最终都时通过acquireProvider方法来获取ContentProvider
下面是ApplicationContentResolver的acquireProvider方法的具体实现。
首先会从ActivtyThread中查找是否已经存在目标Co'tentProvider了,如果存在就直接返回,
ActivityThread中通过mProviderMap来存储已经启动的ContentProvider对象。
7.如果目前ContentProvider没有启动,那么就发送一个进程间请求给AMS
让其启动目标ContentProvider,最后再通过installProvider方法来修改引用计数,
那么AMS是如何启动ContentProvider的尼?我们知道,ContentProvider被启动时会伴随着进程的启动
,再Ams中,首先会启动ContentPrOvider所在的进程,然后再启动ContentProvider,启动进程时由Ams的
startProcessLocked方法来完成的,其内部主要是通过Process的start方法来完成一个新进程的启动,
新进程启动后其入口方法为ActivityThread的main方法,
8.可以看到,ActivityThread的mai'n方法是一个静态方法,在它内部首先会
创建ActivityThread的实例并调用attach方法来进行一系列初始化,接着就开始进行消息循环了。
ActivityThread的attach方法会将ApplicAtionThread对象通过
Ams的attachApplication方法跨进程传递给ams,最终ams会完成ContentProvider的创建过程。
9.Ams的attachApplication方法调用了attachApplicationLocked方法,
attachApplicationLocked中又调用了ApplicAtionThread的bindApplication,这个过程是进程间调用
10.ActivityThread的bindApplication会发送一个BIND_APPLICATION类型的消息给mH
,mH是以一个Handler,它收到消息后会调用ActivityThread的handleBindApplication方法。
11.ActivityThread的handleBindApplication则完成了Application的创建以及ContentProvider的创建,
可以分为如下四个步骤:
(1)创建ContextImpl和Instrumentation。
(2)创建ApplicAtion对象。
(3)启动当前进程的ContentProvider并调用其onCreate方法。
(4)调用ApplicAtion的onCreate方法。
12.经过上面的四个步骤,Co'n'tentProvider已经成功启动,并且其所在进程的Application也已经启动,
这意味着COntentProvider所在的进程已经完成了整个的启动过程,然后与其实进程就可以通过AMS来访问这个
ContentProvider了,拿到了ContentProvider以后,就可以通过Ams来访问这个ContentProvider了
,拿到了ContentProvider以后,就可以通过与它所提供的接口方法来访问它了。
需要注意的是,这里的COntentProvider并不是原始的COntentProvider
,而是ContentProvider的Binder类型的对象IcontenProvider
,IContentProvider的具体实现是ContentProviderNative和ContentProvider.Transport
其中ContentProvider.Transpor继承了ContentProviderNative。
13.这里仍然选择query方法,首先其它应用会通过AMS获取到ContentProvider的Binder对象即IContentProvider
,而IContentProvider的实现者实际上是ContentProvider.Transport
。因此其他应用调用IcontentProvider的query方法时最终会以进程间通信的方式调用到ContentProvider.
Transport的query方法。
14.很显然,Co'n'tentProvider.Transport的query方法调用了ContentProvider的query方法
,query方法的执行结果再通过Bindder发返回给调用者,这样依赖整个调用过程就完成了,
除了query方法,insert,delete和update方法也时类似的。

Android的消息机制:

1.有时候需要在子线程中进行耗时的I/O操作,可能时读取文件或者访问网络,
当耗时操作完成以后可能需要在UI上做一些改变,由于Android开发规范的限制,
我们并不能在子线程中访问UI控件,否则就会触发程序异常,
这个时候通过Handler就可以将更新UI的操作切换到主线程中执行,因此,
本质上来说,Handler并不是专门用于更新UI的,它只是常被开发者用来更新UI。2.Andorid的消息机制主要是指Handler的运行机制,Handler的运行需要底层的MessageQueue和Looper的支撑。
MessageQueue的中文翻译是消息队列,顾名思义,它的内部存储了一组消息,
以队列的形式对外提供插入和删除的工作,虽然叫消息队列,
但是它的内部存储结构并不是真正的队列,而是采用单链表的数据结构来存储消息队列
Looper的中文翻译为循环,在这里可以理解为消息循环,
由于MessageQueue只是一个消息得存储单元,它不能去处理消息,
而Looper就填补了这个功能,Looper会以无线循环得形式去查看是否有新消息
,如果有得话就处理消息,否则就一直等着。
Looper中还有一个特殊得概念,那就是ThreadLocal,ThreadLocal并不是线程,
它的作用是可以在每个线程中存储数据,我们知道Handler创建的时候会采用
当前线程的Looper来构造消息循环系统,那么Handler内部如何获取到当前线程的Looper尼?
这就要使用ThreadLocal了,ThreadLocal并不是线程可以在不同的线程中互不干扰地存储并提供数据,
通过ThreadLocal可以轻松获取每个线程的Looper,当然需要注意的是,线程是默认没有Looper的,
如果需要使用Handler就必须为线程创建Looper,我们经常提到的主线程,也叫UI线程,它就是
ActivityThread,ActivityThread被创建时就会初始化Looper,这也是在主线程中默认可以使用Handler的原因。3.Android的消息机制主要是指Handler的运行机制预计Handler所附带的MesssageQueue和Looper的工作过程,
这三者实际上是一个整体,只不过我们在开发过程中比较多地接触到了Handler而已,
Handler的主要功能是将一个任务切换到某个指定的线程中去执行,
那么Andorid为什么要提供这个功能尼?或者说Andorid为什么需要提供在某个具体的线程中执行任务这种功能尼?
这是因为Android规定访问UI只能在主线程中进行,如果在子线程中访问UI,那么程序就会抛出异常,
ViewRootImpL对UI操作做了验证,这个验证工作是由ViewRootImpl的checkThread方法来完成的。4.针对checkThread方法中抛出的异常信息,由于这一点的限制,
导致必须在主线程中访问ui,但是Android又建以不要在主线程中进行耗时操作
,否则会导致程序无法响应即ANR,考虑这一种情况,加入我们需要从服务端拉取一些信息并将其显示在UI上,
这个时候必须在子线程中进行拉取工作,拉取完毕后又不能在子线程中直接访问UI
,如果没有Handler,那么我们的却没有办法将访问UI的工作切换到主线程中去执行,
因此,系统之所以提供Handler,主要的原因是为了解决在子线程中无法访问UI的矛盾。5.系统为什么不允许在子线程中访问UI尼?这是因为Android的UI控件不是线程安全的
,如果在多线程中并发访问可能会导致UI控件处于不可预期的状态,
那为什么系统不对UI控件的访问加上锁机制尼?缺点有两个:首先加上锁机制会让UI访问的逻辑变得复杂,
其次锁机制会降低UI访问的效率,因为锁机制会阻塞某些线程的执行,鉴于这两个缺点,
最简单且搞笑的方法就是采用单线程模型来处理UI操作,对于开发者来说也不是很麻烦,
只是需要通过Handler切换一下UI访问的执行线程即可。6.Handler创建时会采用当前线程的Looper来构建内部的消息循环系统,
如果当前线程没有Looper,那么就会报错。如何解决这个问题,只需要为当前线程创建Looper即可,
或者在一个有Looper的线程中创建Handler。7.Handler创建完毕后,这个时候其内部的Looper以及MessageQueue就可以和HandLer一起协同工作了
,然后通过Handler的post方法将一个Runnable投递到Handler内部的Looper中处理,
也可以通过Handler的send方法发送一个消息,这个消息同样会在Looper中处理,
其实post方法最终也是通过Send方法来完成的,当Handler的send方法被调用时,
它会调用MessageQueue的enqueueMessage方法将这个消息放入消息队列中,
然后Looper发现有新消息到来的,机会处理这个消息,最终消息中的Runnable或者Handler的handleMessage方法
就会被调用,注意Looper是运行在创建Handler所在的线程中的,
这样一来Handler中的业务逻辑就被切换到创建Handler所在的线程中去执行了。
(handler.post的本质还是在Handler.send中去实现的)。8.当某些数据是以线程为作用域并且不同线程具有不同的数据副本的时候,
就可以考虑采用ThreadLocal,比如说对于Handler来说,它需要获取当亲前线程的Looper,
很显然Looper的作用域解释线程并且不同的线程具有不同的Looper,这个时候
通过ThreadLocal就可以轻松实现Looper在线程中的存取,如果不采用ThreadLocal,
那么系统就必须提供一个全局的哈希表供handler查找指定线程的Looper,
这样一来就必须提供一个类似于LooperManager的类了,但是系统并没有这么做而是选择了ThreadLocal,
这就是ThreadLocal的好处。9.ThreadLocal另一个使用场景是复杂逻辑下的对象传递,比如监听器的传递,
有些时候一个线程中的任务过于复杂,这可能表现为函数调用栈比较深以及代码入口的多样性
,在这种情况下,我们又徐娅监听器能够贯穿整个线程的而执行过程,整个时候可以怎么做尼
?其实这时就可以采用ThreadLocal,才哦那个ThreadLocal可以让监听器作为i线程内的全局对象而存在,
在线程内部只要通过get方法就可以获取到监听器,如果不采用ThreadLocal,
那么我们能想到的可以能是如下两种方法
(1)将监听器通过参数的形式在函数调用栈中进行传递
(2)将监听器作为静态变量供线程访问
第一种方法是当函数调用栈很深的时候,通过函数参数来传递监听器对象这几乎是不可接受的,
这会让程序的设计看起来很糟糕
第二种方法是可以接受的,但是这种状态时不具有可扩充性的没比如同时又两个线程在执行,
那么就需要提供两个静态的监听器对象。
采用ThreadLocal,每个监听器对象都在自己的线程内部储存。10.从ThreadLocal的set和get方法可以看出,他们所操作的对象都时当前线程的localValues对象的tables数组,
因此在不同的线程中访问同一个ThreadLocal的set和get方法
,他们对ThreadLocal所作的读/写操作仅限于各自线程的内部
,这就是为什么ThreadLocal可以在多个线程中互不干扰地存储和修改数据
,理解Threadlocal的实现方式有助于理解Looper的工作原理。11.消息队列的工作原理:消息队列在Andorid中指的时MessAgeQueue,MessageQueue主要包含两个操作:
插入和读取。读取操作本身会伴随着删除操作,插入和读取对应的方法分别为enqueueMessage和next,
其中enqueueMessage的作用时往消息队列中插入一条消息,
而next得作用时从消息队列中取出一条消息并将其从消息队列中移除
,尽管MessaheQueue叫消息队列,但是它得内部实现并不是用的队列
,实际上它是通过一个单链表的数据结构来维护消息列表,单链表在插入和删除上比较有优势。
(1)enqueueMessage的主要操作其实就是单链表的插入操作
(2)next方法是一个无限循环的方法,如果消息队列中没有消息,那么next方法会一直阻塞在这里,当有新消息到来时,next方法会返回这条消息并将其从单链表中移除。12.Looper的工作原理:Looper在Android的消息机制中扮演着消息循环的角色,
具体来说就是它会不停地从MessageQueue中查看是否有新消息,如果有新消息机会立刻处理,
否则就一直阻塞在那里。在他的构造方法中它会创建一个MessageQueue即消息队列
,然后将当前线程的对象保存起来。13.Handler的工作需要Looper,没有Looper的线程就会报错,那么如何为一个线程创建Looper尼?
通过Looper.prepare()即可为当前线程创建一个Looper,接着通过Looper.loop()来开启消息循环。14.Looper除了prepare方法外,还提供了prepareMainLooper方法,
这个方法主要是给主线程也就是ActivityThread创建Looper使用的,
其本质也是通过prepare方法来实现的,由于主线程的Looper比较特殊
,所以Looper提供了一个getMainLooper方法,
通过他可以在任何地方获取到主线程的Looper,Looper也是可以退出的,
looper提供了quit和quitSafely来退出一个Looper,区别:
(1)Quit会直接退出Looper,
(2)quitSafely只是设定一个退出标记,然后把下次队列中的已有消息处理完毕后才安全地退出。
Looper退出后,通过Handler发送的消息会失败,这个时候Handler的send方法会返回false,
在子线程中,如果手动为其创建了Looper,那么在所有的事情完成以后应该调用quit方法来终止消息循环,
否则这个子线程就会一直处于等待的状态,而如果退出Looper以后,这个线程就会立刻终止,
因此建议不需要的时候终止Looper。
Looper最重要的一个方法是loop方法,只有调用了loop后,消息循环系统才会真正地起作用。
Looper的loop方法的工作过程也比较好理解,loop方法是一个死循环,
唯一跳出循环的方式是MessageQueue的next方法返回了null,当Looper的quit方法被调用时,
Looper就会调用MessageQueue的quit或者quitSafely方法来通知消息队列退出,
当消息队列被标记为退出状态时,他的next方法就会返回null。也就是说
,Looper必须退出,否则loop方法就会无限循环下去。Loop方法会调用MessageQueue的next方法来获取新消息,
而next是一个阻塞操作,当没有消息时,next方法会一直阻塞在那里,
这也导致loop方法一直阻塞在那里。
如果MessageQueye的next方法返回了新消息,Looper就会处理这条消息:
msg.target.dispatchMessage(msg),这里的msg.target是发送这条消息的Handler对象,
这样Handler发送的消息最终又交给它的disopatchMessage方法来处理了,
但是这里不同的是,Handler的dispachMessage方法是在创建Handler时所使用的Looper中执行的
,这样就成功地将代码逻辑切换到指定的线程中去执行了。15.Handler的工作原理:Handler的工作主要包含消息的发送和接受过程,
消息的发送可以通过post的一系列方法以及send的一系列方法来实现,
post的一系列方法最终是通过send的一系列方法来实现。16.可以发现,Handler发送消息的过程仅仅是向消息队列中插入了一条消息,
MessageQueue的next方法就会但会这条消息给Looper,Looper收到消息后就开始处理了
,最终消息由Looper交由Handler处理,即Handler的dispatchMessage方法会被调用,
这时Handler就进入了处理消息的阶段。17.Handler处理消息的过程如下:首先,检查Message的callback是否为null,
不为null就通过handleCallback来处理消息。Message的callback是一个Runnable对象,
实际上就是Handler的post方法所传递的Runnable参数,其次,检查mCallback是否为null,
不为null就调用mCallback的handleMessage方法来处理消息。18.通过Callback可以采用如下方式来创建Handler对象:Handler handler=new Handler(callback)
。那么Callback的意义是什么尼?可以用来创建一个Handler的实例但并不需要派生Handler的子类。
在日常开发中,创建Handler最常见的方式就是派生一个Handler的子类并重写其handleMessage方法来处理具体的消息,
而Callback给我们提供了另外一种使用Handler的方式,当我们不想派生子类时,就可以通过Callback来实现。19.Handler的一个默认构造方法public Handler(),这个构造方法会调用下面的构造方法,
很明显,如果当前线程没有Looper
的话,就会抛出“Can not create handler inside thread that has not called Looper.prepare()”这个异常,
这也解释了在没有Looper的子线程中创建Handler会引发程序异常的原因。20.主线程的消息循环:Android的主线程解释ActicityThread,主线程的入口方法为main,
在main方法中系统会通过Looper.prepareMainLooper()来创建主线程的Looper以及MessaeQueue,
并通过Looper.loop()来开启主线程的消息循环。
主线程的消息循环开始了以后,ActivityThread还需要一个Handler来和消息队列进行交互,
这个Handler就ActivirtyThread.H,它内部定义了一组消息类型,主要包含了四大组件的启动和停止等过程。21.ActivityThread通过ApplicationThread和AMS进行进程间通信
,AMS以进程间通信的方式完成ActivityThread的请求后回调ApplicationThread中的Binder方法,
然后AoolicationThread会向H发送消息,H收到消息后会讲AppLlicationThread中的逻辑及切换到
ActivityThread中去执行,即切换到主线程中去执行,这个过程就是主线程的消息循环模型。

主线程的消息循环模型

(1)ActivityThread通过ApplicationThread和AMS进行进程间通信。
(2)AMS以进程间通信的方式完成ActivityThread的请求后回调ApplicationThread中的Binder方法。
(3)然后ApplicationThread会向H发送消息。
(4)H收到消息后会将AppLlicationThread中的逻辑及切换到ActivityThread中去执行,即切换到主线程中去执行。

Andorid的线程和线程池:

1.在Andorid中可以扮演线程角色的还有很多,比如AsyncTask和IntentService,同时HandlerThread也是一种特殊的线程。
尽管AsyncTask,IntentService以及HandlerThread的表现形式都有别于传统的线程,但是他们的本质仍然是传统的线程,
对于AsyncTask来说,它的底层用到了线程池,对于IntentService和HandlerThread来说,他们的底层则直接使用了线程。2.主线程也叫UI线程,主线程的作用是运行四大组件以及处理他们和用户的交互,而子线程的作用则是执行耗时任务,比
如网络请求,I/O操作等。从Andorid3.0开始系统要求网络访问必须在子线程中进行,否则网络访问会失败并抛出异常,这
样做是为了避免主线程由于被耗时操作所阻塞从而出现ANR现象。3.AsyncTask:是一种轻量级的异步任务类,它可以在线程池中执行后台任务,然后把执行的进度和最终结果传递给主线程
中并在主线程中更新UI。从实现上来说,AsyncTask封装了Thread和Handler,通过AsybcTask可以更加方便地执行后台任务
以及在主线程中访问UI,但是AsyncTask并不适合进行特别耗时的后台任务,对于特别耗时的任务来说,建议使用线程池。
(***1)AsyncTask是一个抽象的泛型类***,
它提供了Params,Progress和Result这三个泛型参数,其中a.Params表示参数的类型,b.Progress表示后台任务的执行进度的类型,c.Result则表示后台任务的返回结果的类型。如果AsyncTask确实不需要传递具体的参数,那么三个泛型参数可以用Void来代替。
***(2)AsyncTask提供了4个核心的方法,***
         a.onPreExecute()在主线程中执行,在异步任务执行之前,此方法会被调用,一般可以用于做一=些准备工作。b.doInBackground(Params params),在线程池中执行,此方法用以执行异步任务,params参数表示异步任务的输入参数。在此方法中可以通过publishProgress方法来更新任务的进度,publishProgress方法会调用onProgressUpdate方法。另外此方法需要返回计算结果给onPostRxecute方法。c.onProgressUpdate(Progress..values),在主线程中执行,当后台任务执行进度发生改变时此方法会被调用。d.onPostExecute(Result result),在主线程中执行,在异步任务执行之后,此方法会被调用,其中result参数是后台         任务的返回值,即doInBackground的返回值。上面这几个方法,onPreExecute先执行,接着是doInBackground,最后才是onPostExecute。
除了上述四个方法以外,AsyncTask还提供了onCancelled()方法,它同样在主线程中执行,
当异步任务被取消时,onCancelled()方法会   被调用,这个时候onPosTExecute则不会被调用。

(3)AsyncTask的使用限制:

     a.AsyncTask的类必须在主线程中加载,这就意味着第一次访问AsyncTask必须发生在主线程,当然这个过程在Android4.1及以上版本中已经被系统自动完成。在Android5.0的源码中,可以查看ActivityThread的main方法,它会调用AsyncTask的init方法,这就满足了AsyncTask的类必须在主线程中进行加载这个条件了。b.AsyncTask的对象必须再主线程中创建。c.execute方法必须在UI线程调用。d.不要在程序中直接调用onPreExecute(),onPostExecute,doInBackground和onProgressUpdate方法。e.一个AsyncTask对象只能执行一次,即只能调用一次execute方法,否则会报运行时异常。f.在Andorid1.6之前,AsyncTask是串行执行任务的,Andorid1.6的时候AsyncTask开始采用线程池里处理并行任务,但是从Andorid3.0开始,为了避免AsyncTAsk所带来的并发错误,AsyncTask又采用一个线程来串行执行任务。尽管如此,在Android3.0以及后续的版本中,我们仍然可以通过AsyncTask的executeOnExecutor方法来执行任务。

(4)AsyncTask的工作原理:

首先系统会把AsyncTAsk的Params参数封装为FutureTask对象,FutureTask是一个并发类,
在这里它充当了Runnable的作用,接着这个FutureTask会交给SerialExecutor的execute方法去处理,
SerialExecutor的execute方法首先会把FutureTask对象插入到任务队列mTasks中,
如果这个时候没有正在活动的AsyncTask任务,
那么就会调用SerialExecutor的scheDuleNext方法来执行下一个AsyncTaSk任务。
同时当一个AsyncTask任务执行完后,AsyncTAsk会继续执行其他任务直到所有的任务都被执行为止,
从这一点可以看出,在默认情况下,AsyncTAsk是串行执行的。

(5)AsyncTask中有两个线程池

(SerialExecutor和THREAD_POOL_EXECUTOR)和一个Handler(InternalHandler),其中线程池SerialExecutor用于任务
的排队,而线程池THREAD_POOL_EXECUTOR用于真正地执行任务,InternalHandler用于将执行环境从线程池切换到主
线程,其本质仍然是线程的调用过程。

4.HandlerThread

 (1)HandlerThread继承了Thread,他是一种可以使用Handler的Thread,它的实现也很简单,就是在run方法中通过Looper.prepare()来创建消息队列,并通过Looper.loop()来开启消息循环,这样在实际的使用中就允许在HandlerThread中创建Handler了。(2)从HandlerThread的实现来看,它和普通的Thread有显著的不同之处,普通Thread主要用于在run方法中执行一个耗时任务,而HandlerThread在内部创建了消息队列,外界需要通过Handler的消息方式来通知Handlethread执行一个具体的任务,HandlerThread是一个很有用的类,它在Android中的一个具体的使用场景是IntentServIce,由于HandlerThread的run方法是一个无限循环,因此当明确不需要再使用HandlerThread是,可以通过它的quit或者quitSafeLy方法来终止线程的执行,
quit是直接退出looper;
quit是设置一个标记,当下次消息队列执行完之后再安全地退出。(3)IntentService是一种特殊的Service,它继承了Service并且它是一个抽象类,因此必须创建它的子类才能使用IntentService,IntentService可用于执行后台耗时的任务,当任务执行后它会自动停止,同时由于IntentService是服务的原因,这导致它的优先级比单纯的线程要高很多,所以IntentService比较适合执行一些高优先级的后台任务,因为它优先级高不容易被系统杀死。(4)当IntentService被第一次启动时,它的onCreate方法会被调用,onCreate方法会创建一个HandlerThread,然后使用它的Looper来构造一个Handler对象mServiceHandler,这样通过mServiceHandler发送的消息最终都会在HandlerThread中执行,从这个角度来看,IntentService也可以用于执行后台任务,每次启动IntentService,它的onStartCommand方法就会调用一次,IntentService在onStartCommand中处理每个后台 任务的Intent。(5)IntentService仅仅是通过mServiceHandler发送了一个消息,这个消息会在HandlerThead中被处理,mServiceHandler收到消息后,会将Intent都对象传递给onHandlerIntent方法去处理,注意这个Intent对象的内容和外界的startService(intent)中的intent的内容是完全一致的,通过这个Intent对象即可解析出外界启动IntentService时所传递的参数,通过这些参数就可以区分具体的后台任务,这样在onHandleIntent方法中就可以对不同的后台任务做处理了。当onHandleIntent方法执行结束后IntentxService会通过stopSelf(int startId)则会等待所有的消息都处理完毕后才终止服务。一般来说stopSelf(int startId)在尝试停止服务之前会判断最近启动服务的次数是否和startId相等,如果相等就立刻停止服务,不相等则不停止服务。(6)IntentService的onHandlerIntent方法是一个抽象方法,它需要我们在子类中实现,它的作用是从Intent参数中区分具体的任务并执行这些任务。如果目前只存在一个后台任务,那么onHandleIntent方法执行完这个任务后,stopSele(int satartId)机会直接停止服务,如果目前存在多个后台任务,那么当onHandlerIntent方法执行完最后一个任务时,stopSelf(int startId)才会直接停止服务,另外,由于每执行一个后台任务就必须启动一次IntentService,而IntentService内部则通过消息的方式向HandlerThread请求执行任务,Handler中的Looper是顺次处理消息得,这叫意味着IntentService也是顺序执行后台任务的,当有多个后台任务同时存在时们这些后台任务会按照外界发起的顺序排队执行。

5.Android中的线程池

(1)优点:

a.重用线程池中的线程,避免因为线程的创建和销毁所带来的性能开销。
b.能有效控制线程池的最大并发数,避免大量的线程之间因互相抢占系统资源而导致的阻塞现象。
c.能够对线程进行简单的管理,并提供定时执行以及指定间隔循环执行的等功能。

(2)ThreadPoolExecutor是线程池的真正实现,它的构造方法提供了一系列参数来配置线程池,

a.corePoolSize:线程的核心线程数,默认情况下,核心线程会在线程池中一直存活,即使他们处于闲置状态。如果将
ThreadPoolExecutor的allowCoreThreadTimeOut属性设置为true,那么闲置的核心线程在等待新任务到来时会有超时策略,
这个时间间隔由keepAliveTime所指定,当等待时间超过keepAliveTime所指定的时长后,核心线程就会被终止。b.maximumPoolSize:线程池所能容纳的最大线程数,当活动线程数达到这个数值后,后续的新任务将会被阻塞。c.keepAliveTime:非核心线程闲置时的超时时长,超过这个时长,非核心线程就会被回收,当ThreadPoolExexutor的
allowCoreThreadTimeOut属性设置为true时,keepAliveTime同样会作用于核心线程。d.unit:用于指定keepAliveTime参数的时间单位,这是一个枚举,常用的由TimeUnit.MILLISECONDS(毫秒),
TimeUnit.SECOND(秒)以及TimeUnit.MIMUTES(分钟)等。e.workqueue:线程池中的任务队列,通过线程池的execute方法提交的Runnable对象会存储在这个参数中。f.threadFactory:线程工厂,为线程池提供新线程的功能。ThreadFactory时一个接口,它只有一个方法:Thread new
Thread(Runnable r)。
g.除了上面的这些主要参数外,ThreadPoolExecuTor还有一个不常用的参数RejectedExecutionHandler handler。当线程池
无法执行新任务时,这可能时由于任务队列已满或者时无法成功执行任务,这个时候ThreadPoolExecuTor会调用handler的
rejectedExecution方法来通知调用者,默认情况下rejectedExecution方法会直接抛出一个RejectedExecutionException。
ThreadPoolExecutor为RejectedExecutionHandler提供了几个可选值:CallerRunsPolicy,AbortPolicy,DiscardPolicy和
DiscardOldestPolicy,其中AbortPolicy时默认值,它会直接抛出RejectedExecutionException,。

(3)ThreadPoolExecutor执行任务是大致遵循如下规则

a.如果线程池中的线程数量未达到核心线程的数量,那么会直接启动一个核心线程来执行任务。
b.如果线程池中的线程数量已经达到或者超过核心线程的数量,那么任务会被插入到任务队列中排队等待执行。
c.如果在步骤b中无法将任务插入到任务队列中,这往往是由于任务队列已满,这个时候如果线程数量未达到线程池规定的
最大值,那么会立刻启动一个非核心线程来执行任务。
d.如果步骤c中线程数量已经达到线程池规定的最大值,那么就拒绝执行此任务,ThreadPoolExecutor会调用RejectedExecutionHandler的rejectedExecution方法来通知调用者。

***(4)AsyncTask对THREAD_POOL_EXECUTOR这个线程池进行了配置,配***置后的线程池规格如
下:

a.核心线程数等于CPU核心数+1;
b.线程池的最大线程数为CPU核心数的2倍+1;
c.核心线程无超时机制,非核心线程在闲置时的超时时间为1秒;
d.任务队列的容量为128。

***(5)Android中最常见的四类具有不同功能特性的线程池,他们都直接或间接地通过配置

***ThreadPoolExecutor来实现自己的功能特性:a.FixedThreadPool:通过Executors的newFixedThreadPool方法来创建,它是一种线程数量固定的线程池,当线程处于空闲状态时,他们并不会被回收,除非线程池被关闭了。当所有的线程都处于活动状态时,新任务都会处于等待状态,直到有线程空闲出来。由于FixedThreadPool只有核心线程并且这些核心线程不会被回收,这意味着它能够更加快速地响应外界的请求。可以发现FixedThreadPool中只有核心线程并且核心线程没有超时机制,另外任务队列也是没有大小限制。b.CachedThreadPool:通过Executors的newCachedThreadPool方法来创建,它是一种线程数量不定的线程池,它只有非核心线程,并且其最大线程数为Integer.MAX_VALUE。由于Integer.MAX_VALUE是一个很大的数,实际上就相当于最大线程数可以任意大。当线程池的线程都处于活动状态时,线程池会创建新的线程来处理新任务,否则就会利用空闲的线程来处理新任务。
线程池中的空闲线程都有超时机制,这个超时时长为60秒,超过60秒闲置线程就会被回收,与FixedThreadPool不同的
是,CachedThreadPool的任务队列其实相当于一个空集合,这将导致任何任务都会立即被执行,因为在这种场景下
SynchronousQueue是无法插入任务的,SynchronousQueue是一个非常特殊的队列,在很多情况下可以把它简单理解为一
个无法存储元素的队列。从CachedThreadPool的特性来看,这类线程池比较适合执行大量的耗时较少的任务,当整个线程
池都处于闲置状态时,线程池中的线程都会超时而被停止 ,这个时候CachedThreadPool之中实际上是没有任何线程的,他几乎是不占用任何系统资源的。c.通过Executors的newScheduledThreadPool方法创建。他的核心线程数量是固定的,而非核心线程数是没有限制的,并且当非核心线程限制时会被立即回收,ScheduledThreadPool这类线程池主要用于执行定时任务和具有固定周期的重复任务。d.SingleThreadExecutor:通过Eexcuors的newSingleThreadExecutor方法来创建,这类线程池内部只有一个核心线程,它确保所有的任务都是在同一个线程中按顺序执行。SingleThreadExecutor的意义在于统一所有的外界任务到一个线程中,这使得在这些任务之间不需要处理线程同步的问题


Bitmap的加载和Cache

1.Bitmap在Android中指的是一张图片,可以是png格式也可以是jpg等其他常见的图片格式,那么如何加载一个图片尼?
BitmapFactory类提供了四种方法,decodeFile,decldeResource,decodeStream和decodeByteArray,分别用于支持从文件系
统,资源,输入流以及字节数组中加载出一个Bitmap对象,其中decodeFile和decodeResource又间接调用了
decodeStream方法,这四类方法最终是在Andorid的底层实现的,对应着BitmaopFactory类的几个native方法。2.如何高效地加载Bitmap尼?其实核心思想也很简单,那就是采用BitmapFactoty.Options来加载所需尺寸的图片。这里假
设通过ImageView来显示图片,很多时候ImageViews并没有图片的原始尺寸那么大,这个时候把整个图片加载进来后再设
给ImageView,这显然是没必要的,因为ImageView并没有办法显示原始的图片,通过BitmapFactory.Options就可以按一定
的采样率来加载缩小后的图片,将缩小后的图片在ImageView中显示,这样就会降低内存占用从而在一定程度上避免OOM,
提高了Bitmap加载时的性能。BitmapFactory提供的加载图片的四类方法都支持BitmapFactory.Options参数,通过他们就可
以很方便地对一个图片进行采样缩放。3.通过BitmapFactory.options来缩放图片,主要是用到了它的inSampleSize参数,即采样率。当inSampleSize为1时,采样
后的图片大小为图片的原始大小;当inSampleSize大于1时,比如为2,那么采样后的图片其宽、高均为原图大小的1/2,而
像素数为原图的1/4,其占有的内存大小也为原图的1/4。拿一张1024*1024像素的图片来说,假定采用ARGB8888格式存
储,那么它占有的内存为1024*1024*4,即4MB,如果inSampleSize为2,那么采样后的图片其内存其内存占用只有
512*512*4,即1MB。可以发现采样率inSampleSize必须是大于1的整数图片才会有缩小的效果,并且采样率同时作用于宽/
高,这将导致缩放后的图片大小以采样率的2次方形式递减,即缩放比例为1/(inSampleSize的2次方),比如inSampleSize
为4,那么缩放比例就是1/16。有一种特殊情况,那就是当inSampleSize小于1时,其作用相当于1,即无缩放效果,另外最
新的官方文档指出,inSampleSizede 取值应该总是为2的指数,比如1,2,4,8,16等等,如果外界传递给系统的
inSampleSize不为2的指数,那么系统会向下取整并选择一个最接近的2的指数来代替,比如3,系统会选择2来代替,但是
经过验证发现这个结论并非在所有的Andorid版本上都成立,因此把它当成一个而开发建议即可。4.通过采样率即可有效地加载图片,如何去获得采样率:
1)将BitmapFactory.Options的inJustDecodeBounds参数设为true并加载图片
2)从BitmapFactory.Options中取出图片的原始宽高信息,他们对应于outWidth和outHeight参数。
3)根据采样率的规则并结合目标View的所需大小计算出采样率inSampleSize。
4)将 BitmapFactory.Options的inJustDecodeBounds参数设为false,然后重新加载图片。
经过上面4个步骤,加载出的图片就是最终缩放后的图片,当然也有可能不需要缩放。这里说明一下inJustDecodeBounds
参数,当此参数设为true时,BitmapFactory只会解析图片的原始宽/高信息,并不会去真正地加载图片,所以这些操作是轻
量级的。另外需要注意的是,这个时候BitmapFactory获取的图片宽/高信息和图片的位置以及程序运行的设备有关,比如同
一张图片放在不同的drawable目录下或者程序运行在不同屏幕密度的设备上,这都可能导致BitmapFactory获取到不同的结
果,之所以会出现这个现象,这和Android的资源加载机制有关。

Android中的缓存策略:

1.LurCache是一个泛型类

它内部采用一个LinkedHashMap以强引用的方式存储外界缓存对象,其提供了get和put方法来完成缓存的获取和添加操
作,当缓存满时,LruCache会移除较早使用的缓存对象,然后再添加新的缓存对象
强引用:直接的对象引用;
软引用:当一个对象只有软引用存在时,系统内存不足时此对象会被gc回收;
弱引用:当一个对象只有弱引用存在时,此对象会随时被gc回收。

2.DiskLruCache用于实现存储设备缓存

即磁盘缓存,它通过将缓存对象写入文件系统从而实现缓存的效果。
(1)DiskLruCache的创建:DiskLruCache并不能通过构造方法来创建,它提供了open方法用于创建自身,open方法有四个
参数,其中
第一个参数表示磁盘缓存在文件系统中的存储路径。缓存路径可以选择sd卡上的缓存目录,具体是指
package_name/cache目录,建议:如果应用卸载后就希望删除缓存数据文件,那么就选择sd卡上的缓存目录,如果希望保留缓存数据那就应该选择sd卡上的其他特定目录。
第二个参数表示应用的版本号,一般设为1即可。当版本号发生改变时DiskLruCache会清空之前所有的缓存文件,而这个
特性在实际开发中作用并不大,很多情况下即使应用的版本号发生了改变缓存文件却依然是有效地,因此这个参数设为1比
较好。
第三个参数表示单个节点所对应的数据的个数,一般设为1即可。
第四个参数表示缓存的总大小,比如50MB,当缓存大小超出这个设定值后,DiskLruCache会 清除一些缓存从而保证总大小
不大于这个设定值。
(2)DiskLruCache的缓存添加:缓存添加操作是通过Editor完成的,Editor表示一个缓存对象的编辑对象,这里任然以图片缓
存举例,首先需要获取图片url所对应的key,然后根据key就可以通过edit()来获取Editor对象,如果这个缓存正在被编辑,那
么edit()会返回null,即DiskLruCache不允许同时编辑一个缓存对象,之所以要把url转换成key,是因为图片的url中很可能有特
殊字符,这将影响url在Android中直接使用,一般采用url的md5值作为key.
将图片的url转成key以后,就可以获取Editor对象了,对于这个key来说,如果当前不存在其他Editor对象,那么edit()就会返
回一个新的Editor对象,通过它就可以得到一个文件输出流。需要注意的时,由于前面在DiskLruCache的open方法中设置
了一个节点只能有一个数据,因此下面的DISK_CACHE_INDEX常量直接设为0即可。
(3)DiskLruCache的缓存查找:和缓存的添加过程类似,缓存查找过程也需要将url转换为key,然后通过DiskLruCache的get
犯法得到一个Snapshot对象。为了避免在加载图片过程中导致的OOM问题,一般不建议直接加载原始图片。
3.ImageLoader的实现:
LISVIEW中的列表错位问题,因为view的复用,当一个itemA正在从网络记载图片,它对应的ImageView为A,这个时候用
户快速向下滑动列表,很可能itemB复用了ImageViewA,然后等了一会之前的图片下载完毕了,如果直接给imageviewA设
置图片,由于这个时候imageviewA被itemB所复用,就出现了itemB中显示了itemA的图片。

4.图片的同步加载和异步加载接口的设计

同步加载其工作过程遵循如下几步:首先尝试从内存缓存中读取图片,接着尝试从磁盘缓存中读取图片,最后才从网络中
拉取图片。另外,这个方法不能再主线程中调用否则就抛出异常。
Looper.myLooper()==Looper.getmainLooper判断是否是主线程Looper
异步加载 bindBitmap方法会尝试从内存缓存中读取图片,如果读取成功就直接返回结果,否则会在线程池中去调用
loadBitmap方法。当图片加载成功后再将图片,图片的地址以及需要绑定的imageview封装成一个LoaderResult对象,然后
再通过mMainHandler向主线程发送一个消息,这样就可以在主线程中给ImageView设置图片了。 bindBitmap中用到了线程池和Handler,之所以用线程池是因为如果采用普通的线程去加载图片,随着列表的滑动这可能会产
生大量的线程,这样并不利于整体效率的提升。这里没有使用AsyncTAsk,是因为3.0以上的版本AsyncTask无法实现并发
的效果,

优化列表的卡顿现象:

首先,不要在getView中执行耗时操作,如果在getview方法中加载图片,肯定会导致卡顿,因为加载图片是一个耗时的操
作,这种操作必须通过异步的方式来处理。
其次,控制异步任务的执行频率,如果用户刻意地频繁上下滑动,这会在一顺便产生上百个异步任务,这些异步任务会造
成线程池的拥堵并随即带来大量的UI更新操作。解决方法可以考虑在列表滑动的时候停止加载图片,尽管这个过程是异步
的,等列表停下来以后再加载图片任然可以或者良好的用户体验。具体实现时,可以给Listview或者GridView设置
setOnScrollListener,并在OnScrollListener的onScrollStateChanged方法中判断列表是否处于滑动状态。如果是的话就停止
架子按图片
最后一般前两个步骤列表都不会又卡顿现象,但是在某些特殊情况下,列表还是会有偶尔的卡顿现象,这个时候还可以开
启硬件加速,绝大多数情况下,硬件加速都可以解决莫名其妙的卡顿问题可以通过设置android:hardwareAccelerated=”
true”即可未Activity开启硬件加速。

技术应用

1.方法数过多在地版本的设备上没有办法编译运行
2.一些性能优化建议
(1)避免创建过多的对象
(2)不要过多使用枚举,枚举占用的内存空间要比整型大
(3)常量请使用static final来修饰
(4)使用一些Android特有的数据结构,比如SparseArray和Pair等,他们都具有更好的性能;
(5)适当使用软引用和弱引用
(6)采用内存缓存和磁盘缓存
(7)尽量采用静态内部类,这样可以避免潜在的由于内部类而导致的内存泄露

Android 开发艺术探索 看不懂对着书敲慢慢理解,设计模式之禅总结,平时记录的笔记,3w多次字防止丢失,留存。相关推荐

  1. Android开发艺术探索完结篇——天道酬勤

    这片文章发布,代表着我已经把本书和看完并且笔记也发布完成了,回忆了一下我看Android群英传,只用了两个月,但是看本书却花了2016年05月04日 - 2018年07月16日,整整两年多,真是惭愧 ...

  2. 《Android开发艺术探索》图书勘误

    第一章 在13页提到"系统只在Activity异常终止的时候才会调用onSaveInstanceState与onRestoreInstanceState来储存和恢复数据,其他情况不会触发这个 ...

  3. Android开发艺术探索——第七章:Android动画深入分析

    Android开发艺术探索--第七章:Android动画深入分析 Android的动画可以分成三种,view动画,帧动画,还有属性动画,其实帧动画也是属于view动画的一种,,只不过他和传统的平移之类 ...

  4. Android开发艺术探索读书笔记(一)

    首先向各位严重推荐主席这本书<Android开发艺术探索>. 再感谢主席邀请写这篇读书笔记 + 书评.书已经完整的翻完一遍了,但是还没有细致的品读并run代码,最近有时间正好系统的把整本书 ...

  5. Android开发艺术探索--第二章IPC机制(2)之Binder

    最近在拜读任主席的Android开发艺术探索,现在看了一半,再回头看前面的,感觉跟没有看一样,所以还是把知识点总结一下吧,这一节咱们来讲一下IPC中的Binder 直观来说,Binder是Androi ...

  6. Android 开发艺术探索——第十章 Android的消息机制

    Android 开发艺术探索--第十章 Android的消息机制读书笔记 Handler并不是专门用于更新UI的,只是常被用来更新UI 概述 Android的消息机制主要值得就是Handler的运行机 ...

  7. Android开发艺术探索笔记

    <Android开发艺术探索>这本书在几年前就已经买了,陆陆续续看过几次,都没有看完,没有理解透. 最近重读<Android开发艺术探索>,读了两次,第一次读完感觉还有大量的知 ...

  8. Android开发艺术探索2

    Android开发艺术探索2 该系列文章为<Android开发艺术探索>读书笔记,仅作为学记录,勿喷. Android IPC简介 IPC是Inter-Process Communicat ...

  9. Android开发艺术探索1

    Android开发艺术探索1 该系列文章为<Android开发艺术探索>读书笔记,仅作为学记录,勿喷. Activity的生命周期 onCreate表示Activity正在被创建,可以做一 ...

最新文章

  1. Know more about Cache Buffer Handle
  2. 新型DDoS来袭 | 基于STUN协议的DDoS反射攻击分析
  3. 会不会导致内存泄漏_Java内存泄漏!为什么会泄漏?如何泄漏?怎么定位?
  4. CentOS7 搭建Pulsar 消息队列环境,CentOS(Linux)部署Pulsar,亲测成功,以及Python操作Pulsar实例驱动
  5. 为什么选择红黑树作为底层实现
  6. uva live 4394 String painter 区间dp
  7. 大小端转换代码(宏、函数方式)(浮点、整数)
  8. 上百套HTML5登录页面模板
  9. List转Map-JDK8实现
  10. 【清华大学陈渝】第一章 操作系统概述
  11. 学而思网校怎么查看回放 学而思网校查看回放教程
  12. Redis集群的部署
  13. 新建的module没有蓝色小块
  14. html查看蛋白质,怎么查询蛋白质的全部信息-- UniProKB数据库
  15. 企业logo添加到word的模板制作
  16. Gitbook:无法加载文件C:\Users\Administrator\AppData\Roaming\npm\\gitbook.ps1因为在此系统上禁止运行脚本的解决方法
  17. 洛谷_2762 太空飞行计划问题
  18. shell练习题(牛客网15道题)
  19. h5的开源播放器组件
  20. 解决导入nem-core2.jar包后所引发的maven install 失败的问题

热门文章

  1. 在windows2000中提示您无法关闭计算机!
  2. InfoPath 如何使用 XML 技术
  3. 很难找齐的文学知识-------值得收藏
  4. 人人网2017实习笔试-水仙花数
  5. 修复asf、wmv文件无法拖动播放—asftools实用教程
  6. 企业架构(EA)理论及其在国内金融业的实践综述
  7. 前端(vue/js)下载文件(xlsx、ts等格式)
  8. 搭建自己网站硬件选择
  9. QT4.8.6的交叉编译 - 编译平台:Ubuntu 20.04 LTS,目标平台:linux/imx6ull
  10. MPV房产通证(Master Property Value)是什么?