如果你想在Android Studio中引入一个library到你的项目,你只需添加如下的一行代码到模块的build.gradle文件中。

1
2
3
dependencies {
compile 'com.inthecheesefactory.thecheeselibrary:fb-like:0.9.3'
}

就是如此简单的一行代码,你就可以使用这个library了。

酷呆了。不过你可能很好奇Android Studio是从哪里得到这个library的。这篇文章将详细讲解这是怎么回事,包括如何把你的库发布出去分享给世界各地的其他开发者,这样不仅可以让世界更美好,还可以耍一次酷。

Android studio 是从哪里得到库的?

先从这个简单的问题开始,我相信不是每个人都完全明白Android studio 是从哪里得到这些library的。莫非就是Android studio 从google搜索然后下载了一个合适的给我们?

呵呵,没那么复杂。Android Studio是从build.gradle里面定义的Maven 仓库服务器上下载library的。Apache Maven是Apache开发的一个工具,提供了用于贡献library的文件服务器。总的来说,只有两个标准的Android library文件服务器:jcenter 和 Maven Central。

jcenter

jcenter是一个由 bintray.com维护的Maven仓库 。你可以在这里看到整个仓库的内容。

我们在项目的build.gradle 文件中如下定义仓库,就能使用jcenter了:

1
2
3
4
5
allprojects {
repositories {
jcenter()
}
}

Maven Central

Maven Central 则是由sonatype.org维护的Maven仓库。你可以在这里看到整个仓库。

注:不管是jcenter还是Maven Central ,两者都是Maven仓库

我们在项目的build.gradle 文件中如下定义仓库,就能使用Maven Central了:

1
2
3
4
5
allprojects {
repositories {
mavenCentral()
}
}

注意,虽然jcenter和Maven Central 都是标准的 android library仓库,但是它们维护在完全不同的服务器上,由不同的人提供内容,两者之间毫无关系。在jcenter上有的可能 Maven Central 上没有,反之亦然。
除了两个标准的服务器之外,如果我们使用的library的作者是把该library放在自己的服务器上,我们还可以自己定义特有的Maven仓库服务器。Twitter的Fabric.io 就是这种情况,它们在https://maven.fabric.io/public上维护了一个自己的Maven仓库。如果你想使用Fabric.io的library,你必须自己如下定义仓库的url。

repositories {
maven { url ‘https://maven.fabric.io/public‘ }
}
然后在里面使用相同的方法获取一个library。

dependencies {
compile ‘com.crashlytics.sdk.android:crashlytics:2.2.4@aar’
}
但是将library上传到标准的服务器与自建服务器,哪种方法更好呢?当然是前者。如果将我们的library公开,其他开发者除了一行定义依赖名的代码之外不需要定义任何东西。因此这篇文章中,我们将只关注对开发者更友好的jcenter 和 Maven Central 。

实际上可以在Android Studio上使用的除了Maven 仓库之外还有另外一种仓库:Ivy 仓库 。但是根据我的经验来看,我还没看到任何人用过它,包括我,因此本文就直接忽略了。

(http://www.open-open.com/lib/view/open1435109824278.html)

ThinkInJava

第 1 章 对象入门

面向对象编程(OOP)具有多方面的吸引力。对管理人员,它实现了更快和更廉价的开发与维护过程。对分析
与设计人员,建模处理变得更加简单,能生成清晰、易于维护的设计方案。对程序员,对象模型显得如此高
雅和浅显。此外,面向对象工具以及库的巨大威力使编程成为一项更使人愉悦的任务。

1.1 抽象的进步

五大基本特征:

  1. 所有东西都是对象
  2. 程序是一大堆对象的组合
  3. 每个对象都有自己的存储空间,可容纳其他对象。
  4. 每个对象都有一种类型
  5. 同一类所有对象都能接收相同的消息

    1.2 对象的接口

    所有对象——尽管各有特色——都属于某一系列对象的一部分,这些对象具有通用的特征和行为。
    在面向对象的程序设计中,尽管我们真正要做的是新建各种各样的数据“类型”(Type),但几乎所
    有面向对象的程序设计语言都采用了“class”关键字。当您看到“type”这个字的时候,请同时想到
    “class”;反之亦然。
    如何利用对象完成真正有用的工作呢?必须有一种办法能向对象发出请求,令其做一些实际的事情,比如完
    成一次交易、在屏幕上画一些东西或者打开一个开关等等。每个对象仅能接受特定的请求。我们向对象发出
    的请求是通过它的“接口”(Interface)定义的,对象的“类型”或“类”则规定了它的接口形式。“类
    型”与“接口”的等价或对应关系是面向对象程序设计的基础。
    例子

    1.3 实现方案的隐藏

    “接口”(Interface)规定了可对一个特定的对象发出哪些请求。然而,必须在某个地方存在着一些代码,
    以便满足这些请求。这些代码与那些隐藏起来的数据便叫作“隐藏的实现”
    Java 采用三个显式(明确)关键字以及一个隐式(暗示)关键字来设置类边界:public,private,
    protected 以及暗示性的friendly(default)。若未明确指定其他关键字,则默认为后者。

“public”(公共)意味着后续的定义任何人均可使
用。

“private”(私有)意味着除您自己、类型的创建者以及那个类型的内部函数成员,其
他任何人都不能访问后续的定义信息。private 在您与客户程序员之间竖起了一堵墙。若有人试图访问私有成员,就会得到一个编译期错误。

“friendly (default)”(友好的)涉及“包装”或“封装”(Package)的概念——
即 Java 用来构建库的方法。若某样东西是“友好的”,意味着它只能在这个包装的范围内使用(所以这一访问级别有时也叫作“包装访问”)。

“protected”(受保护的)与“private”相似,只是一个继承的类可访问受保护的成员,但不能访问私有成员。

1.4 方案的重复使用

为重复使用一个类,最简单的办法是仅直接使用那个类的对象。但同时也能将那个类的一个对象置入一个新类。我们把这叫作“创建一个成员对象”。新类可由任意数量和类型的其他对象构成。无论如何,只要新类
达到了设计要求即可。这个概念叫作“组织”——在现有类的基础上组织一个新类。有时,我们也将组织称
作“包含”关系,比如“一辆车包含了一个变速箱”。

继承的频繁使用会大大增加程序的复杂程度。相反,新建类的时候,首先应考虑“组织”对象;这样做显得更加简单和灵活。利用对象的组织,我们的设计可保持清爽。

1.5 继承:重新使用接口

在 Java 语言中,继承是通过 extends 关键字实现的 使用继承时,相当于创建了一个新类。这个新类不仅包含了现有类型的所有成员(尽管private 成员被隐藏起来,且不能访问),但更重要的是,它复制了基础类的方法。

1.5.1 改善基础类

重写(Override)覆盖继承到的那个方法,那个方法仍然没有放弃。
重写是子类对父类的允许访问的方法的实现过程进行重新编写, 返回值和形参都不能改变。即外壳不变,核心重写!
重写的好处在于子类可以根据需要,定义特定于自己的行为。 也就是说子类能够根据需要实现父类的方法。
重写方法不能抛出新的检查异常或者比被重写方法申明更加宽泛的异常。例如: 父类的一个方法申明了一个检查异常 IOException,但是在重写这个方法的时候不能抛出 Exception 异常,因为 Exception 是 IOException 的父类,只能抛出 IOException 的子类异常。
在面向对象原则里,重写意味着可以重写任何现有方法。

重载(overloading) 是在一个类里面,方法名字相同,而参数不同。返回类型可以相同也可以不同。
每个重载的方法(或者构造函数)都必须有一个独一无二的参数类型列表。
只能重载构造函数
overwrite 重写继承到的那个方法的代码,原方法被放弃
重写与重载之间的区别
区别点| 重载方法| 重写方法
参数列表| 必须修改 |一定不能修改
返回类型 |可以修改| 一定不能修改
异常 |可以修改| 可以减少或删除,一定不能抛出新的或者更广的异常
访问| 可以修改| 一定不能做更严格的限制(可以降低限制)

总结:方法的重写(Overriding)和重载(Overloading)是java多态性的不同表现,重写是父类与子类之间多态性的一种表现,重载是一类中多态性的一种表现。

重写与重载

1.5.2 等价与类似关系

等价我们完全能够将衍生类的一
个对象换成基础类的一个对象!可将其想象成一种“纯替换”。在某种意义上,这是进行继承的一种理想方
式。此时,我们通常认为基础类和衍生类之间存在一种“等价”关系

类似新类型拥有旧类型的接口,但也包含了其他函数,所以不能说它们是完全等价的

1.6 多形对象的互换使用

继承最终会以创建一系列类收场,所有类都建立在统一的接口基础上

1.6.1 动态绑定

“多态”(Polymorphic)也叫“动态绑定”(Dynamic Binding)同时也叫“迟绑定”(Late Binding)。

动态绑定是指“在执行期间(而非编译期间)判断所引用对象的实际类型,根据其实际类型调用其相应的方法。”
多态必备的三个条件1、有继承2、有重写3、父类引用指向子类对象
多态

1.6.2 抽象的基础类和接口

abstarct class

interface(接口)关键字将抽象类的概念更延伸了一步,它完全禁止了所有的函数定义。“接口”是一种相当有效和常用的工具。另外如果自己愿意,亦可将多个接口都合并到一起

abstarct class在Java语言中体现了一种继承关系,要想使得继承关系合理,父类和派生类之间必须存在”is a”关系,即父类和派生类在概念本质上应该是相同的。对于interface 来说则不然,并不要求interface的实现者和interface定义在概念本质上是一致的,仅仅是实现了interface定义的契约而已。

1.7 对象的创建和存在时间

java对象需要的数据位于哪儿,如何控制对象的“存在时间”呢?

比如 String bb= new String ();new出来的对象放在堆内存中了,bb这个是局部变量放在栈内存

new出来的都放在heap 堆里

stack 是存放局部变量的

data segment 是存放静态变量和字符串常量的

code segment 是存放代码的

  1. 栈(stack)与堆(heap)都是Java用来在Ram中存放数据的地方。与C++不同,Java自动管理栈和堆,程序员不能直接地设置栈或堆。
  2. 栈的优势是,存取速度比堆要快,仅次于直接位于CPU中的寄存器。但缺点是,存在栈中的数据大小与生存期必须是确定的,缺乏灵活性。另外,栈数据可以共 享,详见第3点。堆的优势是可以动态地分配内存大小,生存期也不必事先告诉编译器,Java的垃圾收集器会自动收走这些不再使用的数据。但缺点是,由于要 在运行时动态分配内存,存取速度较慢。

基本类型(primitive types),共有8种,即int, short, long, byte, float, double, boolean,char出于追求速度的原因,就存在于栈中.

栈有一个很重要的特殊性,就是存在栈中的数据可以共享

如Integer, String, Double等将相应的基本数据类型包装起来的类。这些类数据全部存在于中,Java用new()语句来显示地告诉编译器,在运行时才根据需要动态创建,因此比较灵活,但缺点是要占用更多的时间。

基本数据类型,存储在栈中,“abc”存储在栈中,只要是使用new一个对象,则存储在堆中,而且在栈中,在新建一个新的变量或对象时,会首 先看一下是否已经存在,存在则不创建新的。那在栈中的数据是否不涉及垃圾回收呢。那这部分内容,如何处理呢,还有就是,对象的作用域,程序存储在哪儿呢
对于栈中的数据来说,当超过变量的作用域的时候,会消失。哦,明白了,那种栈的形式

实例化后,也就是用了new运算符号,在堆内存建立一个对象,对象是一块区域,里面包含着每个属性

只要是用new()来新建对象的,都会在堆中创建,而且其字符串是单独存值的,即使与栈中的数据相同,也不会与栈中的数据共享。

当比较包装类里面的数值是否相等时,用equals()方法;当测试两个包装类的引用是否指向同一个对象时,用==

java对象需要的数据位于哪儿

一个内存池中动态创建对象,该内存池亦叫“堆”或者“内存堆”

若需一个新对象,只需在需要它的时候在内存堆里简单地创建
它即可。由于存储空间的管理是运行期间动态进行的,所以在内存堆里分配存储空间的时间比在堆栈里创建
的时间长得多(在堆栈里创建存储空间一般只需要一个简单的指令,将堆栈指针向下或向下移动即可)。由于动态创建方法使对象本来就倾向于复杂,所以查找存储空间以及释放它所需的额外开销不会为对象的创建造成明显的影响。

程序员可用两种方法来破坏一个对象:用程序化的方式决定何时破坏对象,或者利用由运行环境提供的一种“垃圾收集器”特性,自动寻找那些不再使用的对象,并将其清除。当然,垃圾收集器显得方便得多,但要求所有应用程序都必须容忍垃圾收集器的存在,并能默许随垃圾收集带来的额外开销。

1.7.1 集合与迭代器

如果事先不知道需要多少个对象,或者它们的持续时间有多长,那么也不知道如何保存那些对象。既然如此,怎样才能知道那些对象要求多少空间呢?事先上根本无法提前知道,除非进入运行期。

在需要的时候,集合会自动扩充自己,以便适应我们在其中置入的任何东西。所以我们事先不必知道要在一个集合里容下多少东西。只需创建一个集合,以后的工作让它自己负责好了。

根据自己的需要选择适当的类型。其中包括集合、队列、散列表、树、堆栈等等

。如果想对集合中的一系列元素进行操纵或比较,而不是仅仅面向一个,这时又该怎么办呢?

办法就是使用一个“迭代器”(Iterator),它属于一种对象,负责选择集合内的元素,并把它们提供给迭代器的用户。

有两方面的原因促使我们需要对集合作出选择。

首先,集合提供了不同的接口类型以及外部行为。堆栈的接口与行为与队列的不同,而队列的接口与行为又与一个集(Set)列表的不同。利用这个特征,我们解决问题时便有更大的灵活性。

其次,不同的集合在进行特定操作时往往有不同的效率。最好的例子便是矢量(Vector)列表(List)的区别。它们都属于简单的序列,拥有完全一致的接口和外部行为。但在执行一些特定的任务时,需要的开销
却是完全不同的。对矢量内的元素进行的随机访问(存取)是一种常时操作;无论我们选择的选择是什么,需要的时间量都是相同的。但在一个链接列表中,若想到处移动,并随机挑选一个元素,就需付出“惨重”
的代价。而且假设某个元素位于列表较远的地方,找到它所需的时间也会长许多。但在另一方面,如果想在序列中部插入一个元素,用列表就比用矢量划算得多。

1.7.2 单根结构

java所有对象是继承于Object

单根结构中的所有对象(比如所有 Java 对象)都可以保证拥有一些特定的功能。

利用单根结构,我们可以更方便地实现一个垃圾收集器

1.7.3 集合库与方便使用集合

参数化类型 Java generic泛型 声明的类型参数在使用时用具体的类型来替换

1.7.4 清除时的困境:由谁负责清除?

如何才能知道什么时间删除对象呢?用完对象后,系统的其他某些部分可能仍然要发挥作用。同样的问题也会在其他大量场合出现

在 Java 中,垃圾收集器在设计时已考虑到了内存的释放问题(尽管这并不包括清除一个对象涉及到的其他方面)。垃圾收集器“知道”一个对象在什么时候不再使用,然后会自动释放那个对象占据的内存空间。采用这种方式,另外加上所有对象都从单个根类Object 继承的事实,而且由于我们只能在内存堆中以一种方式创建对象,所以Java 的编程要比 C++的编程简单得多。

垃圾收集器对效率及灵活性的影响

1.8 违例控制:解决错误

“违例控制”将错误控制方案内置到程序设计语言中,有时甚至内建到操作系统内。这里的“违例”(Exception)属于一个特殊的对象,它会从产生错误的地方“扔”或“掷”出来。随后,这个违例会被设计用于控制特定类型错误的“违例控制器”捕获。

1.9 多线程

Java 也提供了有限的资源锁定方案。它能锁定任何
对象占用的内存(内存实际是多种共享资源的一种),所以同一时间只能有一个线程使用特定的内存空间。为达到这个目的,需要使用synchronized 关键字。其他类型的资源必须由程序员明确锁定,这通常要求程序员创建一个对象,用它代表一把锁,所有线程在访问那个资源时都必须检查这把锁。

volatile

应用场景:检查一个应用执行关闭或中断状态。因为此关键字拒绝了虚拟对一个变量多次赋值时的优化从而保证了虚拟机一定会检查被该关键字修饰的变量的状态变化。

CountDownLatch

应用场景:控制在一组线程操作执行完成之前当前线程一直处于等待。例如在主线程中执行await()方法阻塞主线程,在工作线程执行完逻辑后执行countDown()方法。

synchronized:java语言的关键字,当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码。

一、当两个并发线程访问同一个对象object中的这个synchronized(this)同步代码块时,一个时间内只能有一个线程得到执行。另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块。

二、然而,当一个线程访问object的一个synchronized(this)同步代码块时,另一个线程仍然可以访问该object中的非synchronized(this)同步代码块。

三、尤其关键的是,当一个线程访问object的一个synchronized(this)同步代码块时,其他线程对object中所有其它synchronized(this)同步代码块的访问将被阻塞。

四、第三个例子同样适用其它同步代码块。也就是说,当一个线程访问object的一个synchronized(this)同步代码块时,它就获得了这个object的对象锁。结果,其它线程对该object对象所有同步代码部分的访问都被暂时阻塞。

五、以上规则对其它对象锁同样适用.

1.10 序列化对象有限永久性保存

serilizable

i71面试总结

1.Activity跳转生命周期

1.1 OtherActivity完全覆盖MainActivity
可以发现在第二个activity的OnResume之后,第一个activity才OnStop,但是在第二个activity的Oncreate之前就执行了OnPause。也就是说只有在第二个activity完全起来之后才调用第一个activity的OnStop
新启动

返回

1.2 PauseActivity不完全覆盖MainActivity
这里只调了第一个activity的OnPause,而没有调用OnStop
新启动
返回
http://blog.csdn.net/yuxmdef1/article/details/18036787
http://blog.csdn.net/android_tutor/article/details/5772285

2.内部类

内部类的实例化
如果内部类未声明为static,在实例化时首先需要new一个外部类的对象。并通过p.new Inner()的方式new 内部类,表明这个内部类指向该外部类。内部类的class类型为:Parent.Inner,而不是p.Inner,这个需要和new的方式区分开。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Test {
public static void main(String[] args) {
Parent p = new Parent();
Parent.Inner i = p.new Inner();
i.print();
}
}
class Parent {
class Inner {
public void print() {
System.out.println("xxx");
}
}
}

静态内部类的实例化
静态内部类与普通内部类的区别在于,静态内部类的对象是不指向与某个具体的外部类对象,所以在创建对象时不需要创建外部类对象。并且在new的时候是通过 new Parent.Inner()方式,而不是Parent.new Inner()。不要和内部类的实例化搞混了。class的声明和内部类是一样的,都是Parent.Inner

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Test {
public static void main(String[] args) {
Parent.Inner i = new Parent.Inner();
i.print();
}
}
class Parent {
staticclass Inner {
public void print() {
System.out.println("xxx");
}
}
}

http://blog.csdn.net/playstudy/article/details/31777389

3.URLCache

目前很多商业应用都会涉及到从网络上读取图片数据的问题,为了节约用户流量,应用一般会将图片缓存起来。图片缓存一般分为内存缓存和外存缓存。内存缓存运用java的缓存机制,在程序完全退出后,缓存所在的内存空间可能被其它应用程序占用从而丢失。外存缓存一般放在程序特有的访问空间或者sd卡中,在sd卡中存放的资源为公有资源,其它程序也可以访问,且对用户来讲没有一个强制清除缓存的规范机制。综合以上,本文采用将缓存图片放置在程序的特有空间中, 其它应用程序无法访问,且用户可以在应用程序管理中的”清除数据”选项中清除缓存。
本文提供三种缓存策略:(1)LRU算法,固定缓存图片数量(max_num),当图片数量超出max_num时,将缓存中最近用的最少的图片删除。(2)FTU算法,固定每张图片的缓存时限,以最后一次使用算起,超过时限后删除。(3)FMU算法,在存储器中固定一定大小的存储空间,超过固定空间后将缓存中占用最大尺寸的图片删除。使用时只需要向方法体中传递图片的URL即可。
http://www.android100.org/html/201602/14/215263.html

4.IntentService

在Android开发中,我们或许会碰到这么一种业务需求,一项任务分成几个子任务,子任务按顺序先后执行,子任务全部执行完后,这项任务才算成功。那么,利用几个子线程顺序执行是可以达到这个目的的,但是每个线程必须去手动控制,而且得在一个子线程执行完后,再开启另一个子线程。或者,全部放到一个线程中让其顺序执行。这样都可以做到,但是,如果这是一个后台任务,就得放到Service里面,由于Service和Activity是同级的,所以,要执行耗时任务,就得在Service里面开子线程来执行。那么,有没有一种简单的方法来处理这个过程呢,答案就是IntentService。

什么是IntentService,首先看看官方的解释:

IntentService is a base class forServices that handle asynchronous requests (expressed asIntents) on demand. Clients send requests throughstartService(Intent) calls; the service is started as needed, handles each Intent in turn using a worker thread, and stops itself when it runs out of work

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

All requests are handled on a single worker thread – they may take as long as necessary (and will not block the application’s main loop), but only one request will be processed at a time.
大致意思是:所有请求都在一个单线程中,不会阻塞应用程序的主线程(UI Thread),同一时间只处理一个请求。
那么,用IntentService有什么好处呢?首先,我们省去了在Service中手动开线程的麻烦,第二,当操作完成时,我们不用手动停止Service,第三,it’s so easy to use!

ok,接下来让我们来看看如何使用,我写了一个Demo来模拟两个耗时操作,Operation1与Operation2,先执行1,2必须等1执行完才能执行:
http://laokaddk.blog.51cto.com/368606/1340540/
http://www.cnblogs.com/zhangs1986/p/3602154.html

5.AsyncTask

在实际应用中经常会遇到比较耗时任务的处理,比如网络连接,数据库操作等情况时,如果这些操作都是放在主线程(UI线程)中,则会造成UI的假死现象,Android中可以使用AsyncTask和Handler两种异步方式来解决这种问题。
AsyncTask:
android提供的轻量级的异步类,可以直接继承AsyncTask,在类中实现异步操作,并提供接口反馈当前异步执行的程度(可以通过接口实现UI进度更新),最后反馈执行的结果给UI主线程.

使用的优点:
简单,快捷,过程可控
使用的缺点:
在使用多个异步操作和并需要进行Ui变更时,就变得复杂起来.
在使用AsyncTask时处理类需要继承AsyncTask,提供三个泛型参数,并且重载AsyncTask的四个方法(至少重载一个)。
在Android 4.1版本之前,AsyncTask类必须在主线程中加载,这意味着对AsyncTask类的第一次访问必须发生在主线程中;在Android 4.1以及以上版本则不存在这一限制,因为ActivityThread(代表了主线程)的main方法中会自动加载AsyncTask
AsyncTask对象必须在主线程中创建
AsyncTask对象的execute方法必须在主线程中调用
一个AsyncTask对象只能调用一次execute方法

三个泛型参数:
1.Param 任务执行器需要的数据类型
2.Progress 后台计算中使用的进度单位数据类型
3.Result 后台计算返回结果的数据类型
在设置参数时通常是这样的:String… params,这表示方法可以有0个或多个此类型参数;有时参数可以设置为不使用,用Void…即可。
四个方法:
1.onPreExecute() 执行预处理,它运行于UI线程,可以为后台任务做一些准备工作,比如绘制一个进度条控件。
2.doInBackground(Params…) 后台进程执行的具体计算在这里实现,doInBackground(Params…)是AsyncTask的关键,此方法必须重载。在这个方法内可以使用publishProgress(Progress…)改变当前的进度值。
3.onProgressUpdate(Progress…) 运行于UI线程。如果在doInBackground(Params…) 中使用了publishProgress(Progress…),就会触发这个方法。在这里可以对进度条控件根据进度值做出具体的响应。
4.onPostExecute(Result) 运行于UI线程,可以对后台任务的结果做出处理,结果就是doInBackground(Params…)的返回值。此方法也要经常重载,如果Result为null表明后台任务没有完成(被取消或者出现异常)。

Handler:
Handler可以分发Message对象和Runnable对象到主线程中, 每个Handler实例,都会绑定到创建他的线程中(一般是位于主线程中)
两个作用:
安排消息或Runnable 在某个主线程中某个地方执行
安排一个动作在不同的线程中执行
Handler中分发消息的方法:
post(Runnable)
postAtTime(Runnable,long)
postDelayed(Runnable,long)
sendEmptyMessage(int)
sendMessage(Message)
sendMessageAtTime(Message,long)
sendMessageDelayed(Message,long)
以上post开头的方法在主线程中调用。 以上send开头的方法在其它线程中调用。
handler.post(thread)似乎实现了新启线程的作用,不过通过执行我们发现,两个线程的ID相同!也就是说,实际上thread还是原来 的主线程,由此可见,handler.post()方法并未真正新建线程,只是在原线程上执行而已,我们并未实现异步机制。

http://blog.csdn.net/wuxinzaiyu/article/details/8954841
http://blog.csdn.net/zj510/article/details/51485120

Q:Android实现异步的几种方式?

1.使用Thread + Handler消息传递机制;

2.使用AsyncTask异步任务;

AsyncTask抽象出后台线程运行的五个状态,分别是:1、准备运行,2、正在后台运行,3、进度更新,4、完成后台任务,5、取消任务,对于这五个阶段,AsyncTask提供了五个回调函数:

1、准备运行:onPreExecute(),该回调函数在任务被执行之后立即由UI线程调用。这个步骤通常用来建立任务,在用户接口(UI)上显示进度条。

2、正在后台运行:doInBackground(Params…),该回调函数由后台线程在onPreExecute()方法执行结束后立即调用。通常在这里执行耗时的后台计算。计算的结果必须由该函数返回,并被传递到onPostExecute()中。在该函数内也可以使用publishProgress(Progress…)来发布一个或多个进度单位(unitsof progress)。这些值将会在onProgressUpdate(Progress…)中被发布到UI线程。

  1. 进度更新:onProgressUpdate(Progress…),该函数由UI线程在publishProgress(Progress…)方法调用完后被调用。一般用于动态地显示一个进度条。

  2. 完成后台任务:onPostExecute(Result),当后台计算结束后调用。后台计算的结果会被作为参数传递给这一函数。

5、取消任务:onCancelled (),在调用AsyncTask的cancel()方法时调用

3.使用runOnUiThread(action)方法;

1
2
3
4
5
6
7
8
9
10
11
12
13
runOnUiThread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
try {
//延迟两秒更新
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
tv.setText("更新后的TextView");
}
});

4.使用Handler的post(Runnabel r)方法;

1
2
3
4
5
6
7
8
9
10
11
12
13
Handler handler = new Handler();
handler.post(new Runnable(){
@Override
public void run() {
try {
//延迟两秒更新
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
tv.setText("更新后的TextView");
}
});

5.RxJava

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Observable.just("")
.map(new Func1<String, Bitmap>() {
@Override
public Bitmap call(String s) {
//可以在这里执行耗时操作,比如下载网络图片,然后转化为Bitmap
return null;
}
}).subscribeOn(Schedulers.io())//把工作线程指定为了IO线程
.observeOn(AndroidSchedulers.mainThread())//把回调线程指定为了UI线程
.subscribe(new Action1<Bitmap>() {
@Override
public void call(Bitmap bitmap) {
//这里是在UI线程,这里显示了图片
mImageView.setImageBitmap(bitmap);
}
});

Q.java class默认的权限?

java中修饰类总共有四种关键字,不是只有public和defalult关键字。如下:
1、public:public表明该数据成员、成员函数是对所有用户开放的,所有用户都可以直接进行调用
2、private:private表示私有,私有的意思就是除了class自己之外,任何人都不可以直接使用,私有财产神圣不可侵犯嘛,即便是子女,朋友,都不可以使用。
3、protected:protected对于子女、朋友来说,就是public的,可以自由使用,没有任何限制,而对于其他的外部class,protected就变成private。
4、default:java的默认访问权限,当没有使用上面提到的任何访问限定词时,就使用它,这种权限通常被称为包访问权限,在这种权限下,类可以访问在同一个包中的其他类的成员,也即可以访问我们前面说的朋友,在包之外,这些成员如同指定了private。

java内部类

内部类就是在一个类的内部定义的类,内部类中不能定义静态成员;内部类可以直接访问外部类中的成员变量,内部类可以定义在外部类的方法外面,也可以定义在外部类的方法体中;在方法体外面定义的内部类的访问类型可以是public,protecte,默认的,private等4种类型,这就好像类中定义的成员变量有4种访问类型一样,它们决定这个内部类的定义对其他类是否可见;对于这种情况,我们也可以在外面创建内部类的实例对象,创建内部类的实例对象时,一定要先创建外部类的实例对象,然后用这个外部类的实例对象去创建内部类的实例对象,代码如下:

1
2
Outer outer = new Outer();
Outer.Inner1 inner1 = outer.new Innner1();

在方法内部定义的内部类前面不能有访问类型修饰符,就好像方法中定义的局部变量一样,但这种内部类的前面可以使用final或abstract修饰符。这种内部类对其他类是不可见的其他类无法引用这种内部类,但是这种内部类创建的实例对象可以传递给其他类访问。这种内部类必须是先定义,后使用,即内部类的定义代码必须出现在使用该类之前,这与方法中的局部变量必须先定义后使用的道理也是一样的。这种内部类可以访问方法体中的局部变量,但是,该局部变量前必须加final修饰符。

普通类只能定义成public和默认的这两种类型。在外面引用Static Nested Class类的名称为“外部类名.内部类名”。在外面不需要创建外部类的实例对象,就可以直接创建Static Nested Class,例如,假设Inner是定义在Outer类中的Static Nested Class,那么可以使用如下语句创建Inner类:
Outer.Inner inner = new Outer.Inner();

由于static Nested Class不依赖于外部类的实例对象,所以,static Nested Class能访问外部类的非static成员变量。当在外部类中访问Static Nested Class时,可以直接使用Static Nested Class的名字,而不需要加上外部类的名字了,在Static Nested Class中也可以直接引用外部类的static的成员变量,不需要加上外部类的名字。
在静态方法中定义的内部类也是Static Nested Class,这时候不能在类前面加static关键字,静态方法中的Static Nested Class与普通方法中的内部类的应用方式很相似,它除了可以直接访问外部类中的static的成员变量,还可以访问静态方法中的局部变量,但是,该局部变量前必须加final修饰符。

1、内部类的形式是怎样的?
⒈静态内部类
不可以访问外部类的普通成员变量,而只能访问外部类中的静态成员
⒉成员内部类
内部类可以引用它的包含类的成员吗,成员内部类不能含有static的变量和方法。因为成员内部类需要先创建了外部类,才能创建它自己的,
⒊局部内部类
⒋匿名内部类
可以继承其他类或实现其他接口

2、为什么要有“内部类”?
1、内部类提供了更好的封装。只能让外部类直接访问,不允许同一个包中的其他类直接访问。
2、内部类可以直接访问外部类的私有属性,内部类被当成其外部类成员。但外部类不能访问内部类的内部属性。
3、利用内部类可以方便实现哪些功能?
可以不受限制的访问外部类的域和方法。
4、内部类的实现机制?

5、内部类可以引用它的包含类(外部类)的成员吗?有没有什么限制?

可以,限制,如果是静态内部类,不能访问外部类的非静态成员。

内部类

内部类2

Picasso请求图片源码分析

Picasso

一般我们在使用Picasso加载图片时,会进行如下代码所示进行调用:

1
Picasso.with(this).load("http://i.imgur.com/DvpvklR.png").into(img);

然而这背后的都是如何处理的呢?下面我们就一步步来看看Picasso库是如何调用来显示一张图片到我们手机界面上的?

##(1)Picasso.with(this)方法———得到Picasso对象
我们先来看一下Picasso.with(this)方法的源代码:

1
2
3
4
5
6
7
8
9
10
public static Picasso with(Context context) {
if (singleton == null) {
synchronized (Picasso.class) {
if (singleton == null) {
singleton = new Builder(context).build();
}
}
}
return singleton;
}

从上面代码可以看出这里利用单例的设计模式来返回一个单例对象(Picasso),这里的关键点变成了 Builder(context).build()是如何构造Picasso对象的问题了?

1.1 Builder(context).build()———-返回Picasso实例

下面,我们接着来看一下build()方法的源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public Picasso build() {
Context context = this.context;
if (downloader == null) {
downloader = Utils.createDefaultDownloader(context); //创建默认的下载器
}
if (cache == null) {
cache = new LruCache(context);//初始化缓存
}
if (service == null) {
service = new PicassoExecutorService();
}
if (transformer == null) {
transformer = RequestTransformer.IDENTITY;
}
Stats stats = new Stats(cache);
Dispatcher dispatcher = new Dispatcher(context, service, HANDLER, downloader, cache, stats);
return new Picasso(context, dispatcher, cache, listener, transformer, requestHandlers, stats,
defaultBitmapConfig, indicatorsEnabled, loggingEnabled);
}

上面代码中首页根据Utils.createDefaultDownloader(context)方法来创建默认的图片下载器;然后创建LruCache()对象用来缓存图片,PicassoExecutorService()对象用来执行下载任务,RequestTransformer是用来设置图片变换的(例如:圆形,圆角显示图片等);Stats用来记录cache缓存的状态;Dispatcher用来进行相关事件的分发器(如:开始下载图片,图片下载完成等);在方法的最后,返回一个Picasso的实例对象来供下步的调用使用!因此可以看出经过Picasso.with(this)方法我们得到了一个Picasso的对象。
看完了Picasso.with(this)方法,下面我们来学习一下load(path)方法,看看在这个方法又做了什么有趣的事情?

##(2)load(path)方法———得到RequestCreator对象

  • 接下来我们一起来探究一下Picasso类load()方法的源码:
1
2
3
4
5
6
7
8
9
public RequestCreator load(String path) {
if (path == null) {
return new RequestCreator(this, null, 0);
}
if (path.trim().length() == 0) {
throw new IllegalArgumentException("Path must not be empty.");
}
return load(Uri.parse(path));
}

在这个方法,首先判断若传入的参数为空,则会创建一个RequestCreator对象,若参数不为空,则会调用load(Uri.parse(path))方法。

下面我们需要看一下load(Uri.parse(path))方法的源码,我们继续进入这个方法:

1
2
3
public RequestCreator load(Uri uri) {
return new RequestCreator(this, uri, 0);
}

在这个方法中会根据path解析的uri来创建一个RequestCreator对象。有的人会问?这个对象有什么作用了?
其实个这对象就是创建一个图片的下载请求。
看完了load(path)方法,下面我们接着来学习一下into()方法,看看在这个方法又做了什么有趣的事情?

##(3)into() ————-开始下载图片及显示图片
由于(2)load(path)了RequestCreator对象,因此,我们需要进入 ### RequestCreator###类的into()方法来look一下,其源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
public void into(ImageView target, Callback callback) {
long started = System.nanoTime();
checkMain(); //主线程检查,不在主线程会抛异常
if (target == null) {
throw new IllegalArgumentException("Target must not be null.");
}
//如果设置过了图片
if (!data.hasImage()) {
//取消请求
picasso.cancelRequest(target);
//设置占位图片
if (setPlaceholder) {
setPlaceholder(target, getPlaceholderDrawable());
}
return;
}
//如果设置了fit()
if (deferred) {
if (data.hasSize()) {
throw new IllegalStateException("Fit cannot be used with resize.");
}
int width = target.getWidth();
int height = target.getHeight();
if (width == 0 || height == 0) {
if (setPlaceholder) {
setPlaceholder(target, getPlaceholderDrawable());
}
picasso.defer(target, new DeferredRequestCreator(this, target, callback));
return;
}
//重新设置宽高
data.resize(width, height);
}
//创建http请求
Request request = createRequest(started);
//创建请求对应的关键字
String requestKey = createKey(request);
//查看是否可从缓存中读取图片
if (shouldReadFromMemoryCache(memoryPolicy)) {
//根据请求key来从缓存中获取Bitmap对象
Bitmap bitmap = picasso.quickMemoryCacheCheck(requestKey);
if (bitmap != null) {
//取消网络请求
picasso.cancelRequest(target);
//设置从缓存中读取的Bitmap到Imageview控件上
setBitmap(target, picasso.context, bitmap, MEMORY, noFade, picasso.indicatorsEnabled);
if (picasso.loggingEnabled) {
log(OWNER_MAIN, VERB_COMPLETED, request.plainId(), "from " + MEMORY);
}
if (callback != null) {
callback.onSuccess();
}
return;
}
}
//设置占位图片
if (setPlaceholder) {
setPlaceholder(target, getPlaceholderDrawable());
}
Action action =
new ImageViewAction(picasso, target, request, memoryPolicy, networkPolicy, errorResId,
errorDrawable, requestKey, tag, callback, noFade);
picasso.enqueueAndSubmit(action);
}

由于这个方法过长,我就不细说了,我已经在源码中加入了注释,感兴趣的同还可以看一下!我们定位到方法的最后两句代码,这两句代码创建一个ImageViewAction对象并通过enqueueAndSubmit(action)方法,把图片的请求进入队列并进行提交,因此,我们需要进入Picasso的enqueueAndSubmit这个方法来了解一下,其源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
void enqueueAndSubmit(Action action) {
Object target = action.getTarget();
if (target != null && targetToAction.get(target) != action) {
// This will also check we are on the main thread.
cancelExistingRequest(target);
targetToAction.put(target, action);
}
submit(action); //调用下面的方法
}
void submit(Action action) {
dispatcher.dispatchSubmit(action);
}

上面第一个方法在最后调用了第二个方法,经过上面这两个方法的调用,最后进入分发器的分发提交方法,其Dispathcer中dispathchSubmit(action)方法源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
//发送handler消息调用下面的方法
void dispatchSubmit(Action action) {
handler.sendMessage(handler.obtainMessage(REQUEST_SUBMIT, action));
}
//这个方法又调用了下面的方法
void performSubmit(Action action) {
performSubmit(action, true);
}
void performSubmit(Action action, boolean dismissFailed) {
//查看是否需要暂停请求
if (pausedTags.contains(action.getTag())) {
pausedActions.put(action.getTarget(), action);
if (action.getPicasso().loggingEnabled) {
log(OWNER_DISPATCHER, VERB_PAUSED, action.request.logId(),
"because tag '" + action.getTag() + "' is paused");
}
return;
}
//查看是否在Bitmap的请求map是否有本次请求
BitmapHunter hunter = hunterMap.get(action.getKey());
if (hunter != null) {
hunter.attach(action);
return;
}
if (service.isShutdown()) {
if (action.getPicasso().loggingEnabled) {
log(OWNER_DISPATCHER, VERB_IGNORED, action.request.logId(), "because shut down");
}
return;
}
//创建BitmapHunter的请求Runnable,在线程中进行网络请求
hunter = forRequest(action.getPicasso(), this, cache, stats, action);
//把BitmapHunter放入线程池进行请求执行
hunter.future = service.submit(hunter);
hunterMap.put(action.getKey(), hunter);
if (dismissFailed) {
failedActions.remove(action.getTarget());
}
if (action.getPicasso().loggingEnabled) {
log(OWNER_DISPATCHER, VERB_ENQUEUED, action.request.logId());
}

在上面这几个方法中最后会调用到service.submit(hunter);进行请求,而service为PicassoExecutorService类的实例化对象,PicassoExecutorService为线程池执行服务,主要用来发进行线程的请求(BitmapHunter),而请求的所有逻辑都在其参数(hunter的run()方法中),因此,我们需要学习一下BitmapHunter类中run()方法的执行逻辑,其方法的源码如下所求:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
@Override public void run() {
try {
//更新线程的想着信息
updateThreadName(data);
if (picasso.loggingEnabled) {
log(OWNER_HUNTER, VERB_EXECUTING, getLogIdsForHunter(this));
}
//进行Bimtap的获取
result = hunt();
if (result == null) {
dispatcher.dispatchFailed(this);
} else {
dispatcher.dispatchComplete(this);
}
} catch (Downloader.ResponseException e) {
if (!e.localCacheOnly || e.responseCode != 504) {
exception = e;
}
dispatcher.dispatchFailed(this);
} catch (NetworkRequestHandler.ContentLengthException e) {
exception = e;
dispatcher.dispatchRetry(this);
} catch (IOException e) {
exception = e;
dispatcher.dispatchRetry(this);
} catch (OutOfMemoryError e) {
StringWriter writer = new StringWriter();
stats.createSnapshot().dump(new PrintWriter(writer));
exception = new RuntimeException(writer.toString(), e);
dispatcher.dispatchFailed(this);
} catch (Exception e) {
exception = e;
dispatcher.dispatchFailed(this);
} finally {
Thread.currentThread().setName(Utils.THREAD_IDLE_NAME);
}
}

在run()方法中通过hunt()来获取Bitmap对象,hunt()方法的源码如下所求:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
Bitmap hunt() throws IOException {
Bitmap bitmap = null;
//从缓存中获取Bitmap
if (shouldReadFromMemoryCache(memoryPolicy)) {
bitmap = cache.get(key);
if (bitmap != null) {
stats.dispatchCacheHit();
loadedFrom = MEMORY;
if (picasso.loggingEnabled) {
log(OWNER_HUNTER, VERB_DECODED, data.logId(), "from cache");
}
return bitmap;
}
}
//从网络上获取Bitmap
data.networkPolicy = retryCount == 0 ? NetworkPolicy.OFFLINE.index : networkPolicy;
RequestHandler.Result result = requestHandler.load(data, networkPolicy);
if (result != null) {
loadedFrom = result.getLoadedFrom();
exifRotation = result.getExifOrientation();
bitmap = result.getBitmap();
// If there was no Bitmap then we need to decode it from the stream.
if (bitmap == null) {
InputStream is = result.getStream();
try {
bitmap = decodeStream(is, data);
} finally {
Utils.closeQuietly(is);
}
}
}
if (bitmap != null) {
if (picasso.loggingEnabled) {
log(OWNER_HUNTER, VERB_DECODED, data.logId());
}
stats.dispatchBitmapDecoded(bitmap);
if (data.needsTransformation() || exifRotation != 0) {
synchronized (DECODE_LOCK) {
if (data.needsMatrixTransform() || exifRotation != 0) {
bitmap = transformResult(data, bitmap, exifRotation);
if (picasso.loggingEnabled) {
log(OWNER_HUNTER, VERB_TRANSFORMED, data.logId());
}
}
if (data.hasCustomTransformations()) {
bitmap = applyCustomTransformations(data.transformations, bitmap);
if (picasso.loggingEnabled) {
log(OWNER_HUNTER, VERB_TRANSFORMED, data.logId(), "from custom transformations");
}
}
}
if (bitmap != null) {
stats.dispatchBitmapTransformed(bitmap);
}
}
}
return bitmap;
}

在上面的run()中当获得Bitmap result后,通过分发器调用dispatcher.dispatchComplete(this)方法通过Handler来发送HUNTER_COMPLETE消息执行 performComplete(hunter) 调用batch(hunter)方法来发送 HUNTER_DELAY_NEXT_BATCH 消息并执行dispatcher.performBatchComplete()方法来送 HUNTER_BATCH_COMPLETE 消息到Picasso对象主线程的HANDLER中,在Handler中收到消息的处理逻辑如下:

1
2
3
4
5
6
7
@SuppressWarnings("unchecked")
List<BitmapHunter> batch = (List<BitmapHunter>) msg.obj;
//noinspection ForLoopReplaceableByForEach
for (int i = 0, n = batch.size(); i < n; i++) {
BitmapHunter hunter = batch.get(i);
hunter.picasso.complete(hunter);
}

然后调用picasso.complete(hunter)方法,源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
void complete(BitmapHunter hunter) {
Action single = hunter.getAction();
List<Action> joined = hunter.getActions();
boolean hasMultiple = joined != null && !joined.isEmpty();
boolean shouldDeliver = single != null || hasMultiple;
if (!shouldDeliver) {
return;
}
Uri uri = hunter.getData().uri;
Exception exception = hunter.getException();
Bitmap result = hunter.getResult();
LoadedFrom from = hunter.getLoadedFrom();
if (single != null) {
deliverAction(result, from, single);
}
if (hasMultiple) {
//noinspection ForLoopReplaceableByForEach
for (int i = 0, n = joined.size(); i < n; i++) {
Action join = joined.get(i);
deliverAction(result, from, join);
}
}
if (listener != null && exception != null) {
listener.onImageLoadFailed(this, uri, exception);
}
}

从上面代码中可以看到,该方法最后会执行deliverAction(result,from,join/single)来继续执行,方法deliverAction方法的源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
private void deliverAction(Bitmap result, LoadedFrom from, Action action) {
if (action.isCancelled()) {
return;
}
if (!action.willReplay()) {
targetToAction.remove(action.getTarget());
}
if (result != null) {
if (from == null) {
throw new AssertionError("LoadedFrom cannot be null.");
}
action.complete(result, from);
if (loggingEnabled) {
log(OWNER_MAIN, VERB_COMPLETED, action.request.logId(), "from " + from);
}
} else {
action.error();
if (loggingEnabled) {
log(OWNER_MAIN, VERB_ERRORED, action.request.logId());
}
}
}

然后会执行 action.complete(result, from);方法,其方法源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
@Override public void complete(Bitmap result, Picasso.LoadedFrom from) {
if (result == null) {
throw new AssertionError(
String.format("Attempted to complete action with no result!\n%s", this));
}
ImageView target = this.target.get();
if (target == null) {
return;
}
Context context = picasso.context;
boolean indicatorsEnabled = picasso.indicatorsEnabled;
PicassoDrawable.setBitmap(target, context, result, from, noFade, indicatorsEnabled);
if (callback != null) {
callback.onSuccess();
}
}
//上面方法通过PicassoDrawable.setBitmap来给imageview设置图片显示
/*----------------------------------------*/
static void setBitmap(ImageView target, Context context, Bitmap bitmap,
Picasso.LoadedFrom loadedFrom, boolean noFade, boolean debugging) {
Drawable placeholder = target.getDrawable();
if (placeholder instanceof AnimationDrawable) {
((AnimationDrawable) placeholder).stop();
}
PicassoDrawable drawable =
new PicassoDrawable(context, bitmap, placeholder, loadedFrom, noFade, debugging);
target.setImageDrawable(drawable);
}

通过调用PicassoDrawable.setBitmap()方法把从网络上请求下来的Bimtap设置到ImageView的控件上,这样一张图片就通过Picasso的加载机制把一个指定的path的图片显示出来了!

Picasso图片加载库的默认缓存大小,最大为50MB,最小为5MB

1
2
private static final int MIN_DISK_CACHE_SIZE = 5 * 1024 * 1024; // 5MB
private static final int MAX_DISK_CACHE_SIZE = 50 * 1024 * 1024; // 50MB

ShenLib内容简介

ShenLib简介

在日常开发中我们难免会遇到以下这几种情况:

1.Android前台代码开发完成了,可是后台还没有封装好可以调试的数据或接口

2.即使第1步中后台封装完成了可测试的数据,但是Android前台想完成边界测试,如一数据有很多文字时,前台显示是否合理,数据异常时界面如何显示等许许多多情况的测试,这时我们总想要求后台给配置一些特殊情景的数据,可这时后台开发人员正在开发其他任务,他们根本不想搭理你的,也不想费劲帮你搞那些异常数据,而对这种情况,我们Android前台开发人员是否可完成自己来搞呢?答案是肯定的!

3.在项目开发中我们一般会有一个内网开发环境,一个外网的环境,还有一个正式上线的环境,此外,对于Android开发人员来说最好有一个不依赖于后台的自测环境!面对不同的开发环境,需要配置不同的参数,如ip地址或域名!对于这个问题,我们Android开发人员需要对不同的环境进行不同的参数配置,来切换到不同的开发环境中,如何可以避免不同的环境下,我们需要每次手动修改配置参数以及如何可以快速的切换到不同环境的进行开发呢?

4.对于测试人员及开发人员开说,最好能在同一台手机上安装不同环境版本apk来方便我们验证不同环境下的问题,避免不停的进行客户端的安装替换!经常在我正在开发的得劲时,有人喊到“帮忙打一个什么环境的apk”,而这时你可能恰好不是他所要的环境,这时你可能就需要到程序中一个个的修改程序的配置参数!这是多么麻烦低效的事啊!

争对这几个问题,我用AndroidStudio进行相关的脚本及代码逻辑的编写来提出解决方案,可能我给出的方法并不一定是最好的方法,但其总归是一种解决方法,希望每位读者看完或多或少有点收获。

1.本地MockServer模仿后台进行数据封装

由于项目中网络框架采用的是Retrofit + Okhttp3所以增加了本地MockServer的拦截器来模仿后台返回数据,MockServerInterceptor拦截器的代码逻辑如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
package com.shenjianli.shenlib.net.interceptor;
import com.shenjianli.shenlib.net.mock.MockService;
import com.shenjianli.shenlib.net.mock.URLData;
import com.shenjianli.shenlib.net.mock.UrlConfigManager;
import com.shenjianli.shenlib.util.LogUtils;
import java.io.IOException;
import okhttp3.Interceptor;
import okhttp3.MediaType;
import okhttp3.Protocol;
import okhttp3.Response;
import okhttp3.ResponseBody;
/** * Created by shenjianli on 2016/7/8.
*/
public class MockServerInterceptor implements Interceptor{
@Override
public Response intercept(Chain chain) throws IOException {
Response response = null;
//拦截到网络请求时,根据UrlConfigManager中的变量MockServiceEnable的值来决定是否使用本地数据封装
//一般用在程序员自测或是边界性适配性测试时使用
if(UrlConfigManager.MockServiceEnable) { //如果MockServiceEnable为true,走下面流程
//取出请求的url地址
String key = chain.request().url().uri().getPath();
//根据配置文件来获取对应的数据
URLData urlData = UrlConfigManager.getUrlConfigManager().findURL(key);
if(null != urlData){
if(null != urlData.getMockClass()){
try {
//利用反射机制来创建本地的MockService类
MockService mockService = (MockService) Class.forName(
urlData.getMockClass()).newInstance();
//根据MockService对象来获取返回给前台的json字符串
String responseString = mockService.getJsonData();
//创建请求的返回内容
response = new Response.Builder()
.code(200)
.message(responseString)
.request(chain.request())
.protocol(Protocol.HTTP_1_0)
.body(ResponseBody.create(MediaType.parse("application/json"), responseString.getBytes()))
.addHeader("content-type", "application/json")
.build();
LogUtils.d("调用mock service " + urlData.getMockClass());
//响应请求
return response;
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
}
LogUtils.i("正常调用网络方法");
//如果MockServiceEnable为fase,继续根据地址发送远程网络请求
return chain.proceed(chain.request());
}
}

有了拦截器后,我们需要把拦截器加入到网络请求的请求链中,使其生效,其具体代码如下:

1
2
3
4
5
6
/**
* 设置MockService
*/
OkHttpClient.Builder builder = new OkHttpClient.Builder();
//把MockServer拦截器加入到网络请求
builder.addInterceptor(new MockServerInterceptor());

实例

1.定义请求的接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.shenjianli.lib.test;
import retrofit2.Call;
import retrofit2.http.GET;
import retrofit2.http.Query;
/**
* Created by shenjianlis on 2016/8/31.
* 测试网络请求的接口
*/
public interface TestApi {
//请求的url地址
@GET("shenjianli/test")
//id为请求的参数,可以根据参数不同返回不同的返回结果
Call<Test> getTestData(@Query("id") String id);
}

在这个请求接口是,主要意思是向地址ServerUrl+shenjianli/test发送一个网络Get请求,传入的参数有id!

2.编写本地MockService

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package com.shenjianli.lib.test;
import com.google.gson.Gson;
import com.shenjianli.shenlib.net.mock.MockService;
import com.shenjianli.shenlib.util.LogUtils;
/**
* Created by shenjianli on 2016/7/8.
* 测试使用的本地MockService用来返回请求的json字符串
*/
public class TestMockService extends MockService {
@Override
public String getJsonData() {
//创建测试数据对象
TestData testData = new TestData();
//进行想着的赋值
testData.setCity("taiyuan");
testData.setCityid("33333");
//测试数据传测试对象中
Test test = new Test();
test.setTestData(testData);
//使用Gson把对象转化为Json字符串
String resultStr = new Gson().toJson(test);
LogUtils.i("获得的json字符串为:" + resultStr);
//返回json字符串
return resultStr;
}
}

主要是继承MockService类并实现一个返回Json字符串的方法,其中生成的json字符串需要与第一步中类Test相对应,这样才能正确的向返回的Test对象赋值!

3.关联请求与MockService

1
2
3
4
5
6
7
8
9
10
11
12
<?xml version="1.0" encoding="UTF-8"?>
<url>
<Node
<!-- 表示请求地址 -->
Key="/shenjianli/test"
<!-- 若需要缓存,可以设置缓存时间 -->
Expires="300"
<!-- 请求地址对应的MockService类地址 -->
MockClass="com.shenjianli.lib.test.TestMockService"/>
</url>

在项目中App模块下的res/xml/url.xml文件中增加如上的结点,其中Key与第一步的请求地址相同,MockClass代表的是Key所对应的MockService对应的类所在的路径。

4.进行调用请求使用MockService

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
package com.shenjianli.lib.test;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.widget.Button;
import android.widget.TextView;
import com.shenjianli.lib.R;
import com.shenjianli.shenlib.net.NetClient;
import com.shenjianli.shenlib.net.RetrofitCallback;
import com.shenjianli.shenlib.util.LogUtils;
import butterknife.Bind;
import butterknife.ButterKnife;
import butterknife.OnClick;
import retrofit2.Call;
public class TestActivity extends AppCompatActivity {
@Bind(R.id.city_tv)
TextView mCityTv;
@Bind(R.id.get_btn)
Button mGetBtn;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test);
ButterKnife.bind(this);
}
@OnClick(R.id.get_btn)
public void onClick() {
//创建网络请求结口
TestApi api = NetClient.retrofit().create(TestApi.class);
//根据网络请求结口获取数据
Call<Test> data = api.getTestData("001");
//启动网络请求去请求数据
data.enqueue(new RetrofitCallback<Test>() {
@Override
public void onSuccess(Test data) {
//成功时返回测试数据
if(null != data){
mCityTv.setText(data.getTestData().getCity());
}
}
@Override
public void onFail(String errorMsg) {
//失败时返回错误信息
LogUtils.i(" 错误信息 " + errorMsg);
}
});
}
}

grade introduce 01

安装Gradle

安装Gradle比较容易,主要通过以下步骤来安装Gradle:

1.访问http://gradle.org/downloads.html或是http://services.gradle.org/distributions来下载发布的版本,一般选择比当前最新版本低一个的版本

2.解压下载的文件到指定的目录

3.增加环境变量GRADLE_HOME并指向第2步中的安装目录(这个不是必须的操作,但是这步的配置会使接下来使用Gradle更加方便)

4.在Path变量中加入%GRADLE_HOME%/bin

MacOS 安装

由于Gradle是使用使用MacOS安装包管理中心进行维护的,因此可以通过使用brew在终端窗口来安装Gradle:

1
sudo brew install gradle

Windows安装

在Windows下安装Gradle可以通过以下步骤:

1.下载Gradle的zip文件并解压到安装目录下,假设我们解压zip文件到以c:\gradle-1.0

2.右击我的电脑,选择属性

3.在系统控件面板中选择高级选项

4.在高级选项中点击环境变量


5.在环境变量中点击系统变量下的新建按钮来新建环境变量GRADLE_HOME并赋值为以c:\gradle-1.0(这上就是在第1步中解压的目录)

6.在系统变量中找到Path后,点击编辑按钮在path的最后面加入 ;%GRADLE_HOME%/bin

git ssh key 生成与使用

Git是分布式的代码管理工具,远程的代码管理是基于SSH的,所以要使用远程的Git则需要SSH的配置。
github的SSH配置如下:

一 、
设置Git的user name和email:
$ git config –global user.name “shenjianli”

$ git config –global user.email “cqtddt@163.com”

二、生成SSH密钥过程:
1.查看是否已经有了ssh密钥:cd ~/.ssh
如果没有密钥则不会有此文件夹,有则备份删除
2.生存密钥:$ ssh-keygen -t rsa -C “cqtddt@163.com”
按3个回车,密码为空。

Your identification has been saved in /home/tekkub/.ssh/id_rsa.
Your public key has been saved in /home/tekkub/.ssh/id_rsa.pub.
The key fingerprint is:
………………

最后得到了两个文件:id_rsa和id_rsa.pub

3.添加密钥到ssh:ssh-add id_rsa.pub

需要之前输入密码。

4.在github上添加ssh密钥,这要添加的是“id_rsa.pub”里面的公钥。
打开https://github.com/ ,登陆shenjianli,然后添加ssh。

5.测试:ssh git@github.com

The authenticity of host ‘github.com (207.97.227.239)’ can’t be established.
RSA key fingerprint is 16:27:ac:a5:76:28:2d:36:63:1b:56:4d:eb:df:a6:48.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added ‘github.com,207.97.227.239′ (RSA) to the list of known hosts.
ERROR: Hi tekkub! You’ve successfully authenticated, but GitHub does not provide shell access
Connection to github.com closed.

三、 开始使用github
1.获取源码:
$ git clone git@github.com:shenjianli/FrescoTest.git

2.这样你的机器上就有一个repo了。
3.git于svn所不同的是git是分布式的,没有服务器概念。所有的人的机器上都有一个repo,每次提交都是给自己机器的repo
仓库初始化:
git init

生成快照并存入项目索引:
git add

文件,还有git rm,git mv等等…
项目索引提交:
git commit

4.协作编程:
将本地repo于远程的origin的repo合并,
推送本地更新到远程:
git push origin master

更新远程更新到本地:
git pull origin master

补充:
添加远端repo:
$ git remote add upstream git@github.com:shenjianli/FrescoTest.git

重命名远端repo:
$ git@github.com:shenjianli/FrescoTest.git为“upstream”

data of test url and json

JSON地址

http://m.mall.icbc.com.cn/mobile/indexSeckill.jhtml
http://m.mall.icbc.com.cn/mobile/indexSlide.jhtml
http://m.mall.icbc.com.cn/mobile/indexPlatformNew.jhtml?flag=1
http://m.mall.icbc.com.cn/mobile/indexTheme.jhtml
http://m.mall.icbc.com.cn/mobile/indexIndustry.jhtml
http://m.mall.icbc.com.cn/mobile/indexChoicenessNew.jhtml?indexpage=1

http://api.rottentomatoes.com/api/public/v1.0/lists/movies/in_theaters.json?apikey=7waqfqbprs7pajbz28mqf6vz&page_limit=25
http://news-at.zhihu.com/api/4/start-image/1080*1776
var API_LATEST_URL = ‘http://news-at.zhihu.com/api/4/news/latest‘;
var API_HOME_URL = ‘http://news.at.zhihu.com/api/4/news/before/‘;
var API_THEME_URL = ‘http://news-at.zhihu.com/api/4/theme/‘;
var API_THEMES_URL = ‘http://news-at.zhihu.com/api/4/themes‘;
https://developer.github.com/v3/
https://api.github.com

图片地址

设计向 20+优秀的Icons图标合集

http://i.imgur.com/rFLNqWI.jpg“,
http://i.imgur.com/C9pBVt7.jpg“,
http://i.imgur.com/rT5vXE1.jpg“,
http://i.imgur.com/aIy5R2k.jpg“,
http://i.imgur.com/MoJs9pT.jpg“,
http://i.imgur.com/S963yEM.jpg“,
http://i.imgur.com/rLR2cyc.jpg“,
http://i.imgur.com/SEPdUIx.jpg“,
http://i.imgur.com/aC9OjaM.jpg“,
http://i.imgur.com/76Jfv9b.jpg“,
http://i.imgur.com/fUX7EIB.jpg“,
http://i.imgur.com/syELajx.jpg“,
http://i.imgur.com/COzBnru.jpg“,
http://i.imgur.com/Z3QjilA.jpg“,

http://img.my.csdn.net/uploads/201407/26/1406383299_1976.jpg“,
http://img.my.csdn.net/uploads/201407/26/1406383291_6518.jpg“,
http://img.my.csdn.net/uploads/201407/26/1406383291_8239.jpg“,
http://img.my.csdn.net/uploads/201407/26/1406383290_9329.jpg“,
http://img.my.csdn.net/uploads/201407/26/1406383290_1042.jpg“,
http://img.my.csdn.net/uploads/201407/26/1406383275_3977.jpg“,
http://img.my.csdn.net/uploads/201407/26/1406383265_8550.jpg“,
http://img.my.csdn.net/uploads/201407/26/1406383264_3954.jpg“,
http://img.my.csdn.net/uploads/201407/26/1406383264_4787.jpg“,
http://img.my.csdn.net/uploads/201407/26/1406383264_8243.jpg“,
http://img.my.csdn.net/uploads/201407/26/1406383248_3693.jpg“,

图片

https://images.unsplash.com/photo-1441742917377-57f78ee0e582?h=1024‘,
https://images.unsplash.com/photo-1441716844725-09cedc13a4e7?h=1024‘,
https://images.unsplash.com/photo-1441448770220-76743f9e6af6?h=1024‘,
https://images.unsplash.com/photo-1441260038675-7329ab4cc264?h=1024‘,
https://images.unsplash.com/photo-1441126270775-739547c8680c?h=1024‘,
https://images.unsplash.com/photo-1440964829947-ca3277bd37f8?h=1024‘,
https://images.unsplash.com/photo-1440847899694-90043f91c7f9?h=1024

京东客户端分析

统计

京东:(用时单位:秒)
经过多次打开京东客户端,并进行网络请求拦截后,发现京东首页的主要数据请求有:

1.首页数据请求(/client.action?/functionId=welcomeHome):
用时:1.902秒

2.首页搜索框json数据请求(/client.action?functionId=searchBoxWord)
用时:0.104秒

3.首页缓存Json请求(/client.action?functionId=getCacheTime)
用时:0.134秒

##购:(用时单位:秒)

##购首页主要数据请求有:

1.轮播图(/mobile/indexSlide.jhtml)
用时:0.971

2.快速入口(/mobile/indexPlatformNew.jhtml?flag=1)
用时:0.982

3.秒杀(/mobile/indexSeckill.jhtml)
用时:0.744

4.专题(/mobile/indexTheme.jhtml)
用时:1.021

5.行业精选(/mobile/indexIndustry.jhtml)
用时:1.025

6.热卖商品(/mobile/indexChoicenessNew.jhtml?indexpage=1)
用时:0.105

分析

京东的这些请求是在一打开客户端时,就开始并行发送首页的数据请求,大多数情况下,在进入首页时,数据都请求完成了,这样就可以直接使用数据进行界面初始化,而不需要等待网络数据请求,因此首页加载较快!

京东请求中最长用时1.902秒,请求下来的数据长度为
text/plain: 20,756
~headers~: 276

对于购,上面这些数据都是在进入首页界面后,才开始上面的数据请求,需要等到网络数据请求下来才能初始化界面,因此相比于京东加载会慢些。 购数据请求最长为1.025秒,请求下来的数据长度为
text/x-json: 912
~headers~: 557

结论

综合上述,可知
京东相对于##购有以下优点:
1.发送首页数据请求的时机在客户端一启动时,便开始数据请求并进行缓存,而##购则是进行首页后才开始网络数据请求
2.首页网络数据请求个数较少,这样可以节省发起网络请求时所需要的不可避免的耗时
3.京东最长用时的请求效率高于##购(京东:(20757+276)/1.903 ##购:(912+557)/1.025)

从客户端来看,##购首页加载比京东慢的主要原因是由上面第1点导致,为了提高首页的加载速度,可以仿京东的实现方面,在客户端一打开时就进行首页网络数据的请求
为首页的初始化进行数据的预加载,从而实现更快的响应速度!

问题及解决方案

1.问题:首页占位样式较难看
分析:界面样式及图片设计粗糙
解决方法:进行界面占位样式的设计及提供高质量的UI图片

2.问题:首页数据加载慢
分析:网络请求时机较迟没有进行预加载
解决方法:仿京东的实现方面,在客户端一打开时就进行首页网络数据的请求,为首页的初始化进行数据的预加载,从而实现更快的响应速度!

附网络截图:

1.京东缓存请求用时图:
京东缓存
2.京东首页数据请求用时图:
京东首页数据请求用时图
3.京东搜索框文字请求用时图:
京东搜索框文字请求用时图