0%

Java NIO

  • Channels通道(连接)
    • FileChannel
    • DatagramChannel
    • SocketChannel
    • ServerSocketChannel
    • Pipe.SinkChannel
    • Pipe.SourceChannel
  • Buffers缓冲区
    • ByteBuffer
    • CharBuffer
    • DoubleBuffer
    • FloatBuffer
    • LongBuffer
    • IntBuffer
    • ShortBuffer
    • MappedByteBuffer
  • Selectors
  • Pipe(管道)

FileChannel

FileChannel是一个连接到文件的通道。可以通过文件通道读写文件。

FileChannel无法设置为非阻塞模式,它总是运行在阻塞模式下。

在Java NIO中,如果两个通道中有一个是FileChannel,那你可以直接将数据从一个channel(译者注:channel中文常译作通道)传输到另外一个channel。

  • FileChannel的基本用法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    RandomAccessFile aFile = new RandomAccessFile("data/nio-data.txt", "rw");
    FileChannel inChannel = aFile.getChannel(); //获取FileChannel

    ByteBuffer buf = ByteBuffer.allocate(48);
    int bytesRead = inChannel.read(buf); //读取FileChannel中数据至ByteBuffer
    long pos = inChannel.position(); //获取FileChannel的当前位置
    inChannel.position(0); //设置FileChannel的当前位置,在此位置之后进行读或写
    buf.flip(); //ByteBuffer从写模式转到读模式
    inChannel.write(buf);//再将ByteBuffer中数据写入FileChannel
    long fileSize = inChannel.size(); //返回此通道的文件的当前大小
    inChannel.truncate(fileSize / 2); //截取文件
    inChannel.force(true); //强制数据写入磁盘,参数表示是否同时将文件元数据(权限信息等)写到磁盘上。
    inChannel.close(); //关闭
  • transferFrom()

    FileChannel的transferFrom()方法可以将数据从源通道传输到FileChannel中(译者注:这个方法在JDK文档中的解释为将字节从给定的可读取字节通道传输到此通道的文件中)。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    RandomAccessFile fromFile = new RandomAccessFile("fromFile.txt", "rw");
    FileChannel fromChannel = fromFile.getChannel();

    RandomAccessFile toFile = new RandomAccessFile("toFile.txt", "rw");
    FileChannel toChannel = toFile.getChannel();

    long position = 0;
    long count = fromChannel.size();
    toChannel.transferFrom(fromChannel, position, count);

    此外要注意,在SoketChannel的实现中,SocketChannel只会传输此刻准备好的数据(可能不足count字节)。因此,SocketChannel可能不会将请求的所有数据(count个字节)全部传输到FileChannel中。

  • transferTo()

    transferTo()方法将数据从FileChannel传输到其他的channel中。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    RandomAccessFile fromFile = new RandomAccessFile("fromFile.txt", "rw");
    FileChannel fromChannel = formFile.getChannel();

    RandomAccessFile toFile = new RandomAccessFile("toFile.txt", "rw");
    FileChannel toChannel = toFile.getChannel();

    long position = 0;
    long count= fromChannel.size();
    fromChannel.transferTo(position, count, toChannel);

SocketChannel

Java NIO中的SocketChannel是一个连接到TCP网络套接字的通道。可以通过以下2种方式创建SocketChannel:

  1. 打开一个SocketChannel并连接到互联网上的某台服务器。
  2. 一个新连接到达ServerSocketChannel时,会创建一个SocketChannel。
  • SocketChannel基本用法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    //通过调用系统级默认 SelectorProvider 对象的 openSocketChannel 方法来创建新的通道。 
    SocketChannel socketChannel = SocketChannel.open(); //打开套接字通道
    //如果通道处于非阻塞模式,则发起一个非阻塞连接操作。如果立即建立连接(使用本地连接时就是如此),则返回true。否则此方法返回 false,并且必须在以后通过调用 finishConnect 方法来完成该连接操作。
    boolean isConnectNow = socketChannel.connect(new InetSocketAddress("www.baidu.com"), 80); //连接到某个套接字
    if(!isConnectNow) {
    socketChannel.finishConnect();
    }
    ByteBuffer buf = ByteBuffer.allocate(48);
    int bytesRead = socketChannel.read(buf);//从SocketChannel读取数据到ByteBuffer
    buf.flip();//反转ByteBuffer,此时limit=position,postion=0,一般与compact()配合使用
    socketChannel.write(buf); //写入SocketChannel;
  • SocketChannel非阻塞模式

    非阻塞模式与选择器搭配会工作的更好,通过将一或多个SocketChannel注册到Selector,可以询问选择器哪个通道已经准备好了读取,写入等。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    socketChannel.configureBlocking(false);
    socketChannel.connect(new InetSocketAddress("http://baidu.com"), 80);
    while(!socketChannel.finishConnect()) {
    //waitting for connecting...
    }
    ByteBuffer buf = ByteBuffer.allocate(48);
    socketChannel.read(buf);

    while(buf.hasRemaing()) {
    socketChannel.write(buf);
    }

ServerSocketChannel

针对面向流的侦听套接字的可选择通道。

  • ServerSocketChannel基本用法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
    serverSocketChannel.socket().bind(new InetSocketAddress(9999));

    serverSocketChannel.configureBlocking(false); //非阻塞模式
    while(true) {
    SocketChannel socketChannel = serverSocketChannel.accept();
    //...
    if(socketChannel != null) {

    }
    }
    serverSocketChannel.close();

DatagramChannel

Java NIO中的DatagramChannel是一个能收发UDP包的通道。因为UDP是无连接的网络协议,所以不能像其它通道那样读取和写入。它发送和接收的是数据包。

  • DatagramChannel基本用法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    DatagramChannel channel = DatagramChannel.open();
    channel.socket().bind(new InetSocketAddress(9999));

    ByteBuffer buf = ByteBuffer.allocate(48);
    buf.clear();
    channel.receive(buf); //接收数据,如果buf大小不够,则会被丢弃

    buf.flip();
    int bytesSend = channel.send(buf, new InetSocketAddress("baidu.com", 80)); //发送数据
    //连接到特定的地址,让其只能从特定地址收发数据,减少安全检查开销
    channel.connect(new InetSocketAddress("baidu.com", 80));
    int bytesRead = channel.read(buf);
    int bytesWritten = channel.write(buf);
阅读全文 »

Java 多态

父类对象引用子类实例。子类继承父类后,同一个方法有不同的表现。

  • 方法重载实现静态多态性(编译时多态)
  • 方法重写实现的动态多态性(运行时多态)

Java 虚拟机

Java程序执行过程

  • Java Source (.java File)
  • Java Compiler(javac)
  • Java Byte Code(.class File)
  • Class Loader
  • Execution Engine
  • Runtime Data Areas
  • Java Virtual Machine

类加载器 ( **Class Loader ** )

  • Bootstrap Class Loader
  • Extension Class Loader
  • System Class Loader
  • User-Defined Class Loader

运行数据区域 ( Runtime Data Areas

  • PC Register
    每个线程有一个PC计数器。当线程启动时,PC计数器被创建,这个计数器存放当前正在被执行的字节码指令( JVM 指令 )的地址。

  • JVM Stack
    Java栈中存放着一系列的栈桢( Stack Frame ),JVM 只能进行压入( Push ) 和弹出 ( Pop ) 栈帧这两种操作。每当调用 一个方法时,JVM就往栈里压入一个栈帧,方法结束返回时弹出栈帧。如果方法执行时异常,可以调用printStackTrace 等方法来查看栈的情况。

    • Stack Frame
      • Local Variable Array 局部变量队列
      • Operand Stack 操作数栈
      • Reference to Constant Pool常量池引用
  • Native Method Stack
    当程序通过 JNI(Java Native Interface)调用本地方法(如 C 或者 C++ 代码)时,就根据本地方法的语言类型建立相应的栈。

  • Heap
    堆中存放的是程序创建的对象或者实例。这个区域对 JVM 的性能影响很大。垃圾回收机制处理的正是这一块内存区域。 所以,类加载器加载其实就是根据编译后的 Class 文件,将 java 字节码载入 JVM 内存,并完成对运行数据处的初始化工作,供执行引擎执行。

  • Method Area
    方法区域是一个 JVM 实例中的所有线程共享的,当启动一个 JVM 实例时,方法区域被创建。它用于存运行时常量池、有关域和方法的信息、静态变量、类和方法的字节码。不同的 JVM 实现方式在实现方法区域的时候会有所区别。Oracle 的 HotSpot 称之为永久区域(Permanent Area)或者永久代(Permanent Generation)。

  • Runtime Constant Pool

    这个区域存放类和接口的常量,除此之外,它还存放方法和域的所有引用。当一个方法或者域被引用的时候,JVM 就通过运行常量池中的这些引用来查找方法和域在内存中的的实际地址。

 

前三个属于每个线程独自拥有。后三者是整个JVM实例中所有线程共有的。

执行引擎 (Execution Engine )

类加载器将字节码载入内存之后,执行引擎以Java字节码指令为单元,读取Java字节码。通过解释器将字节码转化成平台相关的机器码,这个过程也可以由即时编译器 ( JIT Compiler ) 来完成。

Java内存管理

Java的内存管理就是对象的分配和释放问题。

  • 分配

    内存分配由程序完成,使用 new 关键词为对象申请内存空间(基本类型除外),所有的对象都在堆( Heap ) 中分配空间。

  • 释放

    对象的释放由垃圾回收机制( GC )决定和执行。

Java的内存区域分栈内存和堆内存

栈内存

在函数中定义的基本类型变量和对象的引用变量都在函数的栈内存中分配。在函数(代码块)中定义一个变量时, java 就在栈中为这个变量分配内存空间,当超过变量的作用域后,java 会自动释放掉为该变量所分配的内存空间。

堆内存

用来存放由 new 创建的对象和数组以及对象的实例变量。在堆中分配的内存由 java 虚拟机的自动垃圾回收器来管理。

  • 堆的优点是可以动态分配内存大小,生存期也不必事先告诉编译器,因为它是在运行时动态分配内存的。缺点是在运行时动态分配内存,存取速度较慢
  • 栈的优点是存取速度比堆要快,仅次于直接位于CPU中的寄存器。另外栈数据可以共享。缺点是存在栈中的数据大小与生存期必须是确定的,缺乏灵活性。

Java中数据在内存中的存储位置

  • 基本数据类型(8种,即int, short, long, byte, float, double, boolean, char)。保存在栈中,并且共享。所以

    1
    2
    3
    String s1 = "a";
    String s2 = "a";
    System.out.println(s1==s2);//true
  • 对象

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    public class Person {
    String name;
    int age;
    public Person(String name, int age) {
    this.name = name;
    this.age = age;
    }
    }

    Person p = new Person("joally", 24);
    //1.Person p声明一个对象Person,其引用命名为p,此时在栈内存中为引用变量p分配内存空间,此时p是空对象,等同于Person p = null;
    //2.p = new Person("joally", 24)在堆内存中为类的成员变量name和age分配内存,并将其初始化为各数据类型的默认值;接着进行显式初始化(类定义时的初始化值);最后调用构造方法,为成员变量赋值。返回堆内存中对象的引用(相当于首地地址)给引用变量p,以后就可以通过栈内存中的p来引用堆内存中的Person对象了。
  • 数组

    当定义一个数组时,在栈内存中创建一个数组引用,通过该引用(即数组名)来引用数组。

    1
    2
    int x[]; //在栈内存中创建数组引用x;
    x = new int[3];//在堆内存中分配3个保存int型数据的空间,并初始化为0,堆内存的首地址赋值给x,即通过栈内存中x来引用堆内存中的int数组。
  • 静态变量

    用static修饰的变量和方法,实际上是指定了这些变量和方法在内存中的“固定位置” - static stoage,可以理解为所有实例对象共有的内存空间。静态表示的的是内存的共享,就是它的每一个实例都指向同一个内存地址,它的引用(含间接引用)都指向同一个位置。

Java 程序的多个部分(方法,变量,对象)驻留在内存中以下两个位置:堆和栈。实例变量和对象驻留在堆上,局部变量驻留在栈上。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class Dog {
Collar c;
String name;
//1. main()方法位于栈上
public static void main(String[] args) {
//2. 在栈上创建引用变量d,但Dog对象尚未存在
Dog d;
//3. 在堆中创建新的Dog对象,并将其赋予d引用变量
d = new Dog();
//4. 将引用变量的一个副本传递给go()方法
d.go(d);
}
//5. 将go()方法置于栈上,并将dog参数作为局部变量
void go(Dog dog){
//6. 在堆上创建新的Collar对象,并将其赋予Dog的实例变量
c =new Collar();
}
//7.将setName()添加到栈上,并将dogName参数作为其局部变量
void setName(String dogName){
//8. name的实例对象也引用String对象
name=dogName;
}
//9. 程序执行完成后,setName()将会完成并从栈中清除,此时,局部变量dogName也会消失,尽管它所引用的String仍在堆上
}

Java泛型

泛型的主要目标是提高 Java 程序的类型安全。消除强制类型转换,提高代码可读性,减少出错机会。

Java序列化

定义

序列化,就是为了保存对象的状态,与之对应的是反序列化,就是把保存的对象状态再读出来。

序列化/反序列化,是Java提供一种专门用于保存/恢复对象状态的机制。

一般以下几种情况下,我们可能会用到序列化:

  • 想把内存中的对象状态保存到一个文件中或数据库中
  • 想用套接字在网络上传送对象
  • 想通过RMI ( Remote Method Invocation 远程连接调用 ) 传输对象时

java.io.Serializable

类通过实现Serializable接口,可实现自动序列化

  • transient瞬态,用于不序列化的属性
  • 静态变量,属于类的状态,不序列化
  • 如果想自定义序列化的内容,类中需要实现writeObject(java.io.ObjectOutputStream o)/readObject(java.io.ObjectInputStream o)方法,分别用于序列化和反序列化。自定义方法第一行可调用out.defaultWriteObject()/out.defaultReadObject()来调用jdk的默认序列化,然后再实现自己的序列化

java.io.Externalizable

如果一个类要完全负责自己的序列化,则实现 Externalizable 接口,而不是 Serializable 接口。

Externalizable接口定义包括两个方法writeExternal()readExternal()。需要注意的是:声明类实现Externalizable接口会有重大的安全风险。writeExternal()readExternal()方法声明为public,恶意类可以用这些方法读取和写入对象数据。如果对象包含敏感信息,则要格外小心。

  • writeExternal(ObjectOutput out)
  • readExernal(ObjectInput in)
  • readResolve/writeReplace可以用在单例模式时的序列化和反序列化,用于指定替代的对象

Java基本数据结构

  • boolean、char[2byte,16bit]、short[2byte,16bit]、int[4byte,32bit]、float[4byte,64bit]、long[8byte,64bit]、double[8byte,64bit]
  • 基本数据包装类,继承于java.lang.Number类,包括:java.lang.Bytejava.lang.Shortjava.lang.Integerjava.lang.Floatjava.lang.Longjava.lang.Double,以及java.lang.Booleanjava.lang.Character
  • Java字符,原始类型char,包装类java.lang.Character,jdk6基于Unicode Standard version4.0,jdk7基于Unicode Standard version6.0,jdk8基于Unicode verison6.2.0。
  • Unicode编码,使用“U+”然后紧接着一组十六进制的数字表示一个字符。从U+0000到U+FFFF的字符集被称作Basic Multilingual Plane(BMP),又称作零号平面,其中的所有字符,要用四个数字(即2个char,16bit,例如U+4AE0,共支持2^16-1=65535个字符);在零号平面以外的字符则需要使用五个或六个数字,范围为U+0000到U+10FFFF,即通常所说的Unicode标准量
  • java.lang.Math、java.util.Date、java.text.SimpleDateFormat、java.text.NumberFormat、java.text.MessageFormat、java.util.Calendar、java.util.GregorianCalendar
阅读全文 »

前面我写了Dagger2中2种注入的基本用法,这篇文章来讲下Dagger2比较高级一点的用法。本文也是个人学习思考所得,如有错误之处,还请大家多多评论交流。

@Component间的依赖

Dagger2中2种注入的基本用法中,我们按照大多数情况,按页面划分Component类,既一个页面对应一个Component类,这样便于管理。事实上除了在Activity的生存周期内的属性需要注入,很多情况下在Application生存周期内也有属性可以通过Dagger2来注入,这种属性一般是供整个应用来使用的。

因此如大部分教程一样,我们会定义一个全局的Component类来对应于应用的Application类

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
//MainApplicationModule.java
@Module
public class MainApplicationModule {
private MainApplication application;
MainApplicationModule(MainApplication application) {
this.application = application;
}

@Provides
public Context providerApplicationContext() {
return application;
}
}

//MainApplicationComponent.java
@Component(module = MainApplicationModule.class)
public interface MainApplicationComponent{
void inject(MainApplication mainApplication);
}

//MainApplication.java
public class MainApplication extends Application {
private MainApplicationComponent component;

@Override
public void onCreate() {
component = DaggerMainApplicationComponent.builder()
.MainApplicationModule(new MainApplicationModule(this))
.build();
//注入
component().inject(this);
}
}

在MainModule中我们提供了ApplicationContext类型的注入。前面说到Application类的提供的注入是供整个应用使用的,那么如果MainActivity需要使用到MainModule中提供的ApplicationContext类型怎么办?这里就要用到Component间的依赖了。

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
//MainActivityComopnent.java
//我们将MainActivityComponent修改如下,在@Component注解中加上dependencies参数
//此时MainActivityComponent即依赖于MainApplicationComponent
@Component(dependencies = MainApplicationComponent.class, module = MainActivityModule.class)
public interface MainActivityComponent{
void inject(MainActivity mainActivity);
}

//MainApplicationComponent.java
//MainApplicationComponent要提供自己的ApplicationContext给其他Component类使用,
//需要在自己的类中定义相应的返回类型
@Component(module = MainApplicationModule.class)
public interface MainApplicationComponent{
void inject(MainApplication mainApplication);
//增加Context返回类型的方法
Context getApplicationContext();
}

//MainApplication.java
public class MainApplication extends Application {
private MainApplicationComponent component;
//...
//增加一个方法来使MainActivity可以获得MainApplication中的MainApplicationComponent
public MainApplicationComponent getComponent() {
return component;
}
}

//MainActivity.java
public class MainActivity extends AppCompatActivity {
MainActivityComponent mainComponent;

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//依赖的Component在子Component初始化需要提供实例,因此在MainApplication中定义getComponent()方法
//可以查看Dagger2生成的代码,通过调用MainApplicationComponent中的getContext()方法来注入ApplicationContext
mainComponent = DaggerMainActivityComponent.builder()
.applicationComponent(((MainApplication) getApplication()).getComponent())
.build();
//注入
mainComponent.inject(this);
}
}

总结一下:需要依赖其他Component(一般称为子Component)需要在@Comopnent注解中定义dependencies参数,而被依赖的Component(一般称为父Component),需要将可为子Component提供的注入类型,定义成方法,以返回值来提供。

@SubComponent

@SubComponent从名字上就可以看出来其意思:子Component。上节讲的Component间的依赖也有子Component,那这2种子Component有什么区别呢。

其中最大的区别,就是前一种情况,父Component需要显式的增加相关返回值的方法;而@SubComponent定义了相关的子Component后,仅需在父Component中定义返回该子Component的方法。

另外从项目开发进程的角度,前一种情况,一般是定义了父Component后,比如MainApplicationComponent,一般这种Component可能会被其他Compoennt依赖,为了方便以后扩展,我们无论是否有子Compoenent依赖,都先将其可以提供的注入依赖定义为方法。而当某个子Component需要扩展时,则在@Comopnent注解中定义dependencies参数即可。

而后一种情况,MainActivityComponent所注入的Activity,可能我们没办法预见其会需要一个子Component(这种子Component一般是跟其有关系的Activity或者Fragment),因此,如果MainActivity页面后面需要托管某个Fragment,比如MainFragment,则先用@SubComponent注释MainFragmentComponent,然后再修改MainActivityComponent,加上返回MainFragmentComponent的方法。

即,前一种情况,父Component对子Component无感知,而后一种情况则恰恰相反,子Component不清楚其父Component。

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
//MainActivityComponent.java
@Component(dependencies = MainApplicationComponent.class, module = MainActivityModule.class)
public interface MainActivityComponent{
void inject(MainActivity mainActivity);
//增加MainFragmentComponent返回值的方法,
//表示MainFragmentComponent是MainActivityComponent的子Component
MainFragmentComponent getMainFragmentComponent();
}

//MainFragmentComponent.java
//子Component
@SubComponent
public interface MainFragmentComponent {
//注入MainFragment
void inject(MainFragment mainFragment);
}

//MainActivity.java
public class MainActivity extends AppCompatActivity {
MainActivityComponent mainComponent;
//...
//增加方法,返回MainActivityComponent,方便MainFragment调用
public MainActivityComponent getMainActivityComponent() {
return mainComponent;
}
}

//MainFragment.java
public class MainFragment extends Fragment {
MainFragmentComponent mainFragmentComponent;
public void onActivityCreated(Bundle savedInstanceState) {
//通过MainActivityComponent类中的方法来获取子Component
mainFragmentComponent = ((MainActiivty) getActivity).getMainActivityComponent()
.getMainFragmentComponent();
//注入
mainFragmentComponent.inject(this);
}
}

@Scope

@Scope用来限定@Module或@Inject提供的注入元素的作用域。在Java开发中,最常用的设计模式就是单例模式了,单例模式即类在整个应用生命周期或者某一段生命周期中只有一个实例。而Java提供了@Singleton用于注解仅单个实例的类。Dagger2使用@Scope来注解自定义的作用域类,自定义的@Scope类可以作用于@Component类或者@Provides方法,表示整个@Component提供的注入元素均为单例,或者某个@Provides方法提供的注入元素为单例。

此处的单例,是跟随@Component类的实例生存周期一致的,而@Component类如果是在Application中初始化,则很明示整个APP应用生命周期内,该@Component提供的注入元素为单例,如果是在Activity内onCreate方法中初始化,则只要onCreate方法不再次被调用(一般就是Activity的生命周期),则@Component提供的注入元素为单例。

所以无论是@Singleton还是自定义的@Scope,其实是跟随@Component的被初始化情况来决定单例的作用域的,而他们更大的作用,其实是为了增强代码可读性。比如@ApplicationScope、@ActivityScope。其中@Module提供注入方法注解的@Scope必须与@Module类注解的@Scope一致,比如均为@Singleton或者@PerActivity。而@Component之间有依赖关系的,如果都有@Scope修饰,则不能为相同@Scope注解,否则编译会报错。

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
//PerActivity.java
//自定义Scope
//表示单个Activity生命周期注入的实例均为单例[根据字面意思,只是方便理解,并不是真的有这层限制]
@Scope
public @interface PerActivity {
}

//MainApplicationModule.java
@Module
public class MainApplicationModule {
private MainApplication application;
MainApplicationModule(MainApplication application) {
this.application = application;
}

//表示单例,此时相应的Component类也要注解@Singleton
@Singleton
@Provides
public Context providerApplicationContext() {
return application;
}
}

//MainApplicationComponent.java
//MainApplicationComponent注入的类的生命周期内,其所注入的元素均为单例
@Singleton
@Component(module = MainApplicationModule.class)
public interface MainApplicationComponent{
void inject(MainApplication mainApplication);
//增加Context返回类型的方法
Context getApplicationContext();
}

//MainActivityComponent.java
//MainActivityComponent依赖MainApplicationComponent,
//如果其也声明为单例,则不能与MainApplicationComponent声明的@Singleton注解相同
//(虽然作用是一样的,但要表明是不同的作用域)
@PerActivity
@Component(dependencies = MainApplicationComponent.class, module = MainActivityModule.class)
public interface MainActivityComponent{
void inject(MainActivity mainActivity);
//增加MainFragmentComponent返回值的方法,
//表示MainFragmentComponent是MainActivityComponent的子Component
MainFragmentComponent getMainFragmentComponent();
}


//PerFragment.java
//自定义Scope
//表示单个Fragment生命周期注入的实例均为单例[根据字面意思,只是方便理解,并不是真的有这层限制]
@Scope
public @interface PerFragment {
}

//MainFragmentComponent.java
//子Component声明的作用域也不能与其父Component(即MainActivityComponent)一致
//其实@PerFragment与@PerActivity代码是一样的
@PerFragment
@SubComponent
public interface MainFragmentComponent {
//注入MainFragment
void inject(MainFragment mainFragment);
}

@IntoSet/@ElementsIntoSet

@IntoMap和@ElementsIntoSet其实很好理解,可以用来注入集合。@IntoMap注入Map,@ElementsIntoSet注入Set。看代码就明白了:

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
@Module
public class MainActivityModule{
//...
@IntoMap
@StringKey("name") //声明Map中的key值
@Provides
String providerName() {
return "name";
}

@IntoMap
@StringKey("pwd")
@Provides
String providerPwd() {
return "pwd";
}

@ElementsIntoSet
String providerName() {
return "lilei";
}

@ElementsIntoSet
String providerMoreName() {
return "zhangshang";
}
}

public class MainActivity extends AppCompatActivity {

@Inject
HashMap<String> nameMap; //map->{"name"->"name","pwd"->"pwd"}
@Inject
HashSet<String> nameSet; //set->{"lilei","zhangshang"}
}

Dagger2的知识点差不多就这些了。@IntoMap和@ElementsIntoSet其实很简单,不过可能用到的没那么多,因此放到这一章文章中。

以上内容均为个人学习理解,如果有错误之处,可以评论告诉我。共同学习,共同进步。

看了许多Dagger2的文章,主要包括:

但感觉有的文章光有原理没有例子,有的光讲例子,原理又讲的比较少。因此我也来写一篇Dagger2的文章,尽量结合2者,供大家交流学习。

@Inject、@Qualifier

如果我们项目中自己定义的类,比如User类,需要注入到MainActivity中,用来代替new User(),那么在User类中使用@Inject注解User的构造函数表示提供注入,@Inject注解MainActivity中User类的实例属性来表示注入处。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//MainActivity.java
public class MainActivity extends AppCompatActivity {
@Inject
User user; //我需要new User()
}


//User.java
public class User {
String name;
String pwd;

@Inject //我来提供new User()
public User() {

}
}

如果User类有多个构造函数,则需要@Qualifier定义的注解来区分,比如官方的@Named注解

@Qualifier主要是为解决实例提供方有提供多个相同类型的实例时,加以区分,在这里多种构造函数算其中一种情况

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
//Dagger2官方提供的@Named注解源码
@Qualifier
@Documented
@Retention(RUNTIME)
public @interface Named {

/**
* The name.
*/
String value() default "";
}

//User.java
public class User {
String name;
String pwd;

@Inject //我来提供new User()
@Named("user")
public User() {

}

@Named("user_with_value") //我来提供new User(String, String)
public User(String name, String pwd) {
this.name = name;
this.pwd = pwd;
}
}

//MainActivity.java
public class MainActivity extends AppCompatActivity {
@Inject
@Named("user")
User user;

@Inject
@Named("user_with_value")
User userWithValue;
}

@Module、@Component和@Provides

Module的引入是为了解决第三库提供的实例注入问题,比如User类是某个第三方库的文件,那么我们没办法在其构造函数上增加@Inject注解。这时候可以通过@Module注解的类中以@Provides注解相应返回类型为User的方法来提供实例

如果你原先的代码现在需要引入Dagger2,也可以通过@Module方式来提供注入,这样基本不用修改原有代码,另外可以把某个页面需要注入的实例全部在Module类中提供,也方便维护。而这里的某个页面具体要怎么划分,是单个activity或者单个功能,就看情况而定了,目前大多数推荐是以页面来划分Module

我们选择以页面切分Module,以下Module中提供的依赖供MainActivity页面使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//MainActivityModule.java
@Module
public class MainActivityModule {
//返回值表示提供User类的实例注入,Dagger2官方推荐providerXX作方法名
//如果是在Module中提供多个相同返回类型,也需要用到@Qualified注解过的注解来区分
@Provides
@Named("user")
public User providerUser() {
return new User();
}

//这里的参数类型为String,如果有@Provides注解的方法返回值为String,就会被自动注入,
//因此这里比较好的提供参数呢??我也不知道[摊手]
@Provides
@Named("user_with_value")
public User providerValueableUser(String name, String pwd) {
return new User(name, pwd);
}
}

如果通过@Inject和@Module都有提供User类的注入依赖,按照Dagger2的处理,优先使用@Module类中提供的注入。

如果User类仅在MainActivityModule类中有定义提供注入的方法,那么在MainActivity.java类中@Inject的属性如何初始化呢?这时候Dagger2提供@Component类来关联:

1
2
3
4
5
6
7
8
9
10
//MainActivityComponent.java
//定义module值来表示MainComponent可以提供MainActivityModule中的注入
@Component(module = MainActivityModule.class)
public interface MainActivityComponent {
//Dagger2推荐injectXX(Object)方法来定义Component要注入的地方
//Dagger2通过自动生成代码,把MainActivity中要注入的实例,
//与MainActivityModule中提供的实例初始化方法相关联
//相当于User user[MainActivity提供] = new User()[MainActivityModule提供]
void inject(MainActivity mainActivity);
}

这时,需要把MainComponent与MainActivity关联,这部分代码用到了Dagger2自动生成的代码。所以需要先编译工程,然后在MainActivity.java中加上以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//MainActivity.java
public class MainActivity extends AppCompatActivity {
@Inject
@Named("user")
User user; //从MainActivityModule中注入

MainActivityComponent mainComponent; //Component需要初始化

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//DaggerMainActivityComponent是Dagger2通过编译自动生成的,
//可以去看看生成的代码是怎么把@Module和@Inject关联起来的
mainComponent = DaggerMainActivityComponent.builder()
.build();
//注入
mainComponent.inject(this);
}
}

以上讲解了:

  1. @Inject注解在需要注入提供注入处;

  2. @Inject注解在需要注入处;

    @Module通过@Provides提供注入

    @Component在需要注入页面处初始化,来连接@Inject和@Module

二种情况,也是Dagger2最基本的情况。
下一篇会再讲Dagger2复杂一点的用法。包括

  • 多个@Component间的依赖
1
2
@Component(dependencies = MainApplicationComponent.class,
modules ={MainActivityModule.class,ModuleB.class,ModuleA.class,MyModule.class})
  • 子Component@SubComponent
  • 作用域@Scope
1
2
3
4
@Scope
@Documented
@Retention(RUNTIME)
public @interface Singleton {}
  • Map值@IntoMap

  • Set值@ElementsIntoSet

也不知道啥时候会有下一篇!期待吧!

以上内容均为个人学习理解,如果有错误之处,可以评论告诉我。共同学习,共同进步。

Android

  1. Android事件分发机制完全解析,带你从源码的角度彻底理解(上)

  2. Android事件分发机制完全解析,带你从源码的角度彻底理解(下)

    • onTouch() 方法返回false—>onTouchEvents()—>onClick()
    • onInterceptTouchEvent默认返回false,优先子类处理点击事件,否则父view处理
  3. 给 Android 开发者的 RxJava 详解

  4. Android 热修复方案对比

Android tips/count

  1. awesome-android-tips

  2. Android学习资源网站大全

  3. 那些年你用过的 Android 开源项目都有什么?

  4. 从零开始的Android新项目5 - Repository层(上) Retrofit、Repository组装

  5. 我所理解的RxJava——上手其实很简单(一)

  6. 给 Android 开发者的 RxJava 详解

  7. 全面了解Nginx到底能做什么

  8. https://github.com/markzhai/AndroidProjectFrom0 <—这是啥?可以吃吗?

  9. https://ydmmocoo.github.io/2016/06/22/Android%E9%9D%A2%E8%AF%95%E9%A2%98%E6%95%B4%E7%90%86/

  10. https://github.com/xiangzhihong/android-Interview

  11. http://www.jianshu.com/p/e99b5e8bd67b

面试

  1. Android工程师面试题大全
  2. 2016Android某公司面试题

Git

  1. 10 个迅速提升你 Git 水平的提示

设计模式

  1. 图说设计模式

基础

dispatchTouchEvent

onInterceptTouchEvent

onTouchEvent

  1. android 事件分发:dispatchTouchEvent->onInterceptTouchEvent->onTouchEvent
  2. android activity的生命周期:onCreate-onStart-onResume-onPause-onStop-onRestart-onDestroy
  3. activity启动另一个activity时,先调用前一个activity的onPause->另一个activity的onResume

保存数据操作可以在onStop中处理或者onDestroy

  1. fragment生命周期:onAttach->onCreate->onCreateView->onActivityCreated->onStart->onResume->onPause->onStop->onDestroyView->onDestroy->onDetach
  2. service生命周期:bindService->onCreate->onBind->onUnBind->onDestroy

startService-onCreate->onStartCommond-onStart

  1. BroadcastReceiver:registerBroadcast

  2. Android设计模式[https://gof.quanke.name/]

    • 创建型->建造者模式、单例模式、简单工厂模式、工厂方法模式、抽象工厂模式、原型模式[6]
    • 结构型->适配器模式、桥接模式、装饰模式、外观模式、享元模式、代理模式、组合模式[7]
    • 行为型->策略模式、观察者模式、迭代器模式、责任链模式、命令模式、模板方法模式、访问者模式[7]
  3. 创建型

    • 建造者模式,
    • 单例模式,
    • 简单工厂模式(在一个方法里面作判断,来返回相应的对象实例),
    • 工厂方法模式(通过接口定义,子类实现来返回相应的对象实例),一般是静态方法
    • 抽象工厂模式(通过接口定义,将具有同一主题的单独工厂封装在一起)
    • 单例模式
    • 原型模式(clone)
  4. 结构型

    • 适配器模式,客户端:activity->listview.setAdapter(new MyAdapter());一个类的接口转换成用户所期待的,分为类适配器和对象适配器,类适配器表示为class Adapter implements Target extends Adaptee,通过继承来适配,一般不推荐。对象适配器表示为class Adapter implements Target{Adaptee mAdaptee},通过持有被适配对象的实例来适配

      listview.getCount(),getItemPosition->Adapter->Target[ListView所需的getItem,getCount,]->Adaptee[数据集]

    • 桥接模式,将抽象部分与它的实现部分分离,使它们都可以独立地变化

    • 组合模式,组合多个对象形成树形结构以表示具有“整体—部分”关系的层次结构

  5. 程序无响应。避免在UI线程执行大量耗时操作,可以使用AsyncTask,HandleThread,Service,Thread

Retrofit-Gson解析

每次都对应Bean对解析

Class->Callback

1
2
3
4
{
"name":"lee",
"sex":"male"
}

Array->Callback<List>

1
2
3
4
5
6
7
8
9
10
[
{
"name":"lee",
"sex":"male"
},
{
"name":"hu",
"sex":"female"
}
]

每次请求都有状态码和状态值后,如果使用data把具体的对象包起来

封装一个Response

class->Callback<Response>

1
2
3
4
5
6
7
8
{
"code":200,
"message":"OK",
"data":{
"name":"lee",
"sex":"male"
}
}

Array-Callback<Response<List>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"code":200,
"message":"OK",
"data":{[
{
"name":"lee",
"sex":"male"
},
{
"name":"hu",
"sex":"female"
}
]}
}

Retrofit2可以替换的地方:addConvertFactory(GsonConvertFactory.create())->增加解析器,如gson解析,将最优先的解析器放在前面。addCallAdapterFactory(new ErrorHandleFacotry.create())/addCallAdapterFactory(RxJavaCallAdapter.create())->替换成自定义call。

Gson中的GsonBuilder可以自定义,GsonBuilder().registerTypeAdapter(BaseResponse.class,new TypeAdapter() )与registerTypeAdapterFacotry(ItemTypeAdapterFactory.create())基本一致,用于自定义处理BaseResponse返回的数据

《Android开发艺术探索》笔记

  1. 启动新的Activity之前会先调用旧Activity的onPause()方法,因此在onPause()方法中不要做耗时操作

  2. Activity可能被杀死的情况

    • 资源相关的系统配置发生变化。可以通过android:configuration来配置什么情况下不销毁重启Activity,常用属性包括locale,oriontation,keyboardHidden,即语言资源变化,横竖屏切换,键盘显示或隐藏。此时系统回调onConfigurationChanged(Configuration newConfig)函数。
    • 系统内存不足。系统调用onSaveInstanceState->onStop()->onDestroy(),在onSaveInstanceState方法中保存信息;重建时调用,onCreate()->onStart()->onRestoreInstanceState,可以在onCreateonRestoreInstancestate方法中恢复保存的信息。
  3. android:launchMode属性:

    • singleInstance保证类存在单独的task中并仅存在一个实例,适合与程序分离的页面,如闹钟提醒页面;
    • singleTop表示类在当前task栈顶时,复用,此时调用onNewInstance方法,否则新建实例,适合消息通知类activity;
    • singleTask表示类在当前task栈中存在时,清空在其上面的所有activity实例,直接复用,此时调用onNewInstance方法,否则新建实例,适合用于程序入口。
  4. android:taskAffinity属性:

    • 定义该Activity所属的任务栈,默认为程序的包名。配合launchModesingleTask时使用
  5. Activity的Flags

    • FLAG_ACTIVITY_NEW_TASK
      效果等同android:launchMode="singleTask"
    • FLAG_ACTIVITY_SINGLE_TOP
      效果等同android:launchMode="singleTop"
    • FLAG_ACTIVITY_CLEAR_TOP
      清空同一任务栈中在要启动Activity之上的Activity
    • FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
      不在历史Activity列表中显示,效果等同android:excludeFromRecents="true"
  6. Activity所在任务栈由其taskAffinity属性决定,默认为应用包名。新启动Activity若未指定TaskAffinity,则置于启动其的Activity所在任务栈的栈顶。非Activity的Context启动的Activity需要指定Flags为FLAG_ACTIVITY_NEW_TASK,比如从service中启动Activity,即以singleTask方法启动。

android系统架构

  1. Linux内核层
  2. 系统运行库层
  3. 应用框架层
  4. 应用层

隐式Intent

  • 配置

    1
    2
    3
    4
    <intent-filter>
    <action android:name="com.example.activitytest.ACTION_START" />
    <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
  • 调用

    1
    2
    Intent intent = new Intent("com.example.activitytest.ACTION_START");
    startActivity(intent);

    android.intent.category.DEFAULT是一种默认的category,在调用startActivity()方法的时候会自动将这个category添加到Intent中。
    所有intent-filter都必须添加android.intent.category.DEFAULT,除了LAUNCHER类别

  • 分类
    Intent.ACTION_VIEW查看某个URL
    Intent.ACTION_SEND发送邮件
    android.intent.category.LAUNCHER表示activity应该显示在顶级应用启动器中
    android.intent.category.INFO表示activity向用户显示了包信息,但它不应该显示在启动器中

  1. 保存内容到文件

    • 保存到内部存储(Internal Storage)
    • 保存到外部存储(External Storage)
  2. 内部与外部存储比较

    内部存储

    • 总是可用
    • 保存的文件只能被当前应用访问
    • 用户卸载应用后,内部存储保存的文件会被全部删除

    外部存储

    • 当用户挂载外部存储(SD卡)时可用,卸载后无法使用
    • 其他应用可以访问
    • 用户卸载应用后,系统将会删除通过getExternalFilesDir()创建的目录
  3. 保存文件到内部存储

    • getFilesDir()返回应用内部存储根目录
    • getCacheDir()返回应用内部存储缓存目录
    • 创建文件
      1
      File file = new File(context.getFilesDir(), filename);
    • 通过openFileOutput或者FileOutputStream
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      String filename = "myfile";
      String string = "Hello world!";
      FileOutputStream outputStream;
      try {
      outputStream = openFileOutput(filename, Context.MODE_PRIVATE);
      outputStream.write(string.getBytes());
      outputStream.close();
      } catch (Exception e) {
      e.printStackTrace();
      }
    • 创建缓存文件
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      public File getTempFile(Context context, String url) {
      File file;
      try {
      String fileName = Uri.parse(url).getLastPathSegment();
      file = File.createTempFile(fileName, null, context.getCacheDir());
      catch (IOException e) {
      // Error while creating file
      }
      return file;
      }
  4. 保存文件到外部存储

    • 获取外部存储状态
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      /* Checks if external storage is available for read and write */
      public boolean isExternalStorageWritable() {
      String state = Environment.getExternalStorageState();
      if (Environment.MEDIA_MOUNTED.equals(state)) {
        return true;
      }
      return false;
      }
      /* Checks if external storage is available to at least read */
      public boolean isExternalStorageReadable() {
      String state = Environment.getExternalStorageState();
      if (Environment.MEDIA_MOUNTED.equals(state) ||
        Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
        return true;
      }
      return false;
      }
    • 尽管external storage对于用户与其他app是可以修改的,但有2类文件可以保存在external storage中:
      • Public files这些文件对与用户与其他app来说是public的,当用户卸载你的app时,这些文件应该保留。例如,那些被你的app拍摄的图片或者下载的文件。
      • Private files对其他app无意义的,当用户卸载你的app时,系统会删除你的app的private目录。例如,那些被你的app下载的缓存文件。
    • 如果你想要保存文件为public形式的,用getExternalStoragePublicDirectory()方法来获取一个 File 对象来表示存储在external storage的目录。
      1
      2
      3
      4
      5
      6
      7
      8
      9
      public File getAlbumStorageDir(String albumName) {
      // Get the directory for the user's public pictures directory.
      File file = new File(Environment.getExternalStoragePublicDirectory(
          Environment.DIRECTORY_PICTURES), albumName);
      if (!file.mkdirs()) {
          Log.e(LOG_TAG, "Directory not created");
      }
      return file;
      }
    • 如果你想要保存文件为私有的方式,你可以通过执行getExternalFilesDir()来获取相应的目录,并且传递一个指示文件类型的参数。
      1
      2
      3
      4
      5
      6
      7
      8
      9
      public File getAlbumStorageDir(Context context, String albumName) {
      // Get the directory for the app's private pictures directory.
      File file = new File(context.getExternalFilesDir(
      Environment.DIRECTORY_PICTURES), albumName);
      if (!file.mkdirs()) {
      Log.e(LOG_TAG, "Directory not created");
      }
      return file;
      }
  5. 查询剩余空间

  6. 删除文件

    • 删除文件myFile.delete()
    • 如果文件是保存在internal storage,你可以通过Context来访问并通过执行deleteFile()进行删除myContext.deleteFile(fileName);

      当用户卸载你的app时,android系统会删除以下文件:
      所有保存到internal storage的文件。
      所有使用getExternalFilesDir()方式保存在external storage的文件。
      然而,通常来说,你应该手动删除所有通过 getCacheDir() 方式创建的缓存文件,以及那些不会再用到的文件。

  1. 获取SharedPreference

  • getSharedPreferences(String name, int mode) — 通过名称,和访问mode来新建一个shared preference文件。mode值可以是:

    1
    2
    3
    4
    MODE_PRIVATE
    MODE_WORLD_READABLE
    MODE_WORLD_WRITEABLE
    MODE_MULTI_PROCESS
  • getPreferences() — 创建默认的shared preference文件,app只需要一个preference文件时使用。

  • 例子:

    1
    2
    3
    Context context = getActivity();
    SharedPreferences sharedPref = context.getSharedPreferences(
    getString(R.string.preference_file_key), Context.MODE_PRIVATE);
  1. 写Shared Preference

    1
    2
    3
    4
    SharedPreferences sharedPref = getActivity().getPreferences(Context.MODE_PRIVATE);
    SharedPreferences.Editor editor = sharedPref.edit();
    editor.putInt(getString(R.string.saved_high_score), newHighScore);
    editor.commit();
  2. 读Shared Preference

    1
    2
    3
    SharedPreferences sharedPref = getActivity().getPreferences(Context.MODE_PRIVATE);
    long default = getResources().getInteger(R.string.saved_high_score_default));
    long highScore = sharedPref.getInt(getString(R.string.saved_high_score), default);

Android四大组件
Activity
Service
BroadCastReceiver
Content Provider

Activity

  1. Activity的生命周期

  • onCreate():创建
  • onStart():运行
    • onResume():获取焦点
    • onPause():失去焦点
    • onStop():暂停
    • onDestroy():销毁
    • onRestart():重启
  1. Activity四种状态

  • Activ/Running 活动状态
  • Pause 暂停状态
    • Stopped 停止状态
    • Killed 非活动状态

Fragment

  1. Fragment的生命周期

  • onAttach()
  • onCreate()
  • onCreateView()
  • onActivityCreated()
  • onStart()
  • onResume()
  • onPause()
  • onStop()
  • onDestoryView()
  • onDestory()
  • onDetach()

Intent

  1. Intent实现页面之间跳转

    • startActivity(intent)
    • startActivityForResult(intent, requestCode, options)
      • 传递类 onActivityResult(int requestCode, int resultCode, Intent data)
      • 接收类 setResult(int resultCode,Intent intent)