安卓APP

💩

声明周期和启动模式

  • 典型情况下的声明周期
    1. onCreate: 表示Activity正在被创建。
    2. onRestart: 表示Activity正在重新启动。
    3. onStart: 表示Activity正在被启动。这时Activity已经可见,但还是没有出现在前台。
    4. onResume: 表示Activity已经可见了,并且出现在前台并开始活动。
    5. onPause: 表示Activity正在停止。
    6. onStop: 表示Activity即将停止。
    7. onDestroy: 表示Activity即将被销毁。

IPC基础概念

  • 在Android中使用多线程只有一种方法,那就是给四大组件在AndroidMenifest中指定android:process属性,除此之外没有其他办法,也就是说我们无法给一个线程或者一个实体类指定其运行时所在的进程。其实还有一种非常规的多进程方法,那就是通过JNI在native层去fork一个新的进程,但是这种方法属于特殊情况,也不是常用的创建多进程的方式。

  • 一般来说,使用多线程会造成如下几方面的问题:

    1. 静态成员和单例模式完全失效
    2. 线程同步机制完全失效
    3. SharedPreferences的可靠性下降
    4. Application会多次创建
  • 实现跨进程通信的方式很多,比如通过Intent来传递数据,共享文件和SharedPreferences,基于Binder的Messager和AIDL以及Socket等。

  • IPC中的一些基础概念,主要包括三方面内容:Serializable接口、Parcelable接口以及Binder。

  • 什么是对象的序列化?
    对象序列化就是将对象的状态信息转化为可以存储或者传输的形式的过程,这些信息包括Class信息、继承关系信息、访问权限、变量类型以及数值信息等。
    将序列化对象写入文件后,也可以从文件中读取出来并对其进行反序列化操作。通过反序列化将对象的信息全部获取,然后可以在内存中根据这些信息新建对象。
    Java提供了Seriabizable接口来实现对象序列化。当对象在保存对象时,会把其状态保存为一组字节,反序列化时再将这些还原成对象再内存当中重新创建。需要注意的是类中的静态变量是不会被序列化的,因为它不属于对象而是属于类。

Seriablizable

  • Serializable是Java锁提供的一个序列化接口,它是一个空接口,为对象提供标准的序列化和反序列化操作。如下所示:
    User类是一个实现了Serializable接口的类,它是可以被序列化和反序列化的:

    1
    2
    3
    4
    5
    6
    7
    8
    public class User implements Seriablizable {
    private static final long serialVersionUID = 519067123721295773L;

    public int userId;
    public String userName;
    public boolean isMale;
    ...
    }

    只需要采用ObjectOutputStream和ObjectInputStream即可轻松实现对象的序列化和反序列化:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    //序列化过程
    User user = new User(0, "Jake", true);
    ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("cache.txt"));
    out.writeObject(user);
    out.close();

    //反序列化
    ObjectInputStream in = new ObjectInputStream(new FileInputStream("cache.txt"));
    User newUser = (User) in.readObject();
    in.close();
  • 上述代码演示了采用Serializable方式序列化对象的典型过程,很简单,只需要把实现了Serializable接口的User对象写到文件中就可以快速恢复了,恢复后的对象newUser和user的内容完全一样,但是两者并不是同一个对象。

Parcelable

  • Parcelable接口。只要实现了这个接口,一个类的对象就可以实现序列化并可以通过Intent和Binder传递。

  • 以下是一个典型用法:

    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
    public class User implements Parcelable {
    public int userId;
    public String userName;
    public boolean isMale;

    public Book book;

    public User(int userId, String userName, boolean isMale) {
    this.userId = userId;
    this.userName = userName;
    this.isMale = isMale;
    }

    //内容描述
    public int describeContents() {
    return 0;//几乎在所有情况下这个方法都应该返回0,仅当当前对象中存在文件描述符时,此方法返回1。
    }

    //序列化
    public void writeToParcel(Parcel out, int flags) {
    out.writeInt(userId);
    out.writeString(userName);
    out.writeInt(isMale?1:0);
    out.writeParcelable(book, 0);
    }

    //反序列化
    public static final Parcelable.Creator<User> CREATOR = new Parcelable.Creator<User>() {
    public User createFromParcel(Parcel in) {
    return new User(in);
    }

    public User[] = newArray(int size) {
    return new User[size];
    }
    };

    private User(Parcel in) {
    userId = in.readInt();
    userName = in.readString();
    isMale = in.readInt() == 1;
    book = in.readParcelable(Thread.currentThread().getContextClassLoader());
    //由于book是另一个可序列化对象,所以它的反序列化过程需要传递当前线程的上下文类加载器,否则会报找不到类的错误。
    }
    }
  • 系统已经为我们提供了许多实现了Parcelable接口的类,它们都是可以直接序列化的,比如Intent、Bundle、Bitmap等,同时List和Map也可以序列化,前提是它们里面的每个元素都是可序列化的。

Binder

直观来说,Binder是Android中的一个类,它实现了IBinder接口。从IPC角度来说,Binder是Android中的一种跨进程通信方式,Binder还可以理解为一种虚拟的物理设备,它的设备驱动是/dev/binder,该通信方式在Linux中没有;从Android Framework角度来说,Binder是ServiceManager连接各种Manager(ActivityManager、WindowManager,等等)和相应ManagerService的桥梁;从Android应用层来说,Binder是客户端和服务器端进行通信的媒介,当bindService的时候,服务端会返回一个包含了服务端业务调用的Binder对象,通过这个Binder对象,客户端就可以获取服务端提供的服务或者数据,这里的服务包括普通服务和基于AIDL的服务。

Android开发中,BInder主要用在Service中,包括AIDL和Messager,其中普通Service中的Binder不涉及进程间通信,所以较为简单,无法触及Binder的核心,而Messager的底层其实是AIDL。

Binder的工作机制.jpg

Android中的IPC方式

使用Bundle

Activity、Service、Receiver都支持在Intent中传递Bundle数据

使用文件共享

文件共享方式适合在对数据同步要求不高的进程之前进行通信,并且要妥善处理并发读/写的问题。
但是,SharedPreferences是个特例,不建议在进程间通信中使用SharedPreferences。

使用Messenger

Messenger工作原理.jpg

使用AIDL

Messenger是已串行的方式处理客户端发来的消息,如果大量的消息同时发送到服务端,服务端仍然只能一个个处理,如果有大量的并发请求,那么用Messenger就不太适合了。同时,Messenger的作用主要是为了传递消息,很多时候我们可能需要跨进程调用服务端的方法,这种情形用Messenger就无法做到了,但是我们可以使用AIDL来实现跨进程的方法调用。AIDL也是Messenger的底层实现,因此,Messenger本质上也是AIDL,只不过系统为我们做了封装从而方便上层的调用而已。

在Binder的基础上我们可以更容易地理解AIDL。这里先介绍使用AIDL来进行进程间通信的流程,分为服务端和客户端两个方面。

  1. 服务端
    服务端首先要创建一个Service用来监听客户端的连接请求,然后创建一个AIDL文件,将暴露给客户端的接口在这个AIDL文件中声明,最后在Service中实现这个AIDL接口即可。

  2. 客户端
    客户端所要做的事情就稍微简单一些,首先需要绑定服务端的Service,绑定成功后,将服务端返回的Binder对象转成AIDL接口所属的类型,接着就可以调用AIDL中的方法了。

  3. AIDL接口的创建
    首先看AIDL接口的创建,如下所示,我们创建了一个后缀为AIDL的文件,在里面声明了一个接口和两个接口方法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    // IBookManager.aidl
    package com.ryg.chapter_2.aidl;

    import com.ryg.chapter_2.aidl.Book;

    interface IBookManager {
    List<Book> getBookList();
    void addBook(in Book book);
    }

    AIDL文件支持的数据类型:

    数据类型 备注
    基本数据类型 int、long、char、boolean、double等
    String 和 CharSequence
    List 只支持ArrayList,里面每个元素都必须能够被AIDL支持
    Map 只支持HashMap,里面的每个元素都必须被AIDL支持,包括key和value
    Parcelable 所有实现了Parcelable接口的对象
    AIDL 所有的AIDL接口本身也可以在AIDL文件中使用

    以上6种数据类型就是AIDL所支持的所有数据类型,其中自定义的Parcelable对象和AIDL对象必须要显式import进来,不管它们是否和当前AIDL文件位于同一个包内。

    另外一个需要注意的地方是,如果AIDL文件中用到了自定义的Parcelable对象,那么必须新建一个和它同名的AIDL文件,并在其中声明它为Parcelable类型。

    1
    2
    3
    4
    // Book.aidl
    package com.ryg.chapter_2.aidl;

    parcelable Book;

    AIDL中除了基本数据类型,其他类型的参数必须标上方向:in、out或者inout,in表示输入性参数,out表示输出型参数,inout表示输入输出型参数。

    AIDL接口中只支持方法,不支持声明静态常量,这一点区别于传统的接口。

  4. 远程服务端Service的实现
    上面讲述了如何定义AIDL接口,接下来我们就需要实现这个接口了。我们先创建一个Service,称为BookManagerService,代码如下:

    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
    public class BookManagerService extends Service {

    private static final String TAG = "BMS";

    private CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArrayList<Book>();
    //CopyOnWriteArrayList支持并发读/写。
    //前面我们提到,AIDL方法是在服务端的Binder线程池执行的,因此,当多个客户端同时连接的时候,
    //会存在多个线程同时访问的情形,所以我们要在AIDL方法中处理线程同步,
    //而我们这里直接使用CopyOnWriteArrayList来进行自动的线程同步。

    private Binder mBinder = new IBookManager.Stub() {

    @Override
    public List<Book> getBookList() throws RemoteException {
    return mBookList;
    }

    @Override
    public void addBook(Book book) throws RemoteException {
    mBookList.add(book);
    }
    };

    @Override
    public void onCreate() {
    super.onCreate();
    mBookList.add(new Book(1, "Android"));
    mBookList.add(new Book(2, "Ios"));
    }

    @Override
    public IBinder onBind(Intent intent) {
    return mBinder;
    }
    }

    然后我们需要在XML中注册这个Service,如下所示:

    1
    2
    3
    4
    <service
    android:name=".aidl.BookManagerService"
    android:process=":remote" >
    </service>

    注意BookManagerService是运行在独立的进程中的,它和客户端的Activity不在同一个进程中,这样就构成了进程间通信的场景。

  5. 客户端的实现
    客户端的实现就比较简单了,首先要绑定远程服务,绑定成功后将服务端返回的Binder对象转换成AIDL接口,然后就可以通过这个接口去调用服务端的远程方法了,代码如下所示。

    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
    public class BookManagerActivity extends Activity {

    private static final String TAG = "BookManagerActivity";

    private ServiceConnection mConnection = new ServiceConnection() {
    public void onServiceConnected(ComponentName className, IBinder service) {
    IBookManager bookManager = IBookManager.Stub.asInterface(service);
    try {
    List<Book> list = bookManager.getBookList();
    Log.i(TAG, "query book list, list type:" + list.getClass().getCanonicalName());
    Log.i(TAG, "query book list:" + list.toString());
    } catch (RemoteException e) {
    e.printStackTrace();
    }
    }

    public void onServiceDisconnected(ComponentName className) {
    }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_book_manager);
    Intent intent = new Intent(this, BookManagerService.class);
    bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
    }

    @Override
    protected void onDestroy() {
    unbindService(mConnection);
    super.onDestroy();
    }
    }

    绑定成功后,会通过bookManager去调用getBookList方法,然后打印出所获取的图书信息。需要注意的是,服务端的方法有可能需要很久才能执行完毕,这个时候下面的代码就会导致ANR,这一点是需要注意的,后面会再介绍这种情况,之所以先这么写是为了让读者更好地了解AIDL的实现步骤。

    接着在XML中注册此Activity,运行程序。

    RemoteCallbackList是系统专门提供的用于删除跨进程listener的接口。RemoteCallbackList是一个泛型,支持管理任意的AIDL接口。

     - 虽然说多次跨进程传输客户端的同一个对象会在服务端生成不同的对象,但是这些新生成的对象有一个共同点,那就是它们的底层的Binder对象是同一个。当客户端解注册的时候,我们只要遍历服务端所有的listener,找出那个和解注册listener具有相同Binder对象的服务端listener并把它删掉即可。  
     - RemoteCallbackList还有一个很有用的功能,那就是当客户端进程终止后,它能够自动移除客户端所注册的listener。  
     - RemoteCallbackList内部自动实现了线程同步功能,所以我们使用它来注册和解注册时,不需要做额外的线程同步工作。  
    

到这里,AIDL的基本使用方法已经介绍完了,但是有几点还需要再次说明一下。我们知道,客户端调用远程服务的方法,被调用的方法运行在服务端的Binder线程池中,同时客户端线程会被挂起,这个时候如果服务端方法执行比较耗时,就会导致客户端线程长时间地阻塞在这里,而如果这个客户端线程是UI线程的话,就会导致客户端ANR,这当然不是我们想要看到的。因此,如果我们明确知道某个远程方法是耗时的,那么就要避免在客户端的UI线程中去访问远程方法。由于客户端的onServiceConnected和onSerivceDisconnected方法都运行在UI线程中,所以也不可以在它们里面直接调用服务端的耗时方法,这点要尤其注意。另外,由于服务端的方法本身就运行在服务端的Binder线程池中,所以服务端方法本身就可以执行大量耗时操作,这个时候切记不要在服务端方法中开线程去进行异步任务,除非你明确知道自己在干什么,否则不建议这么做。

同理,当远程服务端需要调用客户端的listener中的方法时,被调用的方法也运行在Binder线程池中,只不过是客户端的线程池。所以我们同样不可以在服务端中调用客户端的耗时方法。

最后一步:权限验证!

总结一下大致流程:
首先创建一个Service和一个AIDL接口,接着创建一个类继承自AIDL接口中的Stub类并实现Stub中的抽象方法,在Service的onBind方法中返回这个类的对象,然后客户端就可以绑定服务端Service,建立连接后就可以访问远程服务端的方法了。

使用ContentProvider

使用Socket

四大组件的工作过程

Android的四大组件中除了BroadcastReceiver以外,其他三种组件都必须在AndroidManifest中注册,对于BroadcastReceiver来说,它既可以在AndroidManifest中注册,也可以通过代码来注册。在调用方式上,Activity、Service和BroadcastReceiver需要借助Intent,而ContentProvider则无须借助Intent。

广播(BroadcastReceiver)的注册有两种方式:静态注册和动态注册。静态注册是指在AndroidManifest中注册广播,这种广播在应用安装时会被系统解析,此种形式的广播不需要应用启动就可以收到相应的广播。动态注册广播需要通过Context.registerReceiver()来实现,并且在不需要的时候要通过Context.unRegisterReceiver()来解除广播,此种形式的广播必须要应用启动才能注册并接收广播,因为应用不启动就无法注册广播,无法注册广播就无法收到相应的广播。

Android层次分析 –从顶层到底层

Android的线程和线程池

主线程是指进程所拥有的线程,在Java中默认情况下一个进程只有一个线程,这个线程就是主线程.主线程主要处理界面交互相关的逻辑,因为用户随时会和界面发生交互,因此主线程在任何时候都必须有较高的响应速度,否则就会产生一种界面卡顿的感觉.为了保持较高的响应速度,这就要求主线程中不能执行耗时的任务,这个时候子线程就派上用场了.子线程也叫工作线程,除了主线程以外的线程都是子线程.

Android沿用了Java的线程模型,其中的线程也分为主线程和子线程,其中主线程也叫UI线程。主线程的作用是运行四大组件以及处理它们和用户的交互,而子线程的作用则是执行耗时任务,比如网络请求、I/O操作等。

Android的线程形态:传统的Thread、AsyncTask、HandlerThread、IntentService。

Android线程的基本用法:

  1. 新建一个类继承自Thread,然后重写父类的run()方法,并在里面写耗时逻辑

    1
    2
    3
    4
    5
    6
    class MyThread extends Thread {
    @Override
    public void run() {
    //处理具体的逻辑
    }
    }

    如何启动?

    1
    new MyThread().start();
  2. 实现Runnable接口的方式来定义一个线程

    1
    2
    3
    4
    5
    6
    class MyThread implements Runnable {
    @Override
    public void run() {
    //处理具体的逻辑
    }
    }

    如何启动?

    1
    2
    Mythread myThread = new MyThread();
    new Thread(myThread).start();
  3. 使用匿名类的方式,这种方法更为常见

    1
    2
    3
    4
    5
    6
    new Thread(new Runnable() {
    @Override
    public void run() {
    //处理具体的逻辑
    }
    }).start();

时间

Java 时间之 currentTimeMillis 与 nanoTime - 时间精确测量