标签归档:核心概念

Java synchronized详解

第一篇:

使用synchronized

在编写一个类时,如果该类中的代码可能运行于多线程环境下,那么就要考虑同步的问题。在Java中内置了语言级的同步原语--synchronized,这也大大简化了Java中多线程同步的使用。我们首先编写一个非常简单的多线程的程序,是模拟银行中的多个线程同时对同一个储蓄账户进行存款、取款操作的。
在程序中我们使用了一个简化版本的Account类,代表了一个银行账户的信息。在主程序中我们首先生成了1000个线程,然后启动它们,每一个线程都对John的账户进行存100元,然后马上又取出100元。这样,对于John的账户来说,最终账户的余额应该是还是1000元才对。然而运行的结果却超出我们的想像,首先来看看我们的演示代码:

class Account {
    String name;
    float amount;
    
    
    public Account(String name, float amount) {
        this.name = name;
        this.amount = amount;
    }

    public  void deposit(float amt) {
        float tmp = amount;
        tmp += amt;
        
        try {
            Thread.sleep(100);//模拟其它处理所需要的时间,比如刷新数据库等
        } catch (InterruptedException e) {
            // ignore
        }
        
        amount = tmp;
    }

    public  void withdraw(float amt) {
        float tmp = amount;
        tmp -= amt;

        try {
            Thread.sleep(100);//模拟其它处理所需要的时间,比如刷新数据库等
        } catch (InterruptedException e) {
            // ignore
        }        

        amount = tmp;
    }

    public float getBalance() {
        return amount;
    }
}



public class AccountTest{
    private static int NUM_OF_THREAD = 1000;
    static Thread[] threads = new Thread[NUM_OF_THREAD];
    
    public static void main(String[] args){
        final Account acc = new Account("John"1000.0f);
        for (int i = 0; i< NUM_OF_THREAD; i++) {
            threads[i] = new Thread(new Runnable() {
                public void run() {
                        acc.deposit(100.0f);
                        acc.withdraw(100.0f);
                }
            });
            threads[i].start();
        }

        for (int i=0; i<NUM_OF_THREAD; i++){
            try {
                threads[i].join(); //等待所有线程运行结束
            } catch (InterruptedException e) {
                // ignore
            }
        }
        System.out.println("Finally, John's balance is:" + acc.getBalance());
    }

}

注意,上面在Account的deposit和withdraw方法中之所以要把对amount的运算使用一个临时变量首先存储,sleep一段时间,然后,再赋值给amount,是为了模拟真实运行时的情况。因为在真实系统中,账户信息肯定是存储在持久媒介中,比如RDBMS中,此处的睡眠的时间相当于比较耗时的数据库操作,最后把临时变量tmp的值赋值给amount相当于把amount的改动写入数据库中。运行AccountTest,结果如下(每一次结果都会不同):

E:javaexerbin>java AccountTest
Finally, John's balance is:3900.0

E:javaexerbin>java AccountTest
Finally, John's balance is:4900.0

E:javaexerbin>java AccountTest
Finally, John's balance is:4700.0

E:javaexerbin>java AccountTest
Finally, John's balance is:3900.0

E:javaexerbin>java AccountTest
Finally, John's balance is:3900.0

E:javaexerbin>java AccountTest
Finally, John's balance is:5200.0

为什么会出现这样的问题?这就是多线程中的同步的问题。在我们的程序中,Account中的amount会同时被多个线程所访问,这就是一个竞争资源,通常称作竞态条件。

拿a,b3个线程举例:

a线程存(事件1)->b线程存一半(事件2,刚记下amount)->a线程提(事件3)->b线程存剩下一半(事件4)->b线程提(事件5)

amount变化值:

1000->a线程存(事件1)->1100

1100->b线程存一半(事件2,刚记下amount)->1100

1100->a线程提(事件3)->1000

1100->b线程存剩下一半(事件4)->1200

1200->b线程提(事件5)->1100

事件2,3,4之间出现问题。

对于这样的多个线程共享的资源我们必须进行同步,以避免一个线程的改动被另一个线程所覆盖。在我们这个程序中,Account中的amount是一个竞态条件,所以所有对amount的修改访问都要进行同步,我们将deposit()和withdraw()方法进行同步,修改为:

 public synchronized void deposit(float amt) {
        float tmp = amount;
        tmp += amt;

        try {
            Thread.sleep(1);//模拟其它处理所需要的时间,比如刷新数据库等
        } catch (InterruptedException e) {
            // ignore
        }

        amount = tmp;
    }

    public synchronized void withdraw(float amt) {
        float tmp = amount;
        tmp -= amt;

        try {
            Thread.sleep(1);//模拟其它处理所需要的时间,比如刷新数据库等
        } catch (InterruptedException e) {
            // ignore
        }

        amount = tmp;
    }

此时,再运行,我们就能够得到正确的结果了。Account中的getBalance()也访问了amount,为什么不对getBalance()同步呢?因为getBalance()并不会修改amount的值,所以,同时多个线程对它访问不会造成数据的混乱。

 

同步加锁的是对象,而不是代码。

因此,如果你的类中有一个同步方法,这个方法可以被两个不同的线程同时执行,只要每个线程自己创建一个的该类的实例即可。

 

参考下面的代码:

class Foo extends Thread
{
    private int val;
    public Foo(int v)
    {
        val = v;
    }
    public synchronized void printVal(int v)
    {
        while(true)
            System.out.println(v);
    }
    
    public void run()
    {
        printVal(val);
    }
}

class SyncTest
{
    public static void main(String args[])
    {
        Foo f1 = new Foo(1);
        f1.start();
        Foo f2 = new Foo(3);
        f2.start();
    }
}

运行SyncTest产生的输出是1和3交叉的。如果printVal是断面,你看到的输出只能是1或者只能是3而不能是两者同时出现。程序运行的结果证明两个线程都在并发的执行printVal方法,即使该方法是同步的并且由于是一个无限循环而没有终止。

类的同步:

要实现真正的断面,你必须同步一个全局对象或者对类进行同步。下面的代码给出了一个这样的范例。

class Foo extends Thread
{
    private int val;
    public Foo(int v)
    {
        val = v;
    }
    public void printVal(int v)
    {
        synchronized(Foo.class) {
            while(true)
                System.out.println(v);
        }
    }
    
    public void run()
    {
        printVal(val);
    }
}

 

上面的类不再对个别的类实例同步而是对类进行同步。对于类Foo而言,它只有唯一的类定义,两个线程在相同的锁上同步,因此只有一个线程可以执行printVal方法。

这个代码也可以通过对公共对象加锁。例如给Foo添加一个静态成员。两个方法都可以同步这个对象而达到线程安全。

下面笔者给出一个参考实现,给出同步公共对象的两种通常方法:

1、

class Foo extends Thread
{
    private int val;
    private static Object lock=new Object();
    public Foo(int v)
    {
        val = v;
    }
    
    public void printVal(int v)
    {
        synchronized(lock) {
            while(true)
                System.out.println(v);
        }
    }
    public void run()
    {
        printVal(val);
    }
}

 

上面的这个例子比原文给出的例子要好一些,因为原文中的加锁是针对类定义的。在有多个方法需要同步时,如果使用类作为锁,他们的并发粒度会太小,不如针对每个需要保护的成员变量 定义一个锁,这样并发粒度要大些。

2、

class Foo extends Thread
{
    private String name;
    private String val;
    public Foo(String name,String v)
    {
        this.name=name;
        val = v;
    }
    public void printVal()
    {
        synchronized(val) {
            while(true) System.out.println(name+val);
        }
    }
    public void run()
    {
        printVal();
    }
}

public class SyncMethodTest
{
    public static void main(String args[])
    {
        Foo f1 = new Foo("Foo 1:","printVal");
        f1.start();
        Foo f2 = new Foo("Foo 2:","printVal");
        f2.start();
    }
}

 

上面这个代码需要进行一些额外的说明,因为JVM有一种优化机制,因为String类型的对象是不可变的,因此当你使用””的形式引用字符串时,如果JVM发现内存已经有一个这样的对象,那么它就使用那个对象而不再生成一个新的String对象,这样是为了减小内存的使用。

上面的main方法其实等同于:

public static void main(String args[])
{
    String value="printVal";
    Foo f1 = new Foo("Foo 1:",value);
    f1.start();
    Foo f2 = new Foo("Foo 2:",value);
    f2.start();
}

 

总结:

1、synchronized关键字的作用域有二种:
1)是某个对象实例内,synchronized aMethod(){}可以防止多个线程同时访问这个对象的synchronized方法(如果一个对象有多个synchronized方法,只要一个线程访问了其中的一个synchronized方法,其它线程不能同时访问这个对象中任何一个synchronized方法)。这时,不同的对象实例的synchronized方法是不相干扰的。也就是说,其它线程照样可以同时访问相同类的另一个对象实例中的synchronized方法;
2)是某个类的范围,synchronized static aStaticMethod{}防止多个线程同时访问这个类中的synchronized static 方法。它可以对类的所有对象实例起作用。

2、除了方法前用synchronized关键字,synchronized关键字还可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问。用法是: synchronized(this){/*区块*/},它的作用域是当前对象;

3、synchronized关键字是不能继承的,也就是说,基类的方法synchronized f(){} 在继承类中并不自动是synchronized f(){},而是变成了f(){}。继承类需要你显式的指定它的某个方法为synchronized方法;

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

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

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

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

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

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

 

第二篇:

synchronized 关键字,它包括两种用法:synchronized 方法和 synchronized 块。
1. synchronized 方法:通过在方法声明中加入 synchronized关键字来声明 synchronized 方法。如:
public synchronized void accessVal(int newVal);
synchronized 方法控制对类成员变量的访问:每个类实例对应一把锁,每个 synchronized 方法都必须获得调用该方法的类实例的锁方能执行,否则所属线程阻塞,方法一旦执行,就独占该锁,直到从该方法返回时才将锁释放,此后被阻塞的线程方能获得该锁,重新进入可执行状态。这种机制确保了同一时刻对于每一个类实例,其所有声明为 synchronized 的成员函数中至多只有一个处于可执行状态(因为至多只有

一个能够获得该类实例对应的锁),从而有效避免了类成员变量的访问冲突(只要所有可能访问类成员变量的方法均被声明为 synchronized)。
在 Java 中,不光是类实例,每一个类也对应一把锁,这样我们也可将类的静态成员函数声明为 synchronized ,以控制其对类的静态成员变量的访问。
synchronized 方法的缺陷:若将一个大的方法声明为synchronized 将会大大影响效率,典型地,若将线程类的方法 run() 声明为synchronized ,由于在线程的整个生命期内它一直在运行,因此将导致它对本类任何 synchronized 方法的调用都永远不会成功。当然我们可

以通过将访问类成员变量的代码放到专门的方法中,将其声明为 synchronized ,并在主方法中调用来解决这一问题,但是 Java 为我们提供了更好的解决办法,那就是 synchronized 块。
2. synchronized 块:通过 synchronized关键字来声明synchronized 块。语法如下:
synchronized(syncObject) {
//允许访问控制的代码
}
synchronized 块是这样一个代码块,其中的代码必须获得对象 syncObject (如前所述,可以是类实例或类)的锁方能执行,具体机

制同前所述。由于可以针对任意代码块,且可任意指定上锁的对象,故灵活性较高。
对synchronized(this)的一些理解
一、当两个并发线程访问同一个对象object中的这个synchronized(this)同步代码块时,一个时间内只能有一个线程得到执行。另一个线

程必须等待当前线程执行完这个代码块以后才能执行该代码块。
二、然而,当一个线程访问object的一个synchronized(this)同步代码块时,另一个线程仍然可以访问该object中的非synchronized(this)同步代码块。
三、尤其关键的是,当一个线程访问object的一个synchronized(this)同步代码块时,其他线程对object中所有其它synchronized(this)同步代码块的访问将被阻塞。
四、第三个例子同样适用其它同步代码块。也就是说,当一个线程访问object的一个synchronized(this)同步代码块时,它就获得了这个object的对象锁。结果,其它线程对该object对象所有同步代码部分的访问都被暂时阻塞。
五、以上规则对其它对象锁同样适用
第三篇:

打个比方:一个object就像一个大房子,大门永远打开。房子里有 很多房间(也就是方法)。

这些房间有上锁的(synchronized方法), 和不上锁之分(普通方法)。房门口放着一把钥匙(key),这把钥匙可以打开所有上锁的房间。

另外我把所有想调用该对象方法的线程比喻成想进入这房子某个 房间的人。所有的东西就这么多了,下面我们看看这些东西之间如何作用的。

在此我们先来明确一下我们的前提条件。该对象至少有一个synchronized方法,否则这个key还有啥意义。当然也就不会有我们的这个主题了。

一个人想进入某间上了锁的房间,他来到房子门口,看见钥匙在那儿(说明暂时还没有其他人要使用上锁的 房间)。于是他走上去拿到了钥匙,并且按照自己 的计划使用那些房间。注意一点,他每次使用完一次上锁的房间后会马上把钥匙还回去。即使他要连续使用两间上锁的房间,中间他也要把钥匙还回去,再取回来。

因此,普通情况下钥匙的使用原则是:“随用随借,用完即还。”

这时其他人可以不受限制的使用那些不上锁的房间,一个人用一间可以,两个人用一间也可以,没限制。但是如果当某个人想要进入上锁的房间,他就要跑到大门口去看看了。有钥匙当然拿了就走,没有的话,就只能等了。

要是很多人在等这把钥匙,等钥匙还回来以后,谁会优先得到钥匙?Not guaranteed。象前面例子里那个想连续使用两个上锁房间的家伙,他中间还钥匙的时候如果还有其他人在等钥匙,那么没有任何保证这家伙能再次拿到。 (JAVA规范在很多地方都明确说明不保证,象Thread.sleep()休息后多久会返回运行,相同优先权的线程那个首先被执行,当要访问对象的锁被 释放后处于等待池的多个线程哪个会优先得到,等等。我想最终的决定权是在JVM,之所以不保证,就是因为JVM在做出上述决定的时候,绝不是简简单单根据 一个条件来做出判断,而是根据很多条。而由于判断条件太多,如果说出来可能会影响JAVA的推广,也可能是因为知识产权保护的原因吧。SUN给了个不保证 就混过去了。无可厚非。但我相信这些不确定,并非完全不确定。因为计算机这东西本身就是按指令运行的。即使看起来很随机的现象,其实都是有规律可寻。学过 计算机的都知道,计算机里随机数的学名是伪随机数,是人运用一定的方法写出来的,看上去随机罢了。另外,或许是因为要想弄的确定太费事,也没多大意义,所 以不确定就不确定了吧。)

再来看看同步代码块。和同步方法有小小的不同。

1.从尺寸上讲,同步代码块比同步方法小。你可以把同步代码块看成是没上锁房间里的一块用带锁的屏风隔开的空间。

2.同步代码块还可以人为的指定获得某个其它对象的key。就像是指定用哪一把钥匙才能开这个屏风的锁,你可以用本房的钥匙;你也可以指定

用另一个房子的钥匙才能开,这样的话,你要跑到另一栋房子那儿把那个钥匙拿来,并用那个房子的钥匙来打开这个房子的带锁的屏风。

记住你获得的那另一栋房子的钥匙,并不影响其他人进入那栋房子没有锁的房间。

为什么要使用同步代码块呢?我想应该是这样的:首先对程序来讲同步的部分很影响运行效率,而一个方法通常是先创建一些局部变

量,再对这些变量做一些 操作,如运算,显示等等;而同步所覆盖的代码越多,对效率的影响就越严重。因此我们通常尽量缩小其影响范围。

如何做?同步代码块。我们只把一个方法中该同 步的地方同步,比如运算。

另外,同步代码块可以指定钥匙这一特点有个额外的好处,是可以在一定时期内霸占某个对象的key。还记得前面说过普通情况下钥

匙的使用原则吗。现在不是普通情况了。你所取得的那把钥匙不是永远不还,而是在退出同步代码块时才还。

还用前面那个想连续用两个上锁房间的家伙打比方。怎样才能在用完一间以后,继续使用另一间呢。用同步代码块吧。先创建另外

一个线程,做一个同步代码 块,把那个代码块的锁指向这个房子的钥匙。然后启动那个线程。只要你能在进入那个代码块时抓到这房子的钥匙,你就可以一直保留到退出那个代码块。也就是说 你甚至可以对本房内所有上锁的房间遍历,甚至再sleep(10*60*1000),而房门口却还有1000个线程在等这把钥匙呢。很过瘾吧。

在此对sleep()方法和钥匙的关联性讲一下。一个线程在拿到key后,且没有完成同步的内容时,如果被强制sleep()了,那key还一直在 它那儿。直到它再次运行,做完所有同步内容,才会归还key。记住,那家伙只是干活干累了,去休息一下,他并没干完他要干的事。为了避免别人进入那个房间 把里面搞的一团糟,即使在睡觉的时候他也要把那唯一的钥匙戴在身上。

最后,也许有人会问,为什么要一把钥匙通开,而不是一个钥匙一个门呢?我想这纯粹是因为复杂性问题。一个钥匙一个门当然更安全,但是会牵扯好多问题。钥匙 的产生,保管,获得,归还等等。其复杂性有可能随同步方法的增加呈几何级数增加,严重影响效率。这也算是一个权衡的问题吧。为了增加一点点安全性,导致效 率大大降低,是多么不可取啊。

synchronized的一个简单例子

public class TextThread {

    public static void main(String[] args) {
        TxtThread tt = new TxtThread();
        new Thread(tt).start();
        new Thread(tt).start();
        new Thread(tt).start();
        new Thread(tt).start();
    }
}

class TxtThread implements Runnable {
    int num = 100;
    String str = new String();
    
    public void run() {
        synchronized (str) {
        while (num > 0) {
            try {
                Thread.sleep(1);
            } catch (Exception e) {
                e.getMessage();
            }
            System.out.println(Thread.currentThread().getName()+ "this is " + num--);
            }
        }
    }
}

上面的例子中为了制造一个时间差,也就是出错的机会,使用了Thread.sleep(10)

Java对多线程的支持与同步机制深受大家的喜爱,似乎看起来使用了synchronized关键字就可以轻松地解决多线程共享数据同步问题。到底如何?――还得对synchronized关键字的作用进行深入了解才可定论。

总的说来,synchronized关键字可以作为函数的修饰符,也可作为函数内的语句,也就是平时说的同步方法和同步语句块。如果再细的分类,synchronized可作用于instance变量、object reference(对象引用)、static函数和class literals(类名称字面常量)身上。

在进一步阐述之前,我们需要明确几点:

A.无论synchronized关键字加在方法上还是对象上,它取得的锁都是对象,而不是把一段代码或函数当作锁――而且同步方法很可能还会被其他线程的对象访问。

B.每个对象只有一个锁(lock)与之相关联。

C.实现同步是要很大的系统开销作为代价的,甚至可能造成死锁,所以尽量避免无谓的同步控制。

接着来讨论synchronized用到不同地方对代码产生的影响:

假设P1、P2是同一个类的不同对象,这个类中定义了以下几种情况的同步块或同步方法,P1、P2就都可以调用它们。

1. 把synchronized当作函数修饰符时,示例代码如下:

Public synchronized void methodAAA()

{

//….

}

这也就是同步方法,那这时synchronized锁定的是哪个对象呢?它锁定的是调用这个同步方法对象。也就是说,当一个对象P1在不同的线程中执行这个同步方法时,它们之间会形成互斥,达到同步的效果。但是这个对象所属的Class所产生的另一对象P2却可以任意调用这个被加了synchronized关键字的方法。

上边的示例代码等同于如下代码:

public void methodAAA()

{

synchronized (this)      // (1)

{

//…..

}

}

(1)处的this指的是什么呢?它指的就是调用这个方法的对象,如P1。可见同步方法实质是将synchronized作用于object reference。――那个拿到了P1对象锁的线程,才可以调用P1的同步方法,而对P2而言,P1这个锁与它毫不相干,程序也可能在这种情形下摆脱同步机制的控制,造成数据混乱:(

2.同步块,示例代码如下:

public void method3(SomeObject so)

{

synchronized(so)

{
//…..
}

}

这时,锁就是so这个对象,谁拿到这个锁谁就可以运行它所控制的那段代码。当有一个明确的对象作为锁时,就可以这样写程序,但当没有明确的对象作为锁,只是想让一段代码同步时,可以创建一个特殊的instance变量(它得是一个对象)来充当锁:

class Foo implements Runnable

{

private byte[] lock = new byte[0]; // 特殊的instance变量

Public void methodA()
{

synchronized(lock) { //… }

}

//…..

}

注:零长度的byte数组对象创建起来将比任何对象都经济――查看编译后的字节码:生成零长度的byte[]对象只需3条操作码,而Object lock= new Object()则需要7行操作码。

3.将synchronized作用于static 函数,示例代码如下:

Class Foo
{

public synchronized static void methodAAA()   // 同步的static 函数
{
//….
}

public void methodBBB()
{

synchronized(Foo.class)   // class literal(类名称字面常量)

}
}

代码中的methodBBB()方法是把class literal作为锁的情况,它和同步的static函数产生的效果是一样的,取得的锁很特别,是当前调用这个方法的对象所属的类(Class,而不再是由这个Class产生的某个具体对象了)。

记得在《Effective Java》一书中看到过将 Foo.class和 P1.getClass()用于作同步锁还不一样,不能用P1.getClass()来达到锁这个Class的目的。P1指的是由Foo类产生的对象。

可以推断:如果一个类中定义了一个synchronized的static函数A,也定义了一个synchronized 的instance函数B,那么这个类的同一对象Obj在多线程中分别访问A和B两个方法时,不会构成同步,因为它们的锁都不一样。A方法的锁是Obj这个对象,而B的锁是Obj所属的那个Class。

小结如下:

搞清楚synchronized锁定的是哪个对象,就能帮助我们设计更安全的多线程程序。

还有一些技巧可以让我们对共享资源的同步访问更加安全:

1. 定义private 的instance变量+它的 get方法,而不要定义public/protected的instance变量。如果将变量定义为public,对象在外界可以绕过同步方法的控制而直接取得它,并改动它。这也是JavaBean的标准实现方式之一。

2. 如果instance变量是一个对象,如数组或ArrayList什么的,那上述方法仍然不安全,因为当外界对象通过get方法拿到这个instance对象的引用后,又将其指向另一个对象,那么这个private变量也就变了,岂不是很危险。 这个时候就需要将get方法也加上synchronized同步,并且,只返回这个private对象的clone()――这样,调用端得到的就是对象副本的引用了。

Android应用程序窗口设计框架介绍

 

Android系统中,一个Activity对应一个应用程序窗口,任何一个Activity的启动都是由AMS服务和应用程序进程相互配合来完成的。AMS服务统一调度系统中所有进程的Activity启动,而每个Activity的启动过程则由其所属进程来完成。AMS服务通过realStartActivityLocked函数来通知应用程序进程启动某个Activity:

frameworksbaseservicesjavacomandroidserveram ActivityStack.java

  1. final boolean realStartActivityLocked(ActivityRecord r,
  2.         ProcessRecord app, boolean andResume, boolean checkConfig)
  3.         throws RemoteException {
  4.     …
  5.     //系统参数发送变化,通知Activity
  6.     if (checkConfig) {
  7.         ①Configuration config = mService.mWindowManager.updateOrientationFromAppTokens(mService.mConfiguration,
  8.                 r.mayFreezeScreenLocked(app) ? r.appToken : null);
  9.         mService.updateConfigurationLocked(config, r, falsefalse);
  10.     }
  11.     //将进程描述符设置到启动的Activity描述符中
  12.     r.app = app;
  13.     app.waitingToKill = null;
  14.     //将启动的Activity添加到进程启动的Activity列表中
  15.     int idx = app.activities.indexOf(r);
  16.     if (idx < 0) {
  17.         app.activities.add(r);
  18.     }
  19.     mService.updateLruProcessLocked(app, truetrue);
  20.     try {
  21.         …
  22.         //通知应用程序进程加载Activity
  23.         ②app.thread.scheduleLaunchActivity(new Intent(r.intent), r.appToken,
  24.                 System.identityHashCode(r), r.info,
  25.                 new Configuration(mService.mConfiguration),
  26.                 r.compat, r.icicle, results, newIntents, !andResume,
  27.                 mService.isNextTransitionForward(), profileFile, profileFd,
  28.                 profileAutoStop);
  29.         …
  30.     } catch (RemoteException e) {
  31.         …
  32.     }
  33.     if (mMainStack) {
  34.         mService.startSetupActivityLocked();
  35.     }
  36.     return true;
  37. }

AMS通过realStartActivityLocked函数来调度应用程序进程启动一个Activity,参数r为即将启动的Activity在AMS服务中的描述符,参数app为Activity运行所在的应用程序进程在AMS服务中的描述符。函数通过IApplicationThread代理对象ApplicationThreadProxy通知应用程序进程启动r对应的Activity,应用程序进程完成Activity的加载等准备工作后,AMS最后启动该Activity。启动Activity的创建等工作是在应用程序进程中完成的,AMS是通过IApplicationThread接口和应用程序进程通信的。r.appToken
在AMS服务端的类型为Token,是IApplicationToken的Binder本地对象。

frameworksbasecorejavaandroidapp ActivityThread.java

  1. public final void scheduleLaunchActivity(Intent intent, IBinder token, int ident,
  2.         ActivityInfo info, Configuration curConfig, CompatibilityInfo compatInfo,
  3.         Bundle state, List<ResultInfo> pendingResults,
  4.         List<Intent> pendingNewIntents, boolean notResumed, boolean isForward,
  5.         String profileName, ParcelFileDescriptor profileFd, boolean autoStopProfiler) {
  6.     //将AMS服务传过来的参数封装为ActivityClientRecord对象
  7.     ActivityClientRecord r = new ActivityClientRecord();
  8.     r.token = token;
  9.     r.ident = ident;
  10.     r.intent = intent;
  11.     r.activityInfo = info;
  12.     r.compatInfo = compatInfo;
  13.     r.state = state;
  14.     r.pendingResults = pendingResults;
  15.     r.pendingIntents = pendingNewIntents;
  16.     r.startsNotResumed = notResumed;
  17.     r.isForward = isForward;
  18.     r.profileFile = profileName;
  19.     r.profileFd = profileFd;
  20.     r.autoStopProfiler = autoStopProfiler;
  21.     updatePendingConfiguration(curConfig);
  22.     //使用异步消息方式实现Activity的启动
  23.     queueOrSendMessage(H.LAUNCH_ACTIVITY, r);
  24. }

参数token从AMS服务端经过Binder传输到应用程序进程后,变为IApplicationToken的Binder代理对象,类型为IApplicationToken.Proxy,这是因为AMS和应用程序运行在不同的进程中。

通过queueOrSendMessage函数将Binder跨进程调用转换为应用程序进程中的异步消息处理

frameworksbasecorejavaandroidapp ActivityThread.java

  1. private class H extends Handler {
  2.  public void handleMessage(Message msg) {
  3.     switch (msg.what) {
  4.             case LAUNCH_ACTIVITY: {
  5.                 Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, “activityStart”);
  6.                 ActivityClientRecord r = (ActivityClientRecord)msg.obj;
  7.                 r.packageInfo = getPackageInfoNoCheck(r.activityInfo.applicationInfo, r.compatInfo);
  8.                 handleLaunchActivity(r, null);
  9.                 Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
  10.             } break;
  11.         }
  12.     }
  13. }

LAUNCH_ACTIVITY消息在应用程序主线程消息循环中得到处理,应用程序通过handleLaunchActivity函数来启动Activity。到此AMS服务就完成了Activity的调度任务,将Activity的启动过程完全交给了应用程序进程来完成。

frameworksbasecorejavaandroidapp ActivityThread.java

  1. private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent) {
  2.     //主线程空闲时会定时执行垃圾回收,主线程当前要完成启动Activity的任务,因此这里先暂停GC
  3.     unscheduleGcIdler();
  4.     if (r.profileFd != null) {
  5.         mProfiler.setProfiler(r.profileFile, r.profileFd);
  6.         mProfiler.startProfiling();
  7.         mProfiler.autoStopProfiler = r.autoStopProfiler;
  8.     }
  9.     // Make sure we are running with the most recent config.
  10.     ①handleConfigurationChanged(nullnull);
  11.     //创建Activity
  12.     ②Activity a = performLaunchActivity(r, customIntent);
  13.     if (a != null) {
  14.         r.createdConfig = new Configuration(mConfiguration);
  15.         Bundle oldState = r.state;
  16.         //启动Activity
  17.         ③handleResumeActivity(r.token, false, r.isForward);
  18.         …
  19.     }else{
  20.         …
  21.     }
  22. }

performLaunchActivity

应用程序进程通过performLaunchActivity函数将即将要启动的Activity加载到当前进程空间来,同时为启动Activity做准备。

frameworksbasecorejavaandroidapp ActivityThread.java

  1. private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
  2.     ActivityInfo aInfo = r.activityInfo;
  3.     if (r.packageInfo == null) {
  4.         //通过Activity所在的应用程序信息及该Activity对应的CompatibilityInfo信息从PMS服务中查询当前Activity的包信息
  5.         r.packageInfo = getPackageInfo(aInfo.applicationInfo, r.compatInfo,Context.CONTEXT_INCLUDE_CODE);
  6.     }
  7.     //获取当前Activity的组件信息
  8.     ComponentName component = r.intent.getComponent();
  9.     if (component == null) {
  10.         component = r.intent.resolveActivity(mInitialApplication.getPackageManager());
  11.         r.intent.setComponent(component);
  12.     }
  13.     if (r.activityInfo.targetActivity != null) {
  14.         //packageName为启动Activity的包名,targetActivity为Activity的类名
  15.         component = new ComponentName(r.activityInfo.packageName,
  16.                 r.activityInfo.targetActivity);
  17.     }
  18.     //通过类反射方式加载即将启动的Activity
  19.     Activity activity = null;
  20.     try {
  21.         java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
  22.         ①activity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent);
  23.         StrictMode.incrementExpectedActivityCount(activity.getClass());
  24.         r.intent.setExtrasClassLoader(cl);
  25.         if (r.state != null) {
  26.             r.state.setClassLoader(cl);
  27.         }
  28.     } catch (Exception e) {
  29.         …
  30.     }
  31.     try {
  32.         //通过单例模式为应用程序进程创建Application对象
  33.         ②Application app = r.packageInfo.makeApplication(false, mInstrumentation);
  34.         if (activity != null) {
  35.             //为当前Activity创建上下文对象ContextImpl
  36.             ContextImpl appContext = new ContextImpl();
  37.             //上下文初始化
  38.             ③appContext.init(r.packageInfo, r.token, this);
  39.             appContext.setOuterContext(activity);
  40.             CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());
  41.             …
  42.             Configuration config = new Configuration(mCompatConfiguration);
  43.             //将当前启动的Activity和上下文ContextImpl、Application绑定
  44.             ④activity.attach(appContext, this, getInstrumentation(), r.token,
  45.                     r.ident, app, r.intent, r.activityInfo, title, r.parent,
  46.                     r.embeddedID, r.lastNonConfigurationInstances, config);
  47.             …
  48.             //调用Activity的OnCreate函数
  49.             ⑤mInstrumentation.callActivityOnCreate(activity, r.state);
  50.             …
  51.             //将Activity保存到ActivityClientRecord中,ActivityClientRecord为Activity在应用程序进程中的描述符
  52.             r.activity = activity;
  53.             …
  54.         }
  55.         r.paused = true;
  56.         //ActivityThread的成员变量mActivities保存了当前应用程序进程中的所有Activity的描述符
  57.         mActivities.put(r.token, r);
  58.     } catch (SuperNotCalledException e) {
  59.         …
  60.     }
  61.     return activity;
  62. }

在该函数中,首先通过PMS服务查找到即将启动的Activity的包名信息,然后通过类反射方式创建一个该Activity实例,同时为应用程序启动的每一个Activity创建一个LoadedApk实例对象,应用程序进程中创建的所有LoadedApk对象保存在ActivityThread的成员变量mPackages中。接着通过LoadedApk对象的makeApplication函数,使用单例模式创建Application对象,因此在android应用程序进程中有且只有一个Application实例。然后为当前启动的Activity创建一个ContextImpl上下文对象,并初始化该上下文,到此我们可以知道,启动一个Activity需要以下对象:

1)      XXActivity对象,需要启动的Activity;

2)      LoadedApk对象,每个启动的Activity都拥有属于自身的LoadedApk对象;

3)      ContextImpl对象,每个启动的Activity都拥有属于自身的ContextImpl对象;

4)      Application对象,应用程序进程中有且只有一个实例,和Activity是一对多的关系;

加载Activity类

  1. public Activity newActivity(ClassLoader cl, String className,
  2.         Intent intent)
  3.         throws InstantiationException, IllegalAccessException,
  4.         ClassNotFoundException {
  5.     return (Activity)cl.loadClass(className).newInstance();
  6. }

这里通过类反射的方式来加载要启动的Activity实例对象。

LoadedApk构造过程

首先介绍一下LoadedApk对象的构造过程:

frameworksbasecorejavaandroidapp ActivityThread.java

  1. public final LoadedApk getPackageInfo(String packageName, CompatibilityInfo compatInfo,
  2.         int flags) {
  3.     synchronized (mPackages) {
  4.         //通过Activity的包名从对应的成员变量中查找LoadedApk对象
  5.         WeakReference<LoadedApk> ref;
  6.         if ((flags&Context.CONTEXT_INCLUDE_CODE) != 0) {
  7.             ref = mPackages.get(packageName);
  8.         } else {
  9.             ref = mResourcePackages.get(packageName);
  10.         }
  11.         LoadedApk packageInfo = ref != null ? ref.get() : null;
  12.         if (packageInfo != null && (packageInfo.mResources == null
  13.                 || packageInfo.mResources.getAssets().isUpToDate())) {
  14.             …
  15.             return packageInfo;
  16.         }
  17.     }
  18.     //如果没有,则为当前Activity创建对应的LoadedApk对象
  19.     ApplicationInfo ai = null;
  20.     try {
  21.         //通过包名在PMS服务中查找应用程序信息
  22.         ai = getPackageManager().getApplicationInfo(packageName,
  23.                 PackageManager.GET_SHARED_LIBRARY_FILES, UserId.myUserId());
  24.     } catch (RemoteException e) {
  25.         // Ignore
  26.     }
  27.     //使用另一个重载函数创建LoadedApk对象
  28.     if (ai != null) {
  29.         return getPackageInfo(ai, compatInfo, flags);
  30.     }
  31.     return null;
  32. }

 

  1. public final LoadedApk getPackageInfo(ApplicationInfo ai, CompatibilityInfo compatInfo,
  2.         int flags) {
  3.     boolean includeCode = (flags&Context.CONTEXT_INCLUDE_CODE) != 0;
  4.     boolean securityViolation = includeCode && ai.uid != 0
  5.             && ai.uid != Process.SYSTEM_UID && (mBoundApplication != null
  6.                     ? !UserId.isSameApp(ai.uid, mBoundApplication.appInfo.uid)
  7.                     : true);
  8.     if ((flags&(Context.CONTEXT_INCLUDE_CODE|Context.CONTEXT_IGNORE_SECURITY))
  9.             == Context.CONTEXT_INCLUDE_CODE) {
  10.         …
  11.     }
  12.     return getPackageInfo(ai, compatInfo, null, securityViolation, includeCode);
  13. }

 

  1. private LoadedApk getPackageInfo(ApplicationInfo aInfo, CompatibilityInfo compatInfo,
  2.         ClassLoader baseLoader, boolean securityViolation, boolean includeCode) {
  3.     //再次从对应的成员变量中查找LoadedApk实例
  4.     synchronized (mPackages) {
  5.         WeakReference<LoadedApk> ref;
  6.         if (includeCode) {
  7.             ref = mPackages.get(aInfo.packageName);
  8.         } else {
  9.             ref = mResourcePackages.get(aInfo.packageName);
  10.         }
  11.         LoadedApk packageInfo = ref != null ? ref.get() : null;
  12.         if (packageInfo == null || (packageInfo.mResources != null
  13.                 && !packageInfo.mResources.getAssets().isUpToDate())) {
  14.             …
  15.             //构造一个LoadedApk对象
  16.             packageInfo =new LoadedApk(this, aInfo, compatInfo, this, baseLoader,
  17.                         securityViolation, includeCode &&
  18.                         (aInfo.flags&ApplicationInfo.FLAG_HAS_CODE) != 0);
  19.             //保存LoadedApk实例到ActivityThread的相应成员变量中
  20.             if (includeCode) {
  21.                 mPackages.put(aInfo.packageName,
  22.                         new WeakReference<LoadedApk>(packageInfo));
  23.             } else {
  24.                 mResourcePackages.put(aInfo.packageName,
  25.                         new WeakReference<LoadedApk>(packageInfo));
  26.             }
  27.         }
  28.         return packageInfo;
  29.     }
  30. }

 

frameworksbasecorejavaandroidappLoadedApk.java

  1. public LoadedApk(ActivityThread activityThread, ApplicationInfo aInfo,
  2.         CompatibilityInfo compatInfo,
  3.         ActivityThread mainThread, ClassLoader baseLoader,
  4.         boolean securityViolation, boolean includeCode) {
  5.     mActivityThread = activityThread;
  6.     mApplicationInfo = aInfo;
  7.     mPackageName = aInfo.packageName;
  8.     mAppDir = aInfo.sourceDir;
  9.     final int myUid = Process.myUid();
  10.     mResDir = aInfo.uid == myUid ? aInfo.sourceDir
  11.             : aInfo.publicSourceDir;
  12.     if (!UserId.isSameUser(aInfo.uid, myUid) && !Process.isIsolated()) {
  13.         aInfo.dataDir = PackageManager.getDataDirForUser(UserId.getUserId(myUid),
  14.                 mPackageName);
  15.     }
  16.     mSharedLibraries = aInfo.sharedLibraryFiles;
  17.     mDataDir = aInfo.dataDir;
  18.     mDataDirFile = mDataDir != null ? new File(mDataDir) : null;
  19.     mLibDir = aInfo.nativeLibraryDir;
  20.     mBaseClassLoader = baseLoader;
  21.     mSecurityViolation = securityViolation;
  22.     mIncludeCode = includeCode;
  23.     mCompatibilityInfo.set(compatInfo);
  24.     if (mAppDir == null) {
  25.         //为应用程序进程创建一个ContextImpl上下文
  26.         if (ActivityThread.mSystemContext == null) {
  27.             ActivityThread.mSystemContext =
  28.                 ContextImpl.createSystemContext(mainThread);
  29.             ActivityThread.mSystemContext.getResources().updateConfiguration(
  30.                      mainThread.getConfiguration(),
  31.                      mainThread.getDisplayMetricsLocked(compatInfo, false),
  32.                      compatInfo);
  33.         }
  34.         mClassLoader = ActivityThread.mSystemContext.getClassLoader();
  35.         mResources = ActivityThread.mSystemContext.getResources();
  36.     }
  37. }

从以上LoadedApk的构造函数可以看出,LoadedApk类记录了Activity运行所在的ActivityThread、Activity所在的应用程序信息、Activity的包名、Activity的资源路径、Activity的库路径、Activity的数据存储路径、类加载器和应用程序所使用的资源等信息。

Application构造过程

当Activity为应用程序进程启动的第一个Activity,因此需要构造一个Application对象

frameworksbasecorejavaandroidappLoadedApk.java

  1. public Application makeApplication(boolean forceDefaultAppClass,
  2.         Instrumentation instrumentation) {
  3.     //在应用程序进程空间以单例模式创建Application对象
  4.     if (mApplication != null) {
  5.         return mApplication;
  6.     }
  7.     Application app = null;
  8.     //得到应用程序的Application类名
  9.     String appClass = mApplicationInfo.className;
  10.     //如果应用程序没用重写Application,则使用Android默认的Application类
  11.     if (forceDefaultAppClass || (appClass == null)) {
  12.         appClass = “android.app.Application”;
  13.     }
  14.     try {
  15.         java.lang.ClassLoader cl = getClassLoader();
  16.         //为Application实例创建一个上下文对象ContextImpl
  17.         ①ContextImpl appContext = new ContextImpl();
  18.         //初始化上下文
  19.         ②appContext.init(thisnull, mActivityThread);
  20.         //创建Application实例对象
  21.         ③app = mActivityThread.mInstrumentation.newApplication(
  22.                 cl, appClass, appContext);
  23.         appContext.setOuterContext(app);
  24.     } catch (Exception e) {
  25.         …
  26.     }
  27.     mActivityThread.mAllApplications.add(app);
  28.     mApplication = app;
  29.     if (instrumentation != null) {
  30.         try {
  31.             //调用Application的OnCreate函数
  32.             ④instrumentation.callApplicationOnCreate(app);
  33.         } catch (Exception e) {
  34.             …
  35.         }
  36.     }
  37.     return app;
  38. }

在应用程序开发过程中,当我们重写了Application类后,应用程序加载运行的是我们定义的Application类,否则就加载运行默认的Application类。从Application对象的构造过程就可以解释为什么应用程序启动后首先执行的是Application的OnCreate函数。在实例化Application对象时,同样创建并初始化了一个ContextImpl上下文对象。

ContextImpl构造过程

前面我们介绍了,每一个Activity拥有一个上下文对象ContextImpl,每一个Application对象也拥有一个ContextImpl上下文对象,那么ContextImpl对象又是如何构造的呢?

frameworksbasecorejavaandroidapp ContextImpl.java

  1. ContextImpl() {
  2.     mOuterContext = this;
  3. }

ContextImpl的构造过程什么也没干,通过调用ContextImpl的init函数进行初始化

  1. final void init(LoadedApk packageInfo,IBinder activityToken, ActivityThread mainThread) {
  2.     init(packageInfo, activityToken, mainThread, nullnull);
  3. }

 

  1. final void init(LoadedApk packageInfo,IBinder activityToken, ActivityThread mainThread,
  2.             Resources container, String basePackageName) {
  3.     mPackageInfo = packageInfo;
  4.     mBasePackageName = basePackageName != null ? basePackageName : packageInfo.mPackageName;
  5.     mResources = mPackageInfo.getResources(mainThread);
  6.     if (mResources != null && container != null
  7.             && container.getCompatibilityInfo().applicationScale !=
  8.                     mResources.getCompatibilityInfo().applicationScale) {
  9.         mResources = mainThread.getTopLevelResources(
  10.                 mPackageInfo.getResDir(), container.getCompatibilityInfo());
  11.     }
  12.     mMainThread = mainThread;
  13.     mContentResolver = new ApplicationContentResolver(this, mainThread);
  14.     setActivityToken(activityToken);
  15. }

从ContextImpl的初始化函数中可以知道,ContextImpl记录了应用程序的包名信息、应用程序的资源信息、应用程序的主线程、ContentResolver及Activity对应的IApplicationToken.Proxy,当然对应Application对象所拥有的ContextImpl上下文就没有对应的Token了。通过前面的分析我们可以知道各个对象之间的关系:

对象Attach过程

Activity所需要的对象都创建好了,就需要将Activity和Application对象、ContextImpl对象绑定在一起。

frameworksbasecorejavaandroidapp Activity.java

  1. final void attach(Context context, ActivityThread aThread, Instrumentation instr, IBinder token,
  2.         Application application, Intent intent, ActivityInfo info, CharSequence title,
  3.         Activity parent, String id, NonConfigurationInstances lastNonConfigurationInstances,
  4.         Configuration config) {
  5.     attach(context, aThread, instr, token, 0, application, intent, info, title, parent, id,
  6.         lastNonConfigurationInstances, config);
  7. }

context:Activity的上下文对象,就是前面创建的ContextImpl对象;

aThread:Activity运行所在的主线程描述符ActivityThread;

instr:用于监控Activity运行状态的Instrumentation对象;

token:用于和AMS服务通信的IApplicationToken.Proxy代理对象;

application:Activity运行所在进程的Application对象;

parent:启动当前Activity的Activity;

  1. final void attach(Context context, ActivityThread aThread,
  2.         Instrumentation instr, IBinder token, int ident,
  3.         Application application, Intent intent, ActivityInfo info,
  4.         CharSequence title, Activity parent, String id,
  5.         NonConfigurationInstances lastNonConfigurationInstances,
  6.         Configuration config) {
  7.     //将上下文对象ContextImpl保存到Activity的成员变量中
  8.     attachBaseContext(context);
  9.     //每个Activity都拥有一个FragmentManager,这里就是将当前Activity设置到FragmentManager中管理
  10.     mFragments.attachActivity(this);
  11.     //创建窗口对象
  12.     ①mWindow = PolicyManager.makeNewWindow(this);
  13.     mWindow.setCallback(this);
  14.     mWindow.getLayoutInflater().setPrivateFactory(this);
  15.     if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
  16.         mWindow.setSoftInputMode(info.softInputMode);
  17.     }
  18.     if (info.uiOptions != 0) {
  19.         mWindow.setUiOptions(info.uiOptions);
  20.     }
  21.     //记录应用程序的UI线程
  22.     mUiThread = Thread.currentThread();
  23.     //记录应用程序的ActivityThread对象
  24.     mMainThread = aThread;
  25.     mInstrumentation = instr;
  26.     mToken = token;
  27.     mIdent = ident;
  28.     mApplication = application;
  29.     mIntent = intent;
  30.     mComponent = intent.getComponent();
  31.     mActivityInfo = info;
  32.     mTitle = title;
  33.     mParent = parent;
  34.     mEmbeddedID = id;
  35.     mLastNonConfigurationInstances = lastNonConfigurationInstances;
  36.     //为Activity所在的窗口创建窗口管理器
  37.     ②mWindow.setWindowManager(null, mToken, mComponent.flattenToString(),
  38.             (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
  39.     if (mParent != null) {
  40.         mWindow.setContainer(mParent.getWindow());
  41.     }
  42.     mWindowManager = mWindow.getWindowManager();
  43.     mCurrentConfig = config;
  44. }

在该attach函数中主要做了以下几件事:

1)        将Activity设置到FragmentManager中;

2)        根据参数初始化Activity的成员变量;

3)        为Activity创建窗口Window对象;

4)        为Window创建窗口管理器;

到此为止应用程序进程为启动的Activity对象创建了以下不同的实例对象,它们之间的关系如下:

应用程序窗口创建过程

frameworksbasecorejavacomandroidinternalpolicy PolicyManager.java

  1. public static Window makeNewWindow(Context context) {
  2.     return sPolicy.makeNewWindow(context);
  3. }

通过Policy类的makeNewWindow函数来创建一个应用程序窗口

  1. private static final String POLICY_IMPL_CLASS_NAME =
  2.         “com.android.internal.policy.impl.Policy”;
  3. private static final IPolicy sPolicy;
  4. static {
  5.     try {
  6.         Class policyClass = Class.forName(POLICY_IMPL_CLASS_NAME);
  7.         sPolicy = (IPolicy)policyClass.newInstance();
  8.     } catch (ClassNotFoundException ex) {
  9.         …
  10.     }
  11. }

frameworksbasepolicysrccomandroidinternalpolicyimpl Policy.java

  1. public Window makeNewWindow(Context context) {
  2.     return new PhoneWindow(context);
  3. }

应用程序窗口的创建过程其实就是构造一个PhoneWindow对象。PhoneWindow类是通过静态方式加载到应用程序进程空间的。

  1. private static final String[] preload_classes = {
  2.     “com.android.internal.policy.impl.PhoneLayoutInflater”,
  3.     “com.android.internal.policy.impl.PhoneWindow”,
  4.     “com.android.internal.policy.impl.PhoneWindow$1”,
  5.     “com.android.internal.policy.impl.PhoneWindow$ContextMenuCallback”,
  6.     “com.android.internal.policy.impl.PhoneWindow$DecorView”,
  7.     “com.android.internal.policy.impl.PhoneWindow$PanelFeatureState”,
  8.     “com.android.internal.policy.impl.PhoneWindow$PanelFeatureState$SavedState”,
  9. };
  1. static {
  2.     for (String s : preload_classes) {
  3.         try {
  4.             Class.forName(s);
  5.         } catch (ClassNotFoundException ex) {
  6.             Log.e(TAG, “Could not preload class for phone policy: “ + s);
  7.         }
  8.     }
  9. }

PhoneWindow的构造过程

  1. public PhoneWindow(Context context) {
  2.     super(context);
  3.     mAlternativePanelStyle=getContext().getResources().getBoolean(com.android.internal.R.bool.config_alternativePanelStyle);
  4.     mLayoutInflater = LayoutInflater.from(context);
  5. }

构造过程比较简单,只是得到布局加载服务对象。

窗口管理器创建过程

通过前面的分析我们可以知道,在Activity启动过程中,会为Activity创建一个窗口对象PhoneWindow,应用程序有了窗口那就需要有一个窗口管理器来管理这些窗口,因此在Activity启动过程中还会创建一个WindowManager对象。

frameworksbasecorejavaandroidview Window.java

  1. public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
  2.         boolean hardwareAccelerated) {
  3.     mAppToken = appToken;// IApplicationToken.Proxy代理对象
  4.     mAppName = appName;
  5.     //得到WindowManagerImpl实例,
  6.     if (wm == null) {
  7.         wm = WindowManagerImpl.getDefault();
  8.     }
  9.     //为每个启动的Activity创建一个轻量级的窗口管理器LocalWindowManager
  10.     mWindowManager = new LocalWindowManager(wm, hardwareAccelerated);
  11. }

WindowManagerImpl为重量级的窗口管理器,应用程序进程中有且只有一个WindowManagerImpl实例,它管理了应用程序进程中创建的所有PhoneWindow窗口。Activity并没有直接引用WindowManagerImpl实例,Android系统为每一个启动的Activity创建了一个轻量级的窗口管理器LocalWindowManager,每个Activity通过LocalWindowManager来访问WindowManagerImpl,它们三者之间的关系如下图所示:

WindowManagerImpl以单例模式创建,应用程序进程中有且只有一个WindowManagerImpl实例

frameworksbasecorejavaandroidview WindowManagerImpl.java

  1. private final static WindowManagerImpl sWindowManager = new WindowManagerImpl();
  2. public static WindowManagerImpl getDefault() {
  3.     return sWindowManager;
  4. }

应用程序进程会为每一个Activity创建一个LocalWindowManager实例对象

frameworksbasecorejavaandroidview Window.java

  1. LocalWindowManager(WindowManager wm, boolean hardwareAccelerated) {
  2.     super(wm, getCompatInfo(mContext));
  3.     mHardwareAccelerated = hardwareAccelerated ||
  4.             SystemProperties.getBoolean(PROPERTY_HARDWARE_UI, false);
  5. }

frameworksbasecorejavaandroidview WindowManagerImpl.java

  1. CompatModeWrapper(WindowManager wm, CompatibilityInfoHolder ci) {
  2.     mWindowManager = wm instanceof CompatModeWrapper
  3.             ? ((CompatModeWrapper)wm).mWindowManager : (WindowManagerImpl)wm;
  4.     if (ci == null) {
  5.         mDefaultDisplay = mWindowManager.getDefaultDisplay();
  6.     } else {
  7.         mDefaultDisplay = Display.createCompatibleDisplay(
  8.                 mWindowManager.getDefaultDisplay().getDisplayId(), ci);
  9.     }
  10.     mCompatibilityInfo = ci;
  11. }

  1. public Display getDefaultDisplay() {
  2.     return new Display(Display.DEFAULT_DISPLAY, null);
  3. }

frameworksbasecorejavaandroidviewDisplay.java

  1. Display(int display, CompatibilityInfoHolder compatInfo) {
  2.     synchronized (sStaticInit) {
  3.         if (!sInitialized) {
  4.             nativeClassInit();
  5.             sInitialized = true;
  6.         }
  7.     }
  8.     mCompatibilityInfo = compatInfo != null ? compatInfo : new CompatibilityInfoHolder();
  9.     mDisplay = display;
  10.     init(display);
  11. }

构造Display对象时需要初始化该对象。

frameworksbasecorejniandroid_view_Display.cpp

  1. static void android_view_Display_init(
  2.         JNIEnv* env, jobject clazz, jint dpy)
  3. {
  4.     DisplayInfo info;
  5.     if (headless) {
  6.         // initialize dummy display with reasonable values
  7.         info.pixelFormatInfo.format = 1// RGB_8888
  8.         info.fps = 60;
  9.         info.density = 160;
  10.         info.xdpi = 160;
  11.         info.ydpi = 160;
  12.     } else {
  13.         status_t err = SurfaceComposerClient::getDisplayInfo(DisplayID(dpy), &info);
  14.         if (err < 0) {
  15.             jniThrowException(env, “java/lang/IllegalArgumentException”, NULL);
  16.             return;
  17.         }
  18.     }
  19.     env->SetIntField(clazz, offsets.pixelFormat,info.pixelFormatInfo.format);
  20.     env->SetFloatField(clazz, offsets.fps,      info.fps);
  21.     env->SetFloatField(clazz, offsets.density,  info.density);
  22.     env->SetFloatField(clazz, offsets.xdpi,     info.xdpi);
  23.     env->SetFloatField(clazz, offsets.ydpi,     info.ydpi);
  24. }

Display的初始化过程很简单,就是通过SurfaceComposerClient请求SurfaceFlinger得到显示屏的基本信息。

frameworksnativelibsgui SurfaceComposerClient.cpp

  1. status_t SurfaceComposerClient::getDisplayInfo(
  2.         DisplayID dpy, DisplayInfo* info)
  3. {
  4.     if (uint32_t(dpy)>=NUM_DISPLAY_MAX)
  5.         return BAD_VALUE;
  6.     volatile surface_flinger_cblk_t const * cblk = get_cblk();
  7.     volatile display_cblk_t const * dcblk = cblk->displays + dpy;
  8.     info->w              = dcblk->w;
  9.     info->h              = dcblk->h;
  10.     info->orientation      = dcblk->orientation;
  11.     info->xdpi           = dcblk->xdpi;
  12.     info->ydpi           = dcblk->ydpi;
  13.     info->fps            = dcblk->fps;
  14.     info->density        = dcblk->density;
  15.     return getPixelFormatInfo(dcblk->format, &(info->pixelFormatInfo));
  16. }

我们知道在SurfaceFlinger启动过程中,创建了一块匿名共享内存来保存显示屏的基本信息,这里就是通过访问这块匿名共享内存来读取显示屏信息。到此一个Activity所需要的窗口对象就创建完成了,在应用程序窗口的创建过程中一共创建了以下几个对象:

Activity视图对象的创建过程

在Activity的attach函数中完成应用程序窗口的创建后,通过Instrumentation回调Activity的OnCreate函数来为当前Activity加载布局文件,进一步创建视图对象。

frameworksbasecorejavaandroidappInstrumentation.java

  1. public void callActivityOnCreate(Activity activity, Bundle icicle) {
  2.     …
  3.     activity.performCreate(icicle);
  4.     …
  5. }

frameworksbasecorejavaandroidappActivity.java

  1. final void performCreate(Bundle icicle) {
  2.     onCreate(icicle);
  3.     mVisibleFromClient = !mWindow.getWindowStyle().getBoolean(
  4.             com.android.internal.R.styleable.Window_windowNoDisplay, false);
  5.     mFragments.dispatchActivityCreated();
  6. }

我们知道在应用程序开发中,需要重写Activity的OnCreate函数:

Packagesappsxxxsrccomxxx xxxActivity.java

  1. public void onCreate(Bundle savedInstanceState) {
  2.     super.onCreate(savedInstanceState);
  3.     setContentView(R.layout.main_activity);
  4.     …
  5. }

在OnCreate函数中通过setContentView来设置Activity的布局文件,就是生成该Activity的所有视图对象。

frameworksbasecorejavaandroidappActivity.java

  1. public void setContentView(View view, ViewGroup.LayoutParams params) {
  2.     getWindow().setContentView(view, params);
  3.     //初始化动作条
  4.     initActionBar();
  5. }

getWindow()函数得到前面创建的窗口对象PhoneWindow,通过PhoneWindow来设置Activity的视图。

frameworksbasepolicysrccomandroidinternalpolicyimplPhoneWindow.java

  1. public void setContentView(int layoutResID) {
  2.     //如果窗口顶级视图对象为空,则创建窗口视图对象
  3.     if (mContentParent == null) {
  4.         installDecor();
  5.     } else {//否则只是移除该视图对象中的其他视图
  6.         mContentParent.removeAllViews();
  7.     }
  8.     //加载布局文件,并将布局文件中的所有视图对象添加到mContentParent容器中
  9.     mLayoutInflater.inflate(layoutResID, mContentParent);
  10.     final Callback cb = getCallback();
  11.     if (cb != null && !isDestroyed()) {
  12.         cb.onContentChanged();
  13.     }
  14. }

PhoneWindow的成员变量mContentParent的类型为ViewGroup,是窗口内容存放的地方

frameworksbasepolicysrccomandroidinternalpolicyimplPhoneWindow.java

  1. private void installDecor() {
  2.     if (mDecor == null) {
  3.         ①mDecor = generateDecor();
  4.         mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
  5.         mDecor.setIsRootNamespace(true);
  6.         if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
  7.             mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
  8.         }
  9.     }
  10.     if (mContentParent == null) {
  11.         ②mContentParent = generateLayout(mDecor);
  12.         mDecor.makeOptionalFitsSystemWindows();
  13.         //应用程序窗口标题栏
  14.         mTitleView = (TextView)findViewById(com.android.internal.R.id.title);
  15.         if (mTitleView != null) {
  16.             …
  17.         } else {
  18.             //应用程序窗口动作条
  19.             mActionBar = (ActionBarView) findViewById(com.android.internal.R.id.action_bar);
  20.             if (mActionBar != null) {
  21.                 …
  22.             }
  23.         }
  24.     }
  25. }

通过函数generateDecor()来创建一个DecorView对象

  1. protected DecorView generateDecor() {
  2.     return new DecorView(getContext(), –1);
  3. }

接着通过generateLayout(mDecor)来创建视图对象容器mContentParent

  1. protected ViewGroup generateLayout(DecorView decor) {
  2.     //通过读取属性配置文件设置窗口风格
  3.     if (a.getBoolean(com.android.internal.R.styleable.Window_windowActionBarOverlay, false)) {
  4.         requestFeature(FEATURE_ACTION_BAR_OVERLAY);
  5.     }
  6.     …
  7.     //通过读取属性配置文件设置窗口标志
  8.     if (a.getBoolean(com.android.internal.R.styleable.Window_windowFullscreen, false)) {
  9.     setFlags(FLAG_FULLSCREEN,FLAG_FULLSCREEN&(~getForcedWindowFlags()));
  10.     }
  11.     …
  12.     WindowManager.LayoutParams params = getAttributes();
  13.     …
  14.     mDecor.startChanging();
  15.     //根据窗口主题风格选择不同的布局文件layoutResource
  16.     …
  17.     //加载布局文件
  18.     ①View in = mLayoutInflater.inflate(layoutResource, null);
  19.     //添加到DecorView中
  20.     ②decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
  21.     //从窗口视图中找出窗口内容视图对象
  22.     ③ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
  23.     …
  24.     mDecor.finishChanging();
  25.     return contentParent;
  26. }

到此Activity的所有视图对象都已经创建完毕,DecorView是Activity的顶级视图,由窗口PhoneWindow对象持有,在DecorView视图对象中添加了一个ViewGroup容器组件contentParent,所有用户定义视图组件将被添加到该容器中。

handleResumeActivity

performLaunchActivity函数完成了两件事:

1)        Activity窗口对象的创建,通过attach函数来完成;

2)        Activity视图对象的创建,通过setContentView函数来完成;

这些准备工作完成后,就可以显示该Activity了,应用程序进程通过调用handleResumeActivity函数来启动Activity的显示过程。

frameworksbasecorejavaandroidapp ActivityThread.java

  1. final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward) {
  2.     unscheduleGcIdler();
  3.     ActivityClientRecord r;
  4.     try {
  5.         ①r = performResumeActivity(token, clearHide);
  6.     } catch (Exception e) {
  7.         …
  8.     }
  9.     if (r != null) {
  10.         final Activity a = r.activity;
  11.         …
  12.         if (r.window == null && !a.mFinished && willBeVisible) {
  13.             //获得为当前Activity创建的窗口PhoneWindow对象
  14.             r.window = r.activity.getWindow();
  15.             //获取为窗口创建的视图DecorView对象
  16.             View decor = r.window.getDecorView();
  17.             decor.setVisibility(View.INVISIBLE);
  18.             //在attach函数中就为当前Activity创建了WindowManager对象
  19.             ViewManager wm = a.getWindowManager();
  20.             //得到该视图对象的布局参数
  21.             ②WindowManager.LayoutParams l = r.window.getAttributes();
  22.             //将视图对象保存到Activity的成员变量mDecor中
  23.             a.mDecor = decor;
  24.             l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
  25.             if (r.intent.hasCategory(Intent.CATEGORY_HOME)) {
  26.                 l.idleScreenAvailable = true;
  27.             } else {
  28.                 l.idleScreenAvailable = false;
  29.             }
  30.             l.softInputMode |= forwardBit;
  31.             if (a.mVisibleFromClient) {
  32.                 a.mWindowAdded = true;
  33.                 //将创建的视图对象DecorView添加到Activity的窗口管理器中
  34.                 ③wm.addView(decor, l);
  35.             }
  36.         } else if (!willBeVisible) {
  37.             …
  38.         }
  39.         …
  40.         if (!r.onlyLocalRequest) {
  41.             r.nextIdle = mNewActivities;
  42.             mNewActivities = r;
  43.             Looper.myQueue().addIdleHandler(new Idler());
  44.         }
  45.         …
  46.     } else {
  47.         …
  48.     }
  49. }

我们知道,在前面的performLaunchActivity函数中完成Activity的创建后,会将当前当前创建的Activity在应用程序进程端的描述符ActivityClientRecord以键值对的形式保存到ActivityThread的成员变量mActivities中:mActivities.put(r.token, r),r.token就是Activity的身份证,即是IApplicationToken.Proxy代理对象,也用于与AMS通信。上面的函数首先通过performResumeActivity从mActivities变量中取出Activity的应用程序端描述符ActivityClientRecord,然后取出前面为Activity创建的视图对象DecorView和窗口管理器WindowManager,最后将视图对象添加到窗口管理器中。

我们知道Activity引用的其实是轻量级的窗口管理器LocalWindowManager

frameworksbasecorejavaandroidview Window.java

  1. public final void addView(View view, ViewGroup.LayoutParams params) {
  2.     WindowManager.LayoutParams wp = (WindowManager.LayoutParams)params;
  3.     CharSequence curTitle = wp.getTitle();
  4.     //应用程序窗口
  5.     if (wp.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&
  6.         wp.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
  7.         if (wp.token == null) {
  8.             View decor = peekDecorView();
  9.             if (decor != null) {
  10.                 // LayoutParams 的token设置为W本地Binder对象
  11.                 wp.token = decor.getWindowToken();
  12.             }
  13.         }
  14.         if (curTitle == null || curTitle.length() == 0) {
  15.             //根据窗口类型设置不同的标题
  16.             …
  17.             if (mAppName != null) {
  18.                 title += “:” + mAppName;
  19.             }
  20.             wp.setTitle(title);
  21.         }
  22.     } else {//系统窗口
  23.         if (wp.token == null) {
  24.             wp.token = mContainer == null ? mAppToken : mContainer.mAppToken;
  25.         }
  26.         if ((curTitle == null || curTitle.length() == 0)
  27.                 && mAppName != null) {
  28.             wp.setTitle(mAppName);
  29.         }
  30.     }
  31.     if (wp.packageName == null) {
  32.         wp.packageName = mContext.getPackageName();
  33.     }
  34.     if (mHardwareAccelerated) {
  35.         wp.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
  36.     }
  37.     super.addView(view, params);
  38. }

LocalWindowManager的addView函数对不同类型窗口的布局参数进行相应的设置,比如布局参数中的token设置,如果是应用程序窗口,则设置token为W本地Binder对象。如果不是应用程序窗口,同时当前窗口没有父窗口,则设置token为当前窗口的IApplicationToken.Proxy代理对象,否则设置为父窗口的IApplicationToken.Proxy代理对象。最后视图组件的添加工作交给其父类来完成。LocalWindowManager继承于CompatModeWrapper,是WindowManagerImpl的内部类。

frameworksbasecorejavaandroidviewWindowManagerImpl.java

  1. public void addView(View view, android.view.ViewGroup.LayoutParams params) {
  2.     mWindowManager.addView(view, params, mCompatibilityInfo);
  3. }

前面我们介绍了,每一个Activity拥有一个轻量级窗口管理器,通过轻量级窗口管理器LocalWindowManager来访问重量级窗口管理器WindowManagerImpl,因此视图组件的添加过程又转交给了WindowManagerImpl来实现。

  1. public void addView(View view, ViewGroup.LayoutParams params, CompatibilityInfoHolder cih) {
  2.     addView(view, params, cih, false);
  3. }

该函数又调用WindowManagerImpl的另一个重载函数来添加视图组件

  1. private void addView(View view, ViewGroup.LayoutParams params,
  2.         CompatibilityInfoHolder cih, boolean nest) {
  3.     …
  4.     final WindowManager.LayoutParams wparams= (WindowManager.LayoutParams)params;
  5.     ViewRootImpl root;
  6.     View panelParentView = null;
  7.     synchronized (this) {
  8.         …
  9.         //从mViews中查找当前添加的View
  10.         int index = findViewLocked(view, false);
  11.         //如果已经存在,直接返回
  12.         if (index >= 0) {
  13.             …
  14.             return;
  15.         }
  16.         //尚未添加当前View
  17.         if (wparams.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&
  18.                 wparams.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
  19.             final int count = mViews != null ? mViews.length : 0;
  20.             for (int i=0; i<count; i++) {
  21.                 if (mRoots[i].mWindow.asBinder() == wparams.token) {
  22.                     panelParentView = mViews[i];
  23.                 }
  24.             }
  25.         }
  26.         //为Activity创建一个ViewRootImpl对象
  27.         ①root = new ViewRootImpl(view.getContext());
  28.         …
  29.         //设置视图组件的布局参数
  30.         view.setLayoutParams(wparams);
  31.         if (mViews == null) {
  32.             index = 1;
  33.             mViews = new View[1];
  34.             mRoots = new ViewRootImpl[1];
  35.             mParams = new WindowManager.LayoutParams[1];
  36.         } else {
  37.             //动态增加mViews数组长度
  38.             index = mViews.length + 1;
  39.             Object[] old = mViews;
  40.             mViews = new View[index];
  41.             System.arraycopy(old, 0, mViews, 0, index-1);
  42.             //动态增加mRoots数组长度
  43.             old = mRoots;
  44.             mRoots = new ViewRootImpl[index];
  45.             System.arraycopy(old, 0, mRoots, 0, index-1);
  46.             //动态增加mParams数组长度
  47.             old = mParams;
  48.             mParams = new WindowManager.LayoutParams[index];
  49.             System.arraycopy(old, 0, mParams, 0, index-1);
  50.         }
  51.         index–;
  52.         ②mViews[index] = view;
  53.         mRoots[index] = root;
  54.         mParams[index] = wparams;
  55.     }
  56.     try {
  57.         ③root.setView(view, wparams, panelParentView);
  58.     } catch (RuntimeException e) {
  59.         …
  60.     }
  61. }

到此我们知道,当应用程序向窗口管理器中添加一个视图对象时,首先会为该视图对象创建一个ViewRootImpl对象,并且将视图对象、ViewRootImpl对象、视图布局参数分别保存到窗口管理器WindowManagerImpl得mViews、mRoots、mParams数组中,如下图所示:

最后通过ViewRootImpl对象来完成视图的显示过程。

ViewRootImpl构造过程

frameworksbasecorejavaandroidviewViewRootImpl.java

  1. public ViewRootImpl(Context context) {
  2.     …
  3.     ①getWindowSession(context.getMainLooper());
  4.     mThread = Thread.currentThread();
  5.     mLocation = new WindowLeaked(null);
  6.     mLocation.fillInStackTrace();
  7.     mWidth = –1;
  8.     mHeight = –1;
  9.     mDirty = new Rect();
  10.     mTempRect = new Rect();
  11.     mVisRect = new Rect();
  12.     mWinFrame = new Rect();
  13.     ②mWindow = new W(this);
  14.     mTargetSdkVersion = context.getApplicationInfo().targetSdkVersion;
  15.     mInputMethodCallback = new InputMethodCallback(this);
  16.     mViewVisibility = View.GONE;
  17.     mTransparentRegion = new Region();
  18.     mPreviousTransparentRegion = new Region();
  19.     mFirst = true// true for the first time the view is added
  20.     mAdded = false;
  21.     mAccessibilityManager = AccessibilityManager.getInstance(context);
  22.     mAccessibilityInteractionConnectionManager =
  23.         new AccessibilityInteractionConnectionManager();
  24.     mAccessibilityManager.addAccessibilityStateChangeListener(
  25.             mAccessibilityInteractionConnectionManager);
  26.     ③mAttachInfo = new View.AttachInfo(sWindowSession, mWindow, this, mHandler, this);
  27.     mViewConfiguration = ViewConfiguration.get(context);
  28.     mDensity = context.getResources().getDisplayMetrics().densityDpi;
  29.     mFallbackEventHandler = PolicyManager.makeNewFallbackEventHandler(context);
  30.     mProfileRendering = Boolean.parseBoolean(
  31.             SystemProperties.get(PROPERTY_PROFILE_RENDERING, “false”));
  32.     ④mChoreographer = Choreographer.getInstance();
  33.     PowerManager powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
  34.     mAttachInfo.mScreenOn = powerManager.isScreenOn();
  35.     loadSystemProperties();
  36. }

在ViewRootImpl的构造函数中初始化了一些成员变量,ViewRootImpl创建了以下几个主要对象:

1)        通过getWindowSession(context.getMainLooper())得到IWindowSession的代理对象,该对象用于和WMS通信。

2)        创建了一个W本地Binder对象,用于WMS通知应用程序进程。

3)        采用单例模式创建了一个Choreographer对象,用于统一调度窗口绘图。

4)        创建ViewRootHandler对象,用于处理当前视图消息。

5)        构造一个AttachInfo对象;

6)        创建Surface对象,用于绘制当前视图,当然该Surface对象的真正创建是由WMS来完成的,只不过是WMS传递给应用程序进程的。

  1. private final Surface mSurface = new Surface();
  2. final ViewRootHandler mHandler = new ViewRootHandler();

IWindowSession代理获取过程

frameworksbasecorejavaandroidviewViewRootImpl.java

  1. public static IWindowSession getWindowSession(Looper mainLooper) {
  2.     synchronized (mStaticInit) {
  3.         if (!mInitialized) {
  4.             try {
  5.                 //获取输入法管理器
  6.                 InputMethodManager imm = InputMethodManager.getInstance(mainLooper);
  7.                 //获取窗口管理器
  8.                 IWindowManager windowManager = Display.getWindowManager();
  9.                 //得到IWindowSession代理对象
  10.                 sWindowSession = windowManager.openSession(imm.getClient(), imm.getInputContext());
  11.                 float animatorScale = windowManager.getAnimationScale(2);
  12.                 ValueAnimator.setDurationScale(animatorScale);
  13.                 mInitialized = true;
  14.             } catch (RemoteException e) {
  15.             }
  16.         }
  17.         return sWindowSession;
  18.     }
  19. }

以上函数通过WMS的openSession函数创建应用程序与WMS之间的连接通道,即获取IWindowSession代理对象,并将该代理对象保存到ViewRootImpl的静态成员变量sWindowSession中

  1. static IWindowSession sWindowSession;

因此在应用程序进程中有且只有一个IWindowSession代理对象。

frameworksbaseservicesjavacomandroidserverwmWindowManagerService.java

  1. public IWindowSession openSession(IInputMethodClient client,
  2.         IInputContext inputContext) {
  3.     if (client == nullthrow new IllegalArgumentException(“null client”);
  4.     if (inputContext == nullthrow new IllegalArgumentException(“null inputContext”);
  5.     Session session = new Session(this, client, inputContext);
  6.     return session;
  7. }

在WMS服务端构造了一个Session实例对象。

AttachInfo构造过程

frameworksbasecorejavaandroidview View.java

  1. AttachInfo(IWindowSession session, IWindow window,
  2.         ViewRootImpl viewRootImpl, Handler handler, Callbacks effectPlayer) {
  3.     mSession = session;//IWindowSession代理对象,用于与WMS通信
  4.     mWindow = window;//W对象
  5.     mWindowToken = window.asBinder();//W本地Binder对象
  6.     mViewRootImpl = viewRootImpl;//ViewRootImpl实例
  7.     mHandler = handler;//ViewRootHandler对象
  8.     mRootCallbacks = effectPlayer;//ViewRootImpl实例
  9. }

 

创建Choreographer对象

Android Project Butter分析中介绍了Android4.1引入VSYNC、Triple Buffer和Choreographer来改善Android先天存在的UI流畅性差问题,有关Choreographer的实现过程请参看Android系统Choreographer机制实现过程

视图View添加过程

窗口管理器WindowManagerImpl为当前添加的窗口创建好各种对象后,调用ViewRootImpl的setView函数向WMS服务添加一个窗口对象。

  1. public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
  2.     synchronized (this) {
  3.         if (mView == null) {
  4.             //将DecorView保存到ViewRootImpl的成员变量mView中
  5.             mView = view;
  6.             mFallbackEventHandler.setView(view);
  7.             mWindowAttributes.copyFrom(attrs);
  8.             attrs = mWindowAttributes;
  9.             mClientWindowLayoutFlags = attrs.flags;
  10.             setAccessibilityFocus(nullnull);
  11.             //DecorView实现了RootViewSurfaceTaker接口
  12.             if (view instanceof RootViewSurfaceTaker) {
  13.                 mSurfaceHolderCallback =
  14.                         ((RootViewSurfaceTaker)view).willYouTakeTheSurface();
  15.                 if (mSurfaceHolderCallback != null) {
  16.                     mSurfaceHolder = new TakenSurfaceHolder();
  17.                     mSurfaceHolder.setFormat(PixelFormat.UNKNOWN);
  18.                 }
  19.             }
  20.             …
  21.             //同时将DecorView保存到mAttachInfo中
  22.             mAttachInfo.mRootView = view;
  23.             mAttachInfo.mScalingRequired = mTranslator != null;
  24.             mAttachInfo.mApplicationScale = mTranslator == null ? 1.0f : mTranslator.applicationScale;
  25.             if (panelParentView != null) {
  26.                 mAttachInfo.mPanelParentWindowToken
  27.                         = panelParentView.getApplicationWindowToken();
  28.             }
  29.             …
  30.             //在添加窗口前进行UI布局
  31.             ①requestLayout();
  32.             if ((mWindowAttributes.inputFeatures& WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
  33.                 mInputChannel = new InputChannel();
  34.             }
  35.             try {
  36.                 mOrigWindowType = mWindowAttributes.type;
  37.                 mAttachInfo.mRecomputeGlobalAttributes = true;
  38.                 collectViewAttributes();
  39.                 //将窗口添加到WMS服务中,mWindow为W本地Binder对象,通过Binder传输到WMS服务端后,变为IWindow代理对象
  40.                 ②res = sWindowSession.add(mWindow, mSeq, mWindowAttributes,
  41.                         getHostVisibility(), mAttachInfo.mContentInsets,
  42.                         mInputChannel);
  43.             } catch (RemoteException e) {
  44.                 …
  45.             }
  46.             …
  47.             //建立窗口消息通道
  48.             if (view instanceof RootViewSurfaceTaker) {
  49.                 mInputQueueCallback =
  50.                     ((RootViewSurfaceTaker)view).willYouTakeTheInputQueue();
  51.             }
  52.             if (mInputChannel != null) {
  53.                 if (mInputQueueCallback != null) {
  54.                     mInputQueue = new InputQueue(mInputChannel);
  55.                     mInputQueueCallback.onInputQueueCreated(mInputQueue);
  56.                 } else {
  57.                     mInputEventReceiver = new WindowInputEventReceiver(mInputChannel,Looper.myLooper());
  58.                 }
  59.             }
  60.             …
  61.         }
  62.     }
  63. }

通过前面的分析可以知道,用户自定义的UI作为一个子View被添加到DecorView中,然后将顶级视图DecorView添加到应用程序进程的窗口管理器中,窗口管理器首先为当前添加的View创建一个ViewRootImpl对象、一个布局参数对象ViewGroup.LayoutParams,然后将这三个对象分别保存到当前应用程序进程的窗口管理器WindowManagerImpl中,最后通过ViewRootImpl对象将当前视图对象注册到WMS服务中。

ViewRootImpl的setView函数向WMS服务添加一个窗口对象过程:

1)         requestLayout()在应用程序进程中进行窗口UI布局;

2)         WindowSession.add()向WMS服务注册一个窗口对象;

3)         注册应用程序进程端的消息接收通道;

窗口UI布局过程

frameworksbasecorejavaandroidviewViewRootImpl.java

  1. public void requestLayout() {
  2.     //检查当前线程是否是UI线程
  3.     checkThread();
  4.     //标识当前正在请求UI布局
  5.     mLayoutRequested = true;
  6.     scheduleTraversals();
  7. }

窗口布局过程必须在UI线程中进行,因此该函数首先检查调用requestLayout()函数的线程是否为创建ViewRootImpl对象的线程。然后调用scheduleTraversals()函数启动Choreographer的Callback遍历过程。

  1. void scheduleTraversals() {
  2.     if (!mTraversalScheduled) {
  3.         mTraversalScheduled = true;
  4.         //暂停UI线程消息队列对同步消息的处理
  5.         mTraversalBarrier = mHandler.getLooper().postSyncBarrier();
  6.         //向Choreographer注册一个类型为CALLBACK_TRAVERSAL的回调,用于处理UI绘制
  7.         mChoreographer.postCallback(
  8.                 Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
  9.         //向Choreographer注册一个类型为CALLBACK_INPUT的回调,用于处理输入事件
  10.         scheduleConsumeBatchedInput();
  11.     }
  12. }

关于Choreographer的postCallback()用法在前面进行了详细的介绍,当Vsync事件到来时,mTraversalRunnable对象的run()函数将被调用。

frameworksbasecorejavaandroidviewViewRootImpl.java

  1. final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
  2. final class TraversalRunnable implements Runnable {
  3.         @Override
  4.         public void run() {
  5.             doTraversal();
  6.         }
  7. }

mTraversalRunnable对象的类型为TraversalRunnable,该类实现了Runnable接口,在其run()函数中调用了doTraversal()函数来完成窗口布局。

  1. void doTraversal() {
  2.     if (mTraversalScheduled) {
  3.         mTraversalScheduled = false;
  4.         mHandler.getLooper().removeSyncBarrier(mTraversalBarrier);
  5.         if (mProfile) {
  6.             Debug.startMethodTracing(“ViewAncestor”);
  7.         }
  8.         Trace.traceBegin(Trace.TRACE_TAG_VIEW, “performTraversals”);
  9.         try {
  10.             performTraversals();
  11.         } finally {
  12.             Trace.traceEnd(Trace.TRACE_TAG_VIEW);
  13.         }
  14.         if (mProfile) {
  15.             Debug.stopMethodTracing();
  16.             mProfile = false;
  17.         }
  18.     }
  19. }

performTraversals函数相当复杂,其主要实现以下几个重要步骤:

1.执行窗口测量;

2.执行窗口注册;

3.执行窗口布局;

4.执行窗口绘图;

  1. private void performTraversals() {
  2.     // cache mView since it is used so much below…
  3.     final View host = mView;
  4.     if (host == null || !mAdded)
  5.         return;
  6.     mWillDrawSoon = true;
  7.     boolean windowSizeMayChange = false;
  8.     boolean newSurface = false;
  9.     boolean surfaceChanged = false;
  10.     WindowManager.LayoutParams lp = mWindowAttributes;
  11.     int desiredWindowWidth;
  12.     int desiredWindowHeight;
  13.     final View.AttachInfo attachInfo = mAttachInfo;
  14.     final int viewVisibility = getHostVisibility();
  15.     boolean viewVisibilityChanged = mViewVisibility != viewVisibility
  16.             || mNewSurfaceNeeded;
  17.     WindowManager.LayoutParams params = null;
  18.     if (mWindowAttributesChanged) {
  19.         mWindowAttributesChanged = false;
  20.         surfaceChanged = true;
  21.         params = lp;
  22.     }
  23.     …
  24.     /****************执行窗口测量******************/
  25.     boolean layoutRequested = mLayoutRequested && !mStopped;
  26.     if (layoutRequested) {
  27.         …
  28.         // Ask host how big it wants to be
  29.         windowSizeMayChange |= measureHierarchy(host, lp, res,
  30.                 desiredWindowWidth, desiredWindowHeight);
  31.     }
  32.     …
  33.     /****************向WMS服务添加窗口******************/
  34.     if (mFirst || windowShouldResize || insetsChanged ||
  35.             viewVisibilityChanged || params != null) {
  36.         …
  37.         try {
  38.             final int surfaceGenerationId = mSurface.getGenerationId();
  39.             relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);
  40.             …
  41.         } catch (RemoteException e) {
  42.         }
  43.         …
  44.         if (!mStopped) {
  45.             boolean focusChangedDueToTouchMode = ensureTouchModeLocally(
  46.                     (relayoutResult&WindowManagerImpl.RELAYOUT_RES_IN_TOUCH_MODE) != 0);
  47.             if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()
  48.                     || mHeight != host.getMeasuredHeight() || contentInsetsChanged) {
  49.                 …
  50.                  // Ask host how big it wants to be
  51.                 performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
  52.                 …
  53.             }
  54.         }
  55.     }
  56.     /****************执行窗口布局******************/
  57.     final boolean didLayout = layoutRequested && !mStopped;
  58.     boolean triggerGlobalLayoutListener = didLayout
  59.             || attachInfo.mRecomputeGlobalAttributes;
  60.     if (didLayout) {
  61.         performLayout();
  62.         …
  63.     }
  64.     …
  65.     /****************查找窗口焦点******************/
  66.     boolean skipDraw = false;
  67.     if (mFirst) {
  68.         // handle first focus request
  69.         if (DEBUG_INPUT_RESIZE) Log.v(TAG, “First: mView.hasFocus()=”
  70.                 + mView.hasFocus());
  71.         if (mView != null) {
  72.             if (!mView.hasFocus()) {
  73.                 mView.requestFocus(View.FOCUS_FORWARD);
  74.                 mFocusedView = mRealFocusedView = mView.findFocus();
  75.                 if (DEBUG_INPUT_RESIZE) Log.v(TAG, “First: requested focused view=”
  76.                         + mFocusedView);
  77.             } else {
  78.                 mRealFocusedView = mView.findFocus();
  79.                 if (DEBUG_INPUT_RESIZE) Log.v(TAG, “First: existing focused view=”
  80.                         + mRealFocusedView);
  81.             }
  82.         }
  83.         if ((relayoutResult&WindowManagerImpl.RELAYOUT_RES_ANIMATING) != 0) {
  84.             // The first time we relayout the window, if the system is
  85.             // doing window animations, we want to hold of on any future
  86.             // draws until the animation is done.
  87.             mWindowsAnimating = true;
  88.         }
  89.     } else if (mWindowsAnimating) {
  90.         skipDraw = true;
  91.     }
  92.     /****************执行窗口绘制******************/
  93.     mFirst = false;
  94.     mWillDrawSoon = false;
  95.     mNewSurfaceNeeded = false;
  96.     mViewVisibility = viewVisibility;
  97.     …
  98.     boolean cancelDraw = attachInfo.mTreeObserver.dispatchOnPreDraw() ||
  99.             viewVisibility != View.VISIBLE;
  100.     if (!cancelDraw && !newSurface) {
  101.         if (!skipDraw || mReportNextDraw) {
  102.             if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
  103.                 for (int i = 0; i < mPendingTransitions.size(); ++i) {
  104.                     mPendingTransitions.get(i).startChangingAnimations();
  105.                 }
  106.                 mPendingTransitions.clear();
  107.             }
  108.             performDraw();
  109.         }
  110.     } else {
  111.         if (viewVisibility == View.VISIBLE) {
  112.             // Try again
  113.             scheduleTraversals();
  114.         } else if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
  115.             for (int i = 0; i < mPendingTransitions.size(); ++i) {
  116.                 mPendingTransitions.get(i).endChangingAnimations();
  117.             }
  118.             mPendingTransitions.clear();
  119.         }
  120.     }
  121. }
performMeasure

frameworksbasecorejavaandroidviewViewRootImpl.java

  1. private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
  2.  Trace.traceBegin(Trace.TRACE_TAG_VIEW, “measure”);
  3.  try {
  4.   mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
  5.  } finally {
  6.   Trace.traceEnd(Trace.TRACE_TAG_VIEW);
  7.  }
  8. }
relayoutWindow

frameworksbasecorejavaandroidviewViewRootImpl.java

  1. private int relayoutWindow(WindowManager.LayoutParams params, int viewVisibility,
  2.         boolean insetsPending) throws RemoteException {
  3.     …
  4.     int relayoutResult = sWindowSession.relayout(
  5.             mWindow, mSeq, params,
  6.             (int) (mView.getMeasuredWidth() * appScale + 0.5f),
  7.             (int) (mView.getMeasuredHeight() * appScale + 0.5f),
  8.             viewVisibility, insetsPending ? WindowManagerImpl.RELAYOUT_INSETS_PENDING : 0,
  9.             mWinFrame, mPendingContentInsets, mPendingVisibleInsets,
  10.             mPendingConfiguration, mSurface);
  11.     …
  12.     return relayoutResult;
  13. }

这里通过前面获取的IWindowSession代理对象请求WMS服务执行窗口布局,mSurface是ViewRootImpl的成员变量

  1. private final Surface mSurface = new Surface();

frameworksbasecorejavaandroidview Surface.java

  1. public Surface() {
  2.     checkHeadless();
  3.     if (DEBUG_RELEASE) {
  4.         mCreationStack = new Exception();
  5.     }
  6.     mCanvas = new CompatibleCanvas();
  7. }

该Surface构造函数仅仅创建了一个CompatibleCanvas对象,并没有对该Surface进程native层的初始化,到此我们知道应用程序进程为每个窗口对象都创建了一个Surface对象。并且将该Surface通过跨进程方式传输给WMS服务进程,我们知道,在Android系统中,如果一个对象需要在不同进程间传输,必须实现Parcelable接口,Surface类正好实现了Parcelable接口。ViewRootImpl通过IWindowSession接口请求WMS的完整过程如下:

frameworksbasecorejavaandroidviewIWindowSession.java$ Proxy

  1. public int relayout(android.view.IWindow window, int seq,
  2.         android.view.WindowManager.LayoutParams attrs, int requestedWidth,
  3.         int requestedHeight, int viewVisibility, int flags,
  4.         android.graphics.Rect outFrame,
  5.         android.graphics.Rect outOverscanInsets,
  6.         android.graphics.Rect outContentInsets,
  7.         android.graphics.Rect outVisibleInsets,
  8.         android.content.res.Configuration outConfig,
  9.         android.view.Surface outSurface) throws android.os.RemoteException {
  10.     android.os.Parcel _data = android.os.Parcel.obtain();
  11.     android.os.Parcel _reply = android.os.Parcel.obtain();
  12.     int _result;
  13.     try {
  14.         _data.writeInterfaceToken(DESCRIPTOR);
  15.         _data.writeStrongBinder((((window != null)) ? (window.asBinder()): (null)));
  16.         _data.writeInt(seq);
  17.         if ((attrs != null)) {
  18.             _data.writeInt(1);
  19.             attrs.writeToParcel(_data, 0);
  20.         } else {
  21.             _data.writeInt(0);
  22.         }
  23.         _data.writeInt(requestedWidth);
  24.         _data.writeInt(requestedHeight);
  25.         _data.writeInt(viewVisibility);
  26.         _data.writeInt(flags);
  27.         mRemote.transact(Stub.TRANSACTION_relayout, _data, _reply, 0);
  28.         _reply.readException();
  29.         _result = _reply.readInt();
  30.         if ((0 != _reply.readInt())) {
  31.             outFrame.readFromParcel(_reply);
  32.         }
  33.         if ((0 != _reply.readInt())) {
  34.             outOverscanInsets.readFromParcel(_reply);
  35.         }
  36.         if ((0 != _reply.readInt())) {
  37.             outContentInsets.readFromParcel(_reply);
  38.         }
  39.         if ((0 != _reply.readInt())) {
  40.             outVisibleInsets.readFromParcel(_reply);
  41.         }
  42.         if ((0 != _reply.readInt())) {
  43.             outConfig.readFromParcel(_reply);
  44.         }
  45.         if ((0 != _reply.readInt())) {
  46.             outSurface.readFromParcel(_reply);
  47.         }
  48.     } finally {
  49.         _reply.recycle();
  50.         _data.recycle();
  51.     }
  52.     return _result;
  53. }

从该函数的实现可以看出,应用程序进程中创建的Surface对象并没有传递到WMS服务进程,只是读取WMS服务进程返回来的Surface。那么WMS服务进程是如何响应应用程序进程布局请求的呢?
frameworksbasecorejavaandroidviewIWindowSession.java$ Stub

  1. public boolean onTransact(int code, android.os.Parcel data,
  2.         android.os.Parcel reply, int flags)throws android.os.RemoteException {
  3.     switch (code) {
  4.     case TRANSACTION_relayout: {
  5.         data.enforceInterface(DESCRIPTOR);
  6.         android.view.IWindow _arg0;
  7.         _arg0 = android.view.IWindow.Stub.asInterface(data.readStrongBinder());
  8.         int _arg1;
  9.         _arg1 = data.readInt();
  10.         android.view.WindowManager.LayoutParams _arg2;
  11.         if ((0 != data.readInt())) {
  12.             _arg2 = android.view.WindowManager.LayoutParams.CREATOR
  13.                     .createFromParcel(data);
  14.         } else {
  15.             _arg2 = null;
  16.         }
  17.         int _arg3;
  18.         _arg3 = data.readInt();
  19.         int _arg4;
  20.         _arg4 = data.readInt();
  21.         int _arg5;
  22.         _arg5 = data.readInt();
  23.         int _arg6;
  24.         _arg6 = data.readInt();
  25.         android.graphics.Rect _arg7;
  26.         _arg7 = new android.graphics.Rect();
  27.         android.graphics.Rect _arg8;
  28.         _arg8 = new android.graphics.Rect();
  29.         android.graphics.Rect _arg9;
  30.         _arg9 = new android.graphics.Rect();
  31.         android.graphics.Rect _arg10;
  32.         _arg10 = new android.graphics.Rect();
  33.         android.content.res.Configuration _arg11;
  34.         _arg11 = new android.content.res.Configuration();
  35.         android.view.Surface _arg12;
  36.         _arg12 = new android.view.Surface();
  37.         int _result = this.relayout(_arg0, _arg1, _arg2, _arg3, _arg4,
  38.                 _arg5, _arg6, _arg7, _arg8, _arg9, _arg10, _arg11, _arg12);
  39.         reply.writeNoException();
  40.         reply.writeInt(_result);
  41.         if ((_arg7 != null)) {
  42.             reply.writeInt(1);
  43.             _arg7.writeToParcel(reply,
  44.                     android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
  45.         } else {
  46.             reply.writeInt(0);
  47.         }
  48.         if ((_arg8 != null)) {
  49.             reply.writeInt(1);
  50.             _arg8.writeToParcel(reply,
  51.                     android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
  52.         } else {
  53.             reply.writeInt(0);
  54.         }
  55.         if ((_arg9 != null)) {
  56.             reply.writeInt(1);
  57.             _arg9.writeToParcel(reply,
  58.                     android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
  59.         } else {
  60.             reply.writeInt(0);
  61.         }
  62.         if ((_arg10 != null)) {
  63.             reply.writeInt(1);
  64.             _arg10.writeToParcel(reply,
  65.                     android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
  66.         } else {
  67.             reply.writeInt(0);
  68.         }
  69.         if ((_arg11 != null)) {
  70.             reply.writeInt(1);
  71.             _arg11.writeToParcel(reply,
  72.                     android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
  73.         } else {
  74.             reply.writeInt(0);
  75.         }
  76.         if ((_arg12 != null)) {
  77.             reply.writeInt(1);
  78.             _arg12.writeToParcel(reply,
  79.                     android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
  80.         } else {
  81.             reply.writeInt(0);
  82.         }
  83.         return true;
  84.     }
  85.     }
  86. }

该函数可以看出,WMS服务在响应应用程序进程请求添加窗口时,首先在当前进程空间创建一个Surface对象,然后调用Session的relayout()函数进一步完成窗口添加过程,最后将WMS服务中创建的Surface返回给应用程序进程。

到目前为止,在应用程序进程和WMS服务进程分别创建了一个Surface对象,但是他们调用的都是Surface的无参构造函数,在该构造函数中并未真正初始化native层的Surface,那native层的Surface是在那里创建的呢?
frameworksbaseservicesjavacomandroidserverwm Session.java

  1. public int relayout(IWindow window, int seq, WindowManager.LayoutParams attrs,
  2.         int requestedWidth, int requestedHeight, int viewFlags,
  3.         int flags, Rect outFrame, Rect outContentInsets,
  4.         Rect outVisibleInsets, Configuration outConfig, Surface outSurface) {
  5.     int res = mService.relayoutWindow(this, window, seq, attrs,
  6.             requestedWidth, requestedHeight, viewFlags, flags,
  7.             outFrame, outContentInsets, outVisibleInsets,
  8.             outConfig, outSurface);
  9.     return res;
  10. }

frameworksbaseservicesjavacomandroidserverwm WindowManagerService.java

  1. public int relayoutWindow(Session session, IWindow client, int seq,
  2.         WindowManager.LayoutParams attrs, int requestedWidth,
  3.         int requestedHeight, int viewVisibility, int flags,
  4.         Rect outFrame, Rect outContentInsets,
  5.         Rect outVisibleInsets, Configuration outConfig, Surface outSurface) {
  6.     …
  7.     synchronized(mWindowMap) {
  8.         // TODO(cmautner): synchronize on mAnimator or win.mWinAnimator.
  9.         WindowState win = windowForClientLocked(session, client, false);
  10.         if (win == null) {
  11.             return 0;
  12.         }
  13.         …
  14.         if (viewVisibility == View.VISIBLE &&
  15.                 (win.mAppToken == null || !win.mAppToken.clientHidden)) {
  16.             …
  17.             try {
  18.                 if (!win.mHasSurface) {
  19.                     surfaceChanged = true;
  20.                 }
  21.                 //创建Surface
  22.                 Surface surface = winAnimator.createSurfaceLocked();
  23.                 if (surface != null) {
  24.                     outSurface.copyFrom(surface);
  25.                 } else {
  26.                     outSurface.release();
  27.                 }
  28.             } catch (Exception e) {
  29.                 …
  30.             }
  31.             …
  32.         }
  33.         …
  34.     }
  35.     …
  36. }

frameworksbaseservicesjavacomandroidserverwmWindowStateAnimator.java

  1. Surface createSurfaceLocked() {
  2.     if (mSurface == null) {
  3.         …
  4.         try {
  5.             …
  6.             if (DEBUG_SURFACE_TRACE) {
  7.                 mSurface = new SurfaceTrace(
  8.                         mSession.mSurfaceSession, mSession.mPid,
  9.                         attrs.getTitle().toString(),
  10.                         0, w, h, format, flags);
  11.             } else {
  12.                 mSurface = new Surface(
  13.                     mSession.mSurfaceSession, mSession.mPid,
  14.                     attrs.getTitle().toString(),
  15.                     0, w, h, format, flags);
  16.             }
  17.             mWin.mHasSurface = true;
  18.         } catch (Surface.OutOfResourcesException e) {
  19.             …
  20.         }
  21.         Surface.openTransaction();
  22.         …
  23.     }
  24.     return mSurface;
  25. }

Surface创建过程
frameworksbasecorejavaandroidviewSurface.java

  1. public Surface(SurfaceSession s,int pid, String name, int display, int w, int h, int format, int flags)
  2.     throws OutOfResourcesException {
  3.     checkHeadless();
  4.     if (DEBUG_RELEASE) {
  5.         mCreationStack = new Exception();
  6.     }
  7.     mCanvas = new CompatibleCanvas();
  8.     init(s,pid,name,display,w,h,format,flags);
  9.     mName = name;
  10. }

frameworksbasecorejni android_view_Surface.cpp

  1. static void Surface_init(
  2.         JNIEnv* env, jobject clazz,
  3.         jobject session,
  4.         jint, jstring jname, jint dpy, jint w, jint h, jint format, jint flags)
  5. {
  6.     if (session == NULL) {
  7.         doThrowNPE(env);
  8.         return;
  9.     }
  10.     SurfaceComposerClient* client =
  11.             (SurfaceComposerClient*)env->GetIntField(session, sso.client);
  12.     sp<SurfaceControl> surface;
  13.     if (jname == NULL) {
  14.         surface = client->createSurface(dpy, w, h, format, flags);
  15.     } else {
  16.         const jchar* str = env->GetStringCritical(jname, 0);
  17.         const String8 name(str, env->GetStringLength(jname));
  18.         env->ReleaseStringCritical(jname, str);
  19.         surface = client->createSurface(name, dpy, w, h, format, flags);
  20.     }
  21.     if (surface == 0) {
  22.         jniThrowException(env, OutOfResourcesException, NULL);
  23.         return;
  24.     }
  25.     setSurfaceControl(env, clazz, surface);
  26. }

到此才算真正创建了一个可用于绘图的Surface,从上面的分析我们可以看出,在WMS服务进程端,其实创建了两个Java层的Surface对象,第一个Surface使用了无参构造函数,仅仅构造一个Surface对象而已,而第二个Surface却使用了有参构造函数,参数指定了图象宽高等信息,这个Java层Surface对象还会在native层请求SurfaceFlinger创建一个真正能用于绘制图象的native层Surface。最后通过浅拷贝的方式将第二个Surface复制到第一个Surface中,最后通过writeToParcel方式写回到应用程序进程。

到目前为止,应用程序和WMS一共创建了3个Java层Surface对象,如上图所示,而真正能用于绘图的Surface只有3号,那么3号Surface与2号Surface之间是什么关系呢?outSurface.copyFrom(surface)
frameworksbasecorejni android_view_Surface.cpp

  1. static void Surface_copyFrom(JNIEnv* env, jobject clazz, jobject other)
  2. {
  3.     if (clazz == other)
  4.         return;
  5.     if (other == NULL) {
  6.         doThrowNPE(env);
  7.         return;
  8.     }
  9.     //得到当前Surface所引用的SurfaceControl对象
  10.     const sp<SurfaceControl>& surface = getSurfaceControl(env, clazz);
  11.     //得到源Surface所引用的SurfaceControl对象
  12.     const sp<SurfaceControl>& rhs = getSurfaceControl(env, other);
  13.     //如果它们引用的不是同一个SurfaceControl对象
  14.     if (!SurfaceControl::isSameSurface(surface, rhs)) {
  15.         setSurfaceControl(env, clazz, rhs);
  16.     }
  17. }

2号Surface引用到了3号Surface的SurfaceControl对象后,通过writeToParcel()函数写会到应用程序进程。
frameworksbasecorejni android_view_Surface.cpp

  1. static void Surface_writeToParcel(
  2.         JNIEnv* env, jobject clazz, jobject argParcel, jint flags)
  3. {
  4.     Parcel* parcel = (Parcel*)env->GetIntField(
  5.             argParcel, no.native_parcel);
  6.     if (parcel == NULL) {
  7.         doThrowNPE(env);
  8.         return;
  9.     }
  10.     const sp<SurfaceControl>& control(getSurfaceControl(env, clazz));
  11.     if (control != NULL) {
  12.         SurfaceControl::writeSurfaceToParcel(control, parcel);
  13.     } else {
  14.         sp<Surface> surface(Surface_getSurface(env, clazz));
  15.         if (surface != NULL) {
  16.             Surface::writeToParcel(surface, parcel);
  17.         } else {
  18.             SurfaceControl::writeSurfaceToParcel(NULL, parcel);
  19.         }
  20.     }
  21.     if (flags & PARCELABLE_WRITE_RETURN_VALUE) {
  22.         setSurfaceControl(env, clazz, NULL);
  23.         setSurface(env, clazz, NULL);
  24.     }
  25. }

由于2号Surface引用的SurfaceControl对象不为空,因此这里就将SurfaceControl对象写会给应用程序进程
frameworksnativelibsgui Surface.cpp

  1. status_t SurfaceControl::writeSurfaceToParcel(
  2.         const sp<SurfaceControl>& control, Parcel* parcel)
  3. {
  4.     sp<ISurface> sur;
  5.     uint32_t identity = 0;
  6.     if (SurfaceControl::isValid(control)) {
  7.         sur = control->mSurface;
  8.         identity = control->mIdentity;
  9.     }
  10.     parcel->writeStrongBinder(sur!=0 ? sur->asBinder() : NULL);
  11.     parcel->writeStrongBinder(NULL);  // NULL ISurfaceTexture in this case.
  12.     parcel->writeInt32(identity);
  13.     return NO_ERROR;
  14. }

写入Parcel包裹的对象顺序如下:

应用程序进程中的1号Surface通过readFromParcel()函数读取从WMS服务进程写回的Binder对象。
frameworksbasecorejni android_view_Surface.cpp

  1. static void Surface_readFromParcel(
  2.         JNIEnv* env, jobject clazz, jobject argParcel)
  3. {
  4.     Parcel* parcel = (Parcel*)env->GetIntField( argParcel, no.native_parcel);
  5.     if (parcel == NULL) {
  6.         doThrowNPE(env);
  7.         return;
  8.     }
  9.     sp<Surface> sur(Surface::readFromParcel(*parcel));
  10.     setSurface(env, clazz, sur);
  11. }

frameworksnativelibsgui Surface.cpp

  1. sp<Surface> Surface::readFromParcel(const Parcel& data) {
  2.     Mutex::Autolock _l(sCachedSurfacesLock);
  3.     sp<IBinder> binder(data.readStrongBinder());
  4.     sp<Surface> surface = sCachedSurfaces.valueFor(binder).promote();
  5.     if (surface == 0) {
  6.        surface = new Surface(data, binder);
  7.        sCachedSurfaces.add(binder, surface);
  8.     } else {
  9.         // The Surface was found in the cache, but we still should clear any
  10.         // remaining data from the parcel.
  11.         data.readStrongBinder();  // ISurfaceTexture
  12.         data.readInt32();         // identity
  13.     }
  14.     if (surface->mSurface == NULL && surface->getISurfaceTexture() == NULL) {
  15.         surface = 0;
  16.     }
  17.     cleanCachedSurfacesLocked();
  18.     return surface;
  19. }

应用程序进程中的1号Surface按相反顺序读取WMS服务端返回过来的Binder对象等数据,并构造一个native层的Surface对象。

  1. Surface::Surface(const Parcel& parcel, const sp<IBinder>& ref)
  2.     : SurfaceTextureClient()
  3. {
  4.     mSurface = interface_cast<ISurface>(ref);
  5.     sp<IBinder> st_binder(parcel.readStrongBinder());
  6.     sp<ISurfaceTexture> st;
  7.     if (st_binder != NULL) {
  8.         st = interface_cast<ISurfaceTexture>(st_binder);
  9.     } else if (mSurface != NULL) {
  10.         st = mSurface->getSurfaceTexture();
  11.     }
  12.     mIdentity   = parcel.readInt32();
  13.     init(st);
  14. }

每个Activity可以有一个或多个Surface,默认情况下一个Activity只有一个Surface,当Activity中使用SurfaceView时,就存在多个Surface。Activity默认surface是在relayoutWindow过程中由WMS服务创建的,然后回传给应用程序进程,我们知道一个Surface其实就是应用程序端的本地窗口,关于Surface的初始化过程这里就不在介绍。

performLayout

frameworksbasecorejavaandroidviewViewRootImpl.java

  1. private void performLayout() {
  2.     mLayoutRequested = false;
  3.     mScrollMayChange = true;
  4.     final View host = mView;
  5.     if (DEBUG_ORIENTATION || DEBUG_LAYOUT) {
  6.         Log.v(TAG, “Laying out “ + host + ” to (“ +
  7.                 host.getMeasuredWidth() + “, “ + host.getMeasuredHeight() + “)”);
  8.     }
  9.     Trace.traceBegin(Trace.TRACE_TAG_VIEW, “layout”);
  10.     try {
  11.         host.layout(00, host.getMeasuredWidth(), host.getMeasuredHeight());
  12.     } finally {
  13.         Trace.traceEnd(Trace.TRACE_TAG_VIEW);
  14.     }
  15. }

performDraw

frameworksbasecorejavaandroidview ViewRootImpl.java

  1. private void performDraw() {
  2.     if (!mAttachInfo.mScreenOn && !mReportNextDraw) {
  3.         return;
  4.     }
  5.     final boolean fullRedrawNeeded = mFullRedrawNeeded;
  6.     mFullRedrawNeeded = false;
  7.     mIsDrawing = true;
  8.     Trace.traceBegin(Trace.TRACE_TAG_VIEW, “draw”);
  9.     try {
  10.         draw(fullRedrawNeeded);
  11.     } finally {
  12.         mIsDrawing = false;
  13.         Trace.traceEnd(Trace.TRACE_TAG_VIEW);
  14.     }
  15.     …
  16. }

 

  1. private void draw(boolean fullRedrawNeeded) {
  2.     Surface surface = mSurface;
  3.     if (surface == null || !surface.isValid()) {
  4.         return;
  5.     }
  6.     …
  7.     if (!dirty.isEmpty() || mIsAnimating) {
  8.         //使用硬件渲染
  9.         if (attachInfo.mHardwareRenderer != null && attachInfo.mHardwareRenderer.isEnabled()) {
  10.             // Draw with hardware renderer.
  11.             mIsAnimating = false;
  12.             mHardwareYOffset = yoff;
  13.             mResizeAlpha = resizeAlpha;
  14.             mCurrentDirty.set(dirty);
  15.             mCurrentDirty.union(mPreviousDirty);
  16.             mPreviousDirty.set(dirty);
  17.             dirty.setEmpty();
  18.             if (attachInfo.mHardwareRenderer.draw(mView, attachInfo, this,
  19.                     animating ? null : mCurrentDirty)) {
  20.                 mPreviousDirty.set(00, mWidth, mHeight);
  21.             }
  22.         //使用软件渲染
  23.         } else if (!drawSoftware(surface, attachInfo, yoff, scalingRequired, dirty)) {
  24.             return;
  25.         }
  26.     }
  27.     …
  28. }

窗口添加过程

frameworksbaseservicesjavacomandroidserverwmSession.java

  1. public int add(IWindow window, int seq, WindowManager.LayoutParams attrs,
  2.         int viewVisibility, Rect outContentInsets, InputChannel outInputChannel) {
  3.     return mService.addWindow(this, window, seq, attrs, viewVisibility, outContentInsets,
  4.             outInputChannel);
  5. }

frameworksbaseservicesjavacomandroidserverwmWindowManagerService.java

  1. public int addWindow(Session session, IWindow client, int seq,
  2.         WindowManager.LayoutParams attrs, int viewVisibility,
  3.         Rect outContentInsets, InputChannel outInputChannel) {
  4.     //client为IWindow的代理对象,是Activity在WMS服务中的唯一标示
  5.     int res = mPolicy.checkAddPermission(attrs);
  6.     if (res != WindowManagerImpl.ADD_OKAY) {
  7.         return res;
  8.     }
  9.     boolean reportNewConfig = false;
  10.     WindowState attachedWindow = null;
  11.     WindowState win = null;
  12.     long origId;
  13.     synchronized(mWindowMap) {
  14.         if (mDisplay == null) {
  15.             throw new IllegalStateException(“Display has not been initialialized”);
  16.         }
  17.         //判断窗口是否已经存在
  18.         if (mWindowMap.containsKey(client.asBinder())) {
  19.             Slog.w(TAG, “Window “ + client + ” is already added”);
  20.             return WindowManagerImpl.ADD_DUPLICATE_ADD;
  21.         }
  22.         //如果添加的是应用程序窗口
  23.         if (attrs.type >= FIRST_SUB_WINDOW && attrs.type <= LAST_SUB_WINDOW) {
  24.             //根据attrs.token从mWindowMap中取出应用程序窗口在WMS服务中的描述符WindowState
  25.             attachedWindow = windowForClientLocked(null, attrs.token, false);
  26.             if (attachedWindow == null) {
  27.                 Slog.w(TAG, “Attempted to add window with token that is not a window: “
  28.                       + attrs.token + “.  Aborting.”);
  29.                 return WindowManagerImpl.ADD_BAD_SUBWINDOW_TOKEN;
  30.             }
  31.             if (attachedWindow.mAttrs.type >= FIRST_SUB_WINDOW
  32.                     && attachedWindow.mAttrs.type <= LAST_SUB_WINDOW) {
  33.                 Slog.w(TAG, “Attempted to add window with token that is a sub-window: “
  34.                         + attrs.token + “.  Aborting.”);
  35.                 return WindowManagerImpl.ADD_BAD_SUBWINDOW_TOKEN;
  36.             }
  37.         }
  38.         boolean addToken = false;
  39.         //根据attrs.token从mWindowMap中取出应用程序窗口在WMS服务中的描述符WindowState
  40.         WindowToken token = mTokenMap.get(attrs.token);
  41.         if (token == null) {
  42.             …
  43.             ①token = new WindowToken(this, attrs.token, –1false);
  44.             addToken = true;
  45.         }
  46.         //应用程序窗口
  47.         else if (attrs.type >= FIRST_APPLICATION_WINDOW
  48.                 && attrs.type <= LAST_APPLICATION_WINDOW) {
  49.             AppWindowToken atoken = token.appWindowToken;
  50.             …
  51.         }
  52.         //输入法窗口
  53.         else if (attrs.type == TYPE_INPUT_METHOD) {
  54.             …
  55.         }
  56.         //墙纸窗口
  57.         else if (attrs.type == TYPE_WALLPAPER) {
  58.             …
  59.         }
  60.         //Dream窗口
  61.         else if (attrs.type == TYPE_DREAM) {
  62.             …
  63.         }
  64.         //为Activity窗口创建WindowState对象
  65.         ②win = new WindowState(this, session, client, token,
  66.                 attachedWindow, seq, attrs, viewVisibility);
  67.         …
  68.         if (outInputChannel != null && (attrs.inputFeatures& WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
  69.             String name = win.makeInputChannelName();
  70.             InputChannel[] inputChannels = InputChannel.openInputChannelPair(name);
  71.             win.setInputChannel(inputChannels[0]);
  72.             inputChannels[1].transferTo(outInputChannel);
  73.             mInputManager.registerInputChannel(win.mInputChannel, win.mInputWindowHandle);
  74.         }
  75.         …
  76.         //以键值对<IWindow.Proxy/Token,WindowToken>形式保存到mTokenMap表中
  77.         if (addToken) {
  78.             ③mTokenMap.put(attrs.token, token);
  79.         }
  80.         ④win.attach();
  81.         //以键值对<IWindow的代理对象,WindowState>形式保存到mWindowMap表中
  82.         ⑤mWindowMap.put(client.asBinder(), win);
  83.         …
  84.     }
  85.     …
  86.     return res;
  87. }

我们知道当应用程序进程添加一个DecorView到窗口管理器时,会为当前添加的窗口创建ViewRootImpl对象,同时构造了一个W本地Binder对象,无论是窗口视图对象DecorView还是ViewRootImpl对象,都只是存在于应用程序进程中,在添加窗口过程中仅仅将该窗口的W对象传递给WMS服务,经过Binder传输后,到达WMS服务端进程后变为IWindow.Proxy代理对象,因此该函数的参数client的类型为IWindow.Proxy。参数attrs的类型为WindowManager.LayoutParams,在应用程序进程启动Activity时,handleResumeActivity()函数通过WindowManager.LayoutParams
l = r.window.getAttributes();来得到应用程序窗口布局参数,由于WindowManager.LayoutParams实现了Parcelable接口,因此WindowManager.LayoutParams对象可以跨进程传输,WMS服务的addWindow函数中的attrs参数就是应用程序进程发送过来的窗口布局参数。在LocalWindowManager的addView函数中为窗口布局参数设置了相应的token,如果是应用程序窗口,则布局参数的token设为W本地Binder对象。如果不是应用程序窗口,同时当前窗口没有父窗口,则设置token为当前窗口的IApplicationToken.Proxy代理对象,否则设置为父窗口的IApplicationToken.Proxy代理对象,由于应用程序和WMS分属于两个不同的进程空间,因此经过Binder传输后,布局参数的令牌attrs.token就转变为IWindow.Proxy或者Token。以上函数首先根据布局参数的token等信息构造一个WindowToken对象,然后在构造一个WindowState对象,并将添加的窗口信息记录到mTokenMap和mWindowMap哈希表中。


在WMS服务端创建了所需对象后,接着调用了WindowState的attach()来进一步完成窗口添加。
frameworksbaseservicesjavacomandroidserverwmWindowState.java

  1. void attach() {
  2.     if (WindowManagerService.localLOGV) Slog.v(
  3.         TAG, “Attaching “ + this + ” token=” + mToken
  4.         + “, list=” + mToken.windows);
  5.     mSession.windowAddedLocked();
  6. }

frameworksbaseservicesjavacomandroidserverwmSession.java

  1. void windowAddedLocked() {
  2.     if (mSurfaceSession == null) {
  3.         mSurfaceSession = new SurfaceSession();
  4.         if (WindowManagerService.SHOW_TRANSACTIONS) Slog.i(
  5.                 WindowManagerService.TAG, ”  NEW SURFACE SESSION “ + mSurfaceSession);
  6.         mService.mSessions.add(this);
  7.     }
  8.     mNumWindow++;//记录对应的某个应用程序添加的窗口数量
  9. }

到此一个新的应用程序窗口就添加完成了。总结一下:

应用程序通过IWindowSession接口请求WMS服务添加一个应用程序窗口,WMS服务首先在自己服务进程为应用程序创建创建一个对应的WindowState描述符,然后保存到成员变量mWindowMap中。如果还没有为应用程序进程创建连接SurfaceFlinger的会话,就接着创建该会话通道SurfaceSession,我们知道,Activity中的视图所使用的画布Surface是在WMS服务进程中创建的,但是该画布所使用的图形buffer确实在SurfaceFlinger进程中分配管理的,而图形的绘制确是在应用程序进程中完成的,所以Activity的显示过程需要三个进程的配合才能完成。应用程序进程只与WMS服务进程交互,并不直接和SurfaceFlinger进程交互,而是由WMS服务进程同SurfaceFlinger进程配合。前面我们介绍了应用程序进程是通过IWindowSession接口与WMS服务进程通信的,那WMS服务是如何与SurfaceFlinger进程通信的呢,这就是windowAddedLocked函数要完成的工作。

在windowAddedLocked函数中使用单例模式创建一个SurfaceSession对象,在构造该对象时,通过JNI在native层创建一个与SurfaceFlinger进程的连接。

frameworksbasecorejavaandroidviewSurfaceSession.java

  1. public SurfaceSession() {
  2.     init();
  3. }

该init()函数是一个native函数,其JNI实现如下:

frameworksbasecorejni android_view_Surface.cpp

  1. static void SurfaceSession_init(JNIEnv* env, jobject clazz)
  2. {
  3.     sp<SurfaceComposerClient> client = new SurfaceComposerClient;
  4.     client->incStrong(clazz);
  5.     env->SetIntField(clazz, sso.client, (int)client.get());
  6. }

该函数构造了一个SurfaceComposerClient对象,在第一次强引用该对象时,会请求SurfaceFlinger创建一个专门处理当前应用程序进程请求的Client会话。

每个应用程序进程都持有一个与WMS服务会话通道IWindowSession,而服务端的Session有且只有一个SurfaceSession对象。系统中创建的所有IWindowSession都被记录到WMS服务的mSessions成员变量中,这样WMS就可以知道自己正在处理那些应用程序的请求。到此我们来梳理一下在WMS服务端都创建了那些对象:

1)        WindowState对象,是应用程序窗口在WMS服务端的描述符;

2)        Session对象,应用程序进程与WMS服务会话通道;

3)        SurfaceSession对象,应用程序进程与SurfaceFlinger的会话通道;

 

iOS多线程编程指南(二)线程管理

线程管理

Mac OS X和iOS里面的每个进程都是有一个或多个线程构成,每个线程都代表一个代码的执行路径。每个应用程序启动时候都是一个线程,它执行程序的main函数。应用程序可以生成额外的线程,其中每个线程执行一个特定功能的代码。

当应用程序生成一个新的线程的时候,该线程变成应用程序进程空间内的一个实体。每个线程都拥有它自己的执行堆栈,由内核调度独立的运行时间片。一个线程可以和其他线程或其他进程通信,执行I/O操作,甚至执行任何你想要它完成的任务。因为它们处于相同的进程空间,所以一个独立应用程序里面的所有线程共享相同的虚拟内存空间,并且具有和进程相同的访问权限。

本章提供了Mac OS X和iOS上面可用线程技术的预览,并给出了如何在你的应用程序里面使用它们的例子。

注意:获取关于Mac OS上面线程架构,或者更多关于线程的背景资料。请参阅技术说明TN2028 –“线程架构”。

1.1        线程成本

多线程会占用你应用程序(和系统的)的内存使用和性能方面的资源。每个线程都需要分配一定的内核内存和应用程序内存空间的内存。管理你的线程和协调其调度所需的核心数据结构存储在使用Wired Memory的内核里面。你线程的堆栈空间和每个线程的数据都被存储在你应用程序的内存空间里面。这些数据结构里面的大部分都是当你首次创建线程或者进程的时候被创建和初始化的,它们所需的代价成本很高,因为需要和内核交互。

表2-1量化了在你应用程序创建一个新的用户级线程所需的大致成本。这些成本里面的部分是可配置的,比如为辅助线程分配堆栈空间的大小。创建一个线程所需的时间成本是粗略估计的,仅用于当互相比较的时候。线程创建时间很大程度依赖于处理器的负载,计算速度,和可用的系统和程序空间。

Table 2-1  Thread creation costs

Item

Approximate cost

Notes

Kernel data structures

Approximately 1 KB

This memory is used to store the thread data structures and attributes, much of which is allocated as wired memory and therefore cannot be paged to disk.

Stack space

512 KB (secondary threads)

8 MB (Mac OS X main thread)

1 MB (iOS main thread)

The minimum allowed stack size for secondary threads is 16 KB and the stack size must be a multiple of 4 KB. The space for this memory is set aside in your process space at thread creation time, but the actual pages associated with that memory are not created until they are needed.

Creation time

Approximately 90 microseconds

This value reflects the time between the initial call to create the thread and the time at which the thread’s entry point routine began executing. The figures were determined by analyzing the mean and median values generated during thread creation on an Intel-based iMac with a 2 GHz Core Duo processor and 1 GB of RAM running Mac OS X v10.5.

注意:因为底层内核的支持,操作对象(Operation objectis)可能创建线程更快。它们使用内核里面常驻线程池里面的线程来节省创建的时间,而不是每次都创建新的线程。关于更多使用操作对象(Operation objects)的信息,参阅并发编程指南(Concurrency Programming Guide)。

当编写线程代码时另外一个需要考虑的成本是生产成本。设计一个线程应用程序有时会需要根本性改变你应用程序数据结构的组织方式。要做这些改变可能需要避免使用同步,因为本身设计不好的应用可能会造成巨大的性能损失。设计这些数据结构和在线程代码里面调试问题会增加开发一个线程应用所需的时间。然而避免这些消耗的话,可能在运行时候带来更大的问题,如果你的多线程花费太多的时间在锁的等待而没有做任何事情。

1.1        创建一个线程

创建低级别的线程相对简单。在所有情况下,你必须有一个函数或方法作为线程的主入口点,你必须使用一个可用的线程例程启动你的线程。以下几个部分介绍了比较常用线程创建的基本线程技术。线程创建使用了这些技术的继承属性的默认设置,由你所使用的技术来决定。关于更多如何配置你的线程的信息,参阅“线程属性配置”部分。

1.1.1    使用NSThread

使用NSThread来创建线程有两个可以的方法:

  1. 使用detachNewThreadSelector:toTarget:withObject:类方法来生成一个新的线程。
  2. 创建一个新的NSThread对象,并调用它的start方法。(仅在iOS和Mac OS X v10.5及其之后才支持)

这两种创建线程的技术都在你的应用程序里面新建了一个脱离的线程。一个脱离的线程意味着当线程退出的时候线程的资源由系统自动回收。这也同样意味着之后不需要在其他线程里面显式的连接(join)。因为detachNewThreadSelctor:toTarget:withObject:方法在Mac OS X的任何版本都支持,所以在Cocoa应用里面使用多线程的地方经常可以发现它。为了生成一个新的线程,你只要简单的提供你想要使用为线程主体入口的方法的名称(被指定为一个selector),和任何你想在启动时传递给线程的数据。下面的示例演示了这种方法的基本调用,来使用当前对象的自定义方法来生成一个线程。

1
[NSThread detachNewThreadSelector:@selector(myThreadMainMethod:) toTarget:self withObject:nil];

在Mac OS X v10.5之前,你使用NSThread类来生成多线程。虽然你可以获取一个NSThread对象并访问线程的属性,但你只能在线程运行之后在其内部做到这些。在Mac OS X v10.5支持创建一个NSThread对象,而无需立即生成一个相应的新线程(这些在iOS里面同样可用)。新版支持使得在线程启动之前获取并设置线程的很多属性成为可能。这也让用线程对象来引用正在运行的线程成为可能。

在Mac OS X v10.5及其之后初始化一个NSThread对象的简单方法是使用initWithTarget:selector:object:方法。该方法和detachNewThreadSelector:toTarget:withObject:方法来初始化一个新的NSThread实例需要相同的额外开销。然而它并没有启动一个线程。为了启动一个线程,你可以显式调用先对象的start方法,如下面代码:

1
2
3
4
NSThread* myThread = [[NSThread alloc] initWithTarget:self
                                        selector:@selector(myThreadMainMethod:)
                                        object:nil];
[myThread start];  // Actually create the thread

注意:使用initWithTarget:selector:object:方法的替代办法是子类化NSThread,并重写它的main方法。你可以使用你重写的该方法的版本来实现你线程的主体入口。更多信息,请参阅NSThread Class Reference里面子类化的提示。

如果你拥有一个NSThread对象,它的线程当前真正运行,你可以给该线程发送消息的唯一方法是在你应用程序里面的任何对象使用performSelector:onThread:withObject:waitUntilDone:方法。在Mac OS X v10.5支持在多线程上面执行selectors(而不是在主线程里面),并且它是实现线程间通信的便捷方法。你使用该技术时所发送的消息会被其他线程作为run-loop主体的一部分直接执行(当然这些意味着目标线程必须在它的run loop里面运行,参阅“ Run Loops”)。当你使用该方法来实现线程通信的时候,你可能仍然需要一个同步操作,但是这比在线程间设置通信端口简单多了。

注意:虽然在线程间的偶尔通信的时候使用该方法很好,但是你不能周期的或频繁的使用performSelector:onThread:withObject:waitUntilDone:来实现线程间的通信。

关于线程间通信的可选方法,参阅“设置线程的脱离状态”部分。

1.1.2    使用POSIX的多线程

Mac OS X和iOS提供基于C语言支持的使用POSIX线程API来创建线程的方法。该技术实际上可以被任何类型的应用程序使用(包括Cocoa和Cocoa Touch的应用程序),并且如果你当前真为多平台开发应用的话,该技术可能更加方便。你使用来创建线程的POSIX例程被调用的时候,使用pthread_create刚好足够。

列表2-1显示了两个使用POSIX来创建线程的自定义函数。LaunchThread函数创建了一个新的线程,该线程的例程由PosixThreadMainRoutine函数来实现。因为POSIX创建的线程默认情况是可连接的(joinable),下面的例子改变线程的属性来创建一个脱离的线程。把线程标记为脱离的,当它退出的时候让系统有机会立即回收该线程的资源。

Listing 2-1  Creating a thread in C

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
#include  <assert.h>
#include  <pthread.h>
void* PosixThreadMainRoutine(void* data)
{
    // Do some work here.
    return NULL;
}
void LaunchThread()
{
    // Create the thread using POSIX routines.
    pthread_attr_t  attr;
    pthread_t       posixThreadID;
    int             returnVal;
    returnVal = pthread_attr_init(&attr);
    assert(!returnVal);
    returnVal = pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
    assert(!returnVal);
    int     threadError = pthread_create(&posixThreadID, &attr, &PosixThreadMainRoutine, NULL);
    returnVal = pthread_attr_destroy(&attr);
    assert(!returnVal);
    if (threadError != 0)
    {
         // Report an error.
    }
}

如果你把上面列表的代码添加到你任何一个源文件,并且调用LaunchThread函数,它将会在你的应用程序里面创建一个新的脱离线程。当然,新创建的线程使用该代码没有做任何有用的事情。线程将会加载并立即退出。为了让它更有兴趣,你需要添加代码到PosixThreadMainRoutine函数里面来做一些实际的工作。为了保证线程知道该干什么,你可以在创建的时候给线程传递一个数据的指针。把该指针作为pthread_create的最后一个参数。

为了在新建的线程里面和你应用程序的主线程通信,你需要建立一条和目标线程之间的稳定的通信路径。对于基于C语言的应用程序,有几种办法来实现线程间的通信,包括使用端口(ports),条件(conditions)和共享内存(shared memory)。对于长期存在的线程,你应该几乎总是成立某种线程间的通信机制,让你的应用程序的主线程有办法来检查线程的状态或在应用程序退出时干净关闭它。

关于更多介绍POSIX线程函数的信息,参阅pthread的主页。

1.1.3    使用NSObject来生成一个线程

在iOS和Mac OS X v10.5及其之后,所有的对象都可能生成一个新的线程,并用它来执行它任意的方法。方法performSelectorInBackground:withObject:新生成一个脱离的线程,使用指定的方法作为新线程的主体入口点。比如,如果你有一些对象(使用变量myObj来代表),并且这些对象拥有一个你想在后台运行的doSomething的方法,你可以使用如下的代码来生成一个新的线程:

1
[myObj performSelectorInBackground:@selector(doSomething) withObject:nil];

调用该方法的效果和你在当前对象里面使用NSThread的detachNewThreadSelector:toTarget:withObject:传递selectore,object作为参数的方法一样。新的线程将会被立即生成并运行,它使用默认的设置。在selectore内部,你必须配置线程就像你在任何线程里面一样。比如,你可能需要设置一个自动释放池(如果你没有使用垃圾回收机制),在你要使用它的时候配置线程的run loop。关于更是介绍如果配置线程的信息,参阅“配置线程属性”部分。

1.1.4    使用其他线程技术

尽管POSIX例程和NSThread类被推荐使用来创建低级线程,但是其他基于C语言的技术在Mac OS X上面同样可用。在这其中,唯一一个可以考虑使用的是多处理服务(Multiprocessing Services),它本身就是在POSIX线程上执行。多处理服务是专门为早期的Mac OS版本开发的,后来在Mac OS X里面的Carbon应用程序上面同样适用。如果你有代码真是有该技术,你可以继续使用它,尽管你应该把这些代码转化为POSIX。该技术在iOS上面不可用。

关于更多如何使用多处理服务的信息,参阅多处理服务编程指南(Multiprocessing Services Programming Guide)

1.1.5    在Cocoa程序上面使用POSIX线程

经管NSThread类是Cocoa应用程序里面创建多线程的主要接口,如果可以更方便的话你可以任意使用POSIX线程带替代。例如,如果你的代码里面已经使用了它,而你又不想改写它的话,这时你可能需要使用POSIX多线程。如果你真打算在Cocoa程序里面使用POSIX线程,你应该了解如果在Cocoa和线程间交互,并遵循以下部分的一些指南。

u  Cocoa框架的保护

对于多线程的应用程序,Cocoa框架使用锁和其他同步方式来保证代码的正确执行。为了保护这些锁造成在单线程里面性能的损失,Cocoa直到应用程序使用NSThread类生成它的第一个新的线程的时候才创建这些锁。如果你仅且使用POSIX例程来生成新的线程,Cocoa不会收到关于你的应用程序当前变为多线程的通知。当这些刚好发生的时候,涉及Cocoa框架的操作哦可能会破坏甚至让你的应用程序崩溃。

为了让Cocoa知道你正打算使用多线程,你所需要做的是使用NSThread类生成一个线程,并让它立即退出。你线程的主体入口点不需要做任何事情。只需要使用NSThread来生成一个线程就足够保证Cocoa框架所需的锁到位。

如果你不确定Cocoa是否已经知道你的程序是多线程的,你可以使用NSThread的isMultiThreaded方法来检验一下。

u  混合POSIX和Cocoa的锁

在同一个应用程序里面混合使用POSIX和Cocoa的锁很安全。Cocoa锁和条件对象基本上只是封装了POSIX的互斥体和条件。然而给定一个锁,你必须总是使用同样的接口来创建和操纵该锁。换言之,你不能使用Cocoa的NSLock对象来操纵一个你使用pthread_mutex_init函数生成的互斥体,反之亦然。

1.2        配置线程属性

创建线程之后,或者有时候是之前,你可能需要配置不同的线程环境。以下部分描述了一些你可以做的改变,和在什么时候你需要做这些改变。

1.2.1    配置线程的堆栈大小

对于每个你新创建的线程,系统会在你的进程空间里面分配一定的内存作为该线程的堆栈。该堆栈管理堆栈帧,也是任何线程局部变量声明的地方。给线程分配的内存大小在“线程成本”里面已经列举了。

如果你想要改变一个给定线程的堆栈大小,你必须在创建该线程之前做一些操作。所有的线程技术提供了一些办法来设置线程堆栈的大小。虽然可以使用NSThread来设置堆栈大小,但是它只能在iOS和Mac OS X v10.5及其之后才可用。表2-2列出了每种技术的对于不同的操作。

Table 2-2  Setting the stack size of a thread

Technology Option
Cocoa In iOS and Mac OS X v10.5 and later, allocate and initialize an NSThread object (do not use thedetachNewThreadSelector:toTarget:withObject: method). Before calling the start method of the thread object, use thesetStackSize: method to specify the new stack size.
POSIX Create a new pthread_attr_t structure and use the pthread_attr_setstacksize function to change the default stack size. Pass the attributes to the pthread_create function when creating your thread.
Multiprocessing Services Pass the appropriate stack size value to the MPCreateTask function when you create your thread.

1.2.2    配置线程本地存储

每个线程都维护了一个键-值的字典,它可以在线程里面的任何地方被访问。你可以使用该字典来保存一些信息,这些信息在整个线程的执行过程中都保持不变。比如,你可以使用它来存储在你的整个线程过程中Run loop里面多次迭代的状态信息。

Cocoa和POSIX以不同的方式保存线程的字典,所以你不能混淆并同时调用者两种技术。然而只要你在你的线程代码里面坚持使用了其中一种技术,最终的结果应该是一样的。在Cocoa里面,你使用NSThread的threadDictionary方法来检索一个NSMutableDictionary对象,你可以在它里面添加任何线程需要的键。在POSIX里面,你使用pthread_setspecific和pthread_getspecific函数来设置和访问你线程的键和值。

1.2.3    设置线程的脱离状态

大部分上层的线程技术都默认创建了脱离线程(Datached thread)。大部分情况下,脱离线程(Detached thread)更受欢迎,因为它们允许系统在线程完成的时候立即释放它的数据结构。脱离线程同时不需要显示的和你的应用程序交互。意味着线程检索的结果由你来决定。相比之下,系统不回收可连接线程(Joinable thread)的资源直到另一个线程明确加入该线程,这个过程可能会阻止线程执行加入。

你可以认为可连接线程类似于子线程。虽然你作为独立线程运行,但是可连接线程在它资源可以被系统回收之前必须被其他线程连接。可连接线程同时提供了一个显示的方式来把数据从一个正在退出的线程传递到其他线程。在它退出之前,可连接线程可以传递一个数据指针或者其他返回值给pthread_exit函数。其他线程可以通过pthread_join函数来拿到这些数据。

重要:在应用程序退出时,脱离线程可以立即被中断,而可连接线程则不可以。每个可连接线程必须在进程被允许可以退出的时候被连接。所以当线程处于周期性工作而不允许被中断的时候,比如保存数据到硬盘,可连接线程是最佳选择。

如果你想要创建可连接线程,唯一的办法是使用POSIX线程。POSIX默认创建的线程是可连接的。为了把线程标记为脱离的或可连接的,使用pthread_attr_setdetachstate函数来修改正在创建的线程的属性。在线程启动后,你可以通过调用pthread_detach函数来把线程修改为可连接的。关于更多POSIX线程函数信息,参与pthread主页。关于更多如果连接一个线程,参阅pthread_join的主页。

1.2.4    设置线程的优先级

你创建的任何线程默认的优先级是和你本身线程相同。内核调度算法在决定该运行那个线程时,把线程的优先级作为考量因素,较高优先级的线程会比较低优先级的线程具有更多的运行机会。较高优先级不保证你的线程具体执行的时间,只是相比较低优先级的线程,它更有可能被调度器选择执行而已。

重要:让你的线程处于默认优先级值是一个不错的选择。增加某些线程的优先级,同时有可能增加了某些较低优先级线程的饥饿程度。如果你的应用程序包含较高优先级和较低优先级线程,而且它们之间必须交互,那么较低优先级的饥饿状态有可能阻塞其他线程,并造成性能瓶颈。

如果你想改变线程的优先级,Cocoa和POSIX都提供了一种方法来实现。对于Cocoa线程而言,你可以使用NSThread的setThreadPriority:类方法来设置当前运行线程的优先级。对于POSIX线程,你可以使用pthread_setschedparam函数来实现。关于更多信息,参与NSThread Class Reference或pthread_setschedparam主页。

1.3        编写你线程的主体入口点

对于大部分而言,Mac OS X上面线程结构的主体入口点和其他平台基本一样。你需要初始化你的数据结构,做一些工作或可行的设置一个run loop,并在线程代码被执行完后清理它。根据设计,当你写的主体入口点的时候有可能需要采取一些额外的步骤。

1.3.1    创建一个自动释放池(Autorelease Pool)

在Objective – C框架链接的应用程序,通常在它们的每一个线程必须创建至少一个自动释放池。如果应用程序使用管理模型,即应用程序处理的retain和release对象,那么自动释放池捕获任何从该线程autorelease的对象。

如果应用程序使用的垃圾回收机制,而不是管理的内存模型,那么创建一个自动释放池不是绝对必要的。在垃圾回收的应用程序里面,一个自动释放池是无害的,而且大部分情况是被忽略。允许通过个代码管理必须同时支持垃圾回收和内存管理模型。在这种情况下,内存管理模型必须支持自动释放池,当应用程序运行垃圾回收的时候,自动释放池只是被忽略而已。

如果你的应用程序使用内存管理模型,在你编写线程主体入口的时候第一件事情就是创建一个自动释放池。同样,在你的线程最后应该销毁该自动释放池。该池保证自动释放。虽然对象被调用,但是它们不被release直到线程退出。列表2-2显示了线程主体入口使用自动释放池的基本结构。

Listing 2-2  Defining your thread entry point routine

1
2
3
4
5
6
- (void)myThreadMainRoutine
{
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; // Top-level pool
    // Do thread work here.
    [pool release];  // Release the objects in the pool.
}

因为高级的自动释放池不会释放它的对象直到线程退出。长时运行的线程需求新建额外的自动释放池来更频繁的释放它的对象。比如,一个使用run loop的线程可能在每次运行完一次循环的时候创建并释放该自动释放池。更频繁的释放对象可以防止你的应用程序内存占用太大造成性能问题。虽然对于任何与性能相关的行为,你应该测量你代码的实际表现,并适当地调整使用自动释放池。

关于更多内存管理的信息和自动释放池,参阅“内存高级管理编程指南(Advanced Memory Management Programming Guide)”。

1.3.2    设置异常处理

如果你的应用程序捕获并处理异常,那么你的线程代码应该时刻准备捕获任何可能发生的异常。虽然最好的办法是在异常发生的地方捕获并处理它,但是如果在你的线程里面捕获一个抛出的异常失败的话有可能造成你的应用程序强退。在你线程的主体入口点安装一个try/catch模块,可以让你捕获任何未知的异常,并提供一个合适的响应。

当在Xcode构建你项目的时候,你可以使用C++或者Objective-C的异常处理风格。 关于更多设置如何在Objective-C里面抛出和捕获异常的信息,参阅Exception Programming Topics。

1.3.3    设置一个Run Loop

当你想编写一个独立运行的线程时,你有两种选择。第一种选择是写代码作为一个长期的任务,很少甚至不中断,线程完成的时候退出。第二种选择是把你的线程放入一个循环里面,让它动态的处理到来的任务请求。第一种方法不需要在你的代码指定任何东西;你只需要启动的时候做你打算做的事情即可。然而第二种选择需要在你的线程里面添加一个run loop。

Mac OS X和iOS提供了在每个线程实现run loop内置支持。Cocoa、Carbon和UIKit自动在你应用程序的主线程启动一个run loop,但是如果你创建任何辅助线程,你必须手工的设置一个run loop并启动它。

关于更多使用和配置run loop的信息,参阅“Run Loops”部分。

1.4        中断线程

退出一个线程推荐的方法是让它在它主体入口点正常退出。经管Cocoa、POSIX和Multiprocessing Services提供了直接杀死线程的例程,但是使用这些例程是强烈不鼓励的。杀死一个线程阻止了线程本身的清理工作。线程分配的内存可能造成泄露,并且其他线程当前使用的资源可能没有被正确清理干净,之后造成潜在的问题。

如果你的应用程序需要在一个操作中间中断一个线程,你应该设计你的线程响应取消或退出的消息。对于长时运行的操作,这意味着周期性停止工作来检查该消息是否到来。如果该消息的确到来并要求线程退出,那么线程就有机会来执行任何清理和退出工作;否则,它返回继续工作和处理下一个数据块。

响应取消消息的一个方法是使用run loop的输入源来接收这些消息。列表2-3显示了该结构的类似代码在你的线程的主体入口里面是怎么样的(该示例显示了主循环部分,不包括设立一个自动释放池或配置实际的工作步骤)。该示例在run loop上面安装了一个自定义的输入源,它可以从其他线程接收消息。关于更多设置输入源的信息,参阅“配置Run Loop源”。执行工作的总和的一部分后,线程运行的run loop来查看是否有消息抵达输入源。如果没有,run loop立即退出,并且循环继续处理下一个数据块。因为该处理器并没有直接的访问exitNow局部变量,退出条件是通过线程的字典来传输的。

Listing 2-3  Checking for an exit condition during a long job

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
- (void)threadMainRoutine
{
    BOOL moreWorkToDo = YES;
    BOOL exitNow = NO;
    NSRunLoop* runLoop = [NSRunLoop currentRunLoop];
    // Add the exitNow BOOL to the thread dictionary.
    NSMutableDictionary* threadDict = [[NSThread currentThread] threadDictionary];
    [threadDict setValue:[NSNumber numberWithBool:exitNow] forKey:@"ThreadShouldExitNow"];
    // Install an input source.
    [self myInstallCustomInputSource];
    while (moreWorkToDo && !exitNow)
    {
        // Do one chunk of a larger body of work here.
        // Change the value of the moreWorkToDo Boolean when done.
        // Run the run loop but timeout immediately if the input source isn't waiting to fire.
        [runLoop runUntilDate:[NSDate date]];
        // Check to see if an input source handler changed the exitNow value.
        exitNow = [[threadDict valueForKey:@"ThreadShouldExitNow"] boolValue];
    }
}

iOS多线程编程指南(一)关于多线程编程

第一章      关于多线程编程

多年来,计算机的最大性能主要受限于它的中心微处理器的速度。然而由于个别处理器已经开始达到它的瓶颈限制,芯片制造商开始转向多核设计,让计算机具有了同时执行多个任务的能力。尽管Mac OS X利用了这些核心优势,在任何时候可以执行系统相关的任务,但自己的应用程序也可以通过多线程方法利用这些优势。

1.1        什么是多线程

多线程是一个比较轻量级的方法来实现单个应用程序内多个代码执行路径。在系统级别内,程序并排执行,系统分配到每个程序的执行时间是基于该程序的所需时间和其他程序的所需时间来决定的。然而在每个应程序的内部,存在一个或多个执行线程,它同时或在一个几乎同时发生的方式里执行不同的任务。系统本身管理这些执行的线程,调度它们在可用的内核上运行,并在需要让其他线程执行的时候抢先打断它们。

从技术角度来看,一个线程就是一个需要管理执行代码的内核级和应用级数据结构组合。内核级结构协助调度线程事件,并抢占式调度一个线程到可用的内核之上。应用级结构包括用于存储函数调用的调用堆栈和应用程序需要管理和操作线程属性和状态的结构。

在非并发的应用程序,只有一个执行线程。该线程开始和结束于你应用程序的main循环,一个个方法和函数的分支构成了你整个应用程序的所有行为。与此相反,支持并发的应用程序开始可以在需要额外的执行路径时候创建一个或多个线程。每个新的执行路径有它自己独立于应用程序main循环的定制开始循环。在应用程序中存在多个线程提供了两个非常重要的的潜在优势:

  1. 多个线程可以提高应用程序的感知响应。
  2. 多个线程可以提高应用程序在多核系统上的实时性能。

如果你的应用程序只有单独的线程,那么该独立程序需要完成所有的事情。它必须对事件作出响应,更新您的应用程序的窗口,并执行所有实现你应用程序行为需要的计算。拥有单独线程的主要问题是在同一时间里面它只能执行一个任务。那么当你的应用程序需要很长时间才能完成的时候会发生什么呢?当你的代码忙于计算你所需要的值的时候,你的程序就会停止响应用户事件和更新它的窗口。如果这样的情况持续足够长的时间,用户就会误认为你的程序被挂起了,并试图强制退出。如果你把你的计算任务转移到一个独立的线程里面,那么你的应用程序主线程就可以自由并及时响应用户的交互。

当然多线程并不是解决程序性能问题的灵丹妙药。多线程带来好处同时也伴随着潜在问题。应用程序内拥有多个可执行路径,会给你的代码增加更多的复杂性。每个线程需要和其他线程协调其行为,以防止它破坏应用程序的状态信息。因为应用程序内的多个线程共享内存空间,它们访问相同的数据结构。如果两个线程试图同时处理相同的数据结构,一个线程有可能覆盖另外线程的改动导致破坏该数据结构。即使有适当的保护,你仍然要注意由于编译器的优化导致给你代码产生很微妙的(和不那么微妙)的Bug。

1.2        线程术语

在讨论多线程和它支持的相关技术之前,我们有必要先了解一些基本的术语。如果你熟悉Carbon的多处理器服务API或者UNIX系统的话,你会发现本文档里面“任务(task)”被用于不同的定义。在Mac OS的早期版本,术语“任务(task)”是用来区分使用多处理器服务创建的线程和使用Carbon线程管理API创建的线程。在UNIX系统里面,术语“任务(task)”也在一段时间内被用于指代运行的进程。在实际应用中,多处理器服务任务是相当于抢占式的线程。

由于Carbon线程管理器和多处理器服务API是Mac OS X的传统技术,本文件采用下列术语:

  1. 线程(线程)用于指代独立执行的代码段。
  2. 进程(process)用于指代一个正在运行的可执行程序,它可以包含多个线程。
  3. 任务(task)用于指代抽象的概念,表示需要执行工作。

 

 

1.3        多线程的替代方法

你自己创建多线程代码的一个问题就是它会给你的代码带来不确定性。多线程是一个相对较低的水平和复杂的方式来支持你的应用程序并发。如果你不完全理解你的设计选择的影响,你可能很容易遇到同步或定时问题,其范围可以从细微的行为变化到严重到让你的应用程序崩溃并破坏用户数据。

你需要考虑的另一个因素是你是否真的需要多线程或并发。多线程解决了如何在同一个进程内并发的执行多路代码路径的问题。然而在很多情况下你是无法保证你所在做的工作是并发的。多线程引入带来大量的开销,包括内存消耗和CPU占用。你会发现这些开销对于你的工作而言实在太大,或者有其他方法会更容易实现。

表1-1列举了多线程的替代方法。该表包含了多线程的替代技术(比如操作对象和GCD)和如何更高效的使用单个线程。

Table 1-1  Alternative technologies to threads

Technology Description
Operation objects Introduced in Mac OS X v10.5, an operation object is a wrapper for a task that would normally be executed on a secondary thread. This wrapper hides the thread management aspects of performing the task, leaving you free to focus on the task itself. You typically use these objects in conjunction with an operation queue object, which actually manages the execution of the operation objects on one more threads.
For more information on how to use operation objects, see Concurrency Programming Guide.

Grand Central Dispatch (GCD)

Introduced in Mac OS x v10.6, Grand Central Dispatch is another alternative to threads that lets you focus on the tasks you need to perform rather than on thread management. With GCD, you define the task you want to perform and add it to a work queue, which handles the scheduling of your task on an appropriate thread. Work queues take into account the number of available cores and the current load to execute your tasks more efficiently than you could do yourself using threads.
For information on how to use GCD and work queues, see Concurrency Programming Guide
Idle-time notifications For tasks that are relatively short and very low priority, idle time notifications let you perform the task at a time when your application is not as busy. Cocoa provides support for idle-time notifications using the NSNotificationQueue object. To request an idle-time notification, post a notification to the default NSNotificationQueue object using the NSPostWhenIdle option. The queue delays the delivery of your notification object until the run loop becomes idle. For more information, see Notification Programming Topics.
Asynchronous functions The system interfaces include many asynchronous functions that provide automatic concurrency for you. These APIs may use system daemons and processes or create custom threads to perform their task and return the results to you. (The actual implementation is irrelevant because it is separated from your code.) As you design your application, look for functions that offer asynchronous behavior and consider using them instead of using the equivalent synchronous function on a custom thread.
Timers You can use timers on your application’s main thread to perform periodic tasks that are too trivial to require a thread, but which still require servicing at regular intervals. For information on timers, see “Timer Sources.”
Separate processes Although more heavyweight than threads, creating a separate process might be useful in cases where the task is only tangentially related to your application. You might use a process if a task requires a significant amount of memory or must be executed using root privileges. For example, you might use a 64-bit server process to compute a large data set while your 32-bit application displays the results to the user.

注意:当使用fork函数加载独立进程的时候,你必须总是在fork后面调用exec或者类似的函数。基于Core Foundation、Cocao或者Core Data框架(无论显式还是隐式关联)的应用程序随后调用exec函数或者类似的函数都会导出不确定的结果。

1.4        线程支持

如果你已经有代码使用了多线程,Mac OS X和iOS提供几种技术来在你的应用程序里面创建多线程。此外,两个系统都提供了管理和同步你需要在这些线程里面处理的工作。以下几个部分描述了一些你在Mac OS X和iOS上面使用多线程的时候需要注意的关键技术。

1.4.1    线程包

虽然多线程的底层实现机制是Mach的线程,你很少(即使有)使用Mach级的线程。相反,你会经常使用到更多易用的POSIX 的API或者它的衍生工具。Mach的实现没有提供多线程的基本特征,但是包括抢占式的执行模型和调度线程的能力,所以它们是相互独立的。

列表1-2列举你可以在你的应用程序使用的线程技术。

Table 1-2  Thread technologies

Technology Description
Cocoa threads Cocoa implements threads using the NSThread class. Cocoa also provides methods on NSObject for spawning new threads and executing code on already-running threads. For more information, see “Using NSThread” and “Using NSObject to Spawn a Thread.”
POSIX threads POSIX threads provide a C-based interface for creating threads. If you are not writing a Cocoa application, this is the best choice for creating threads. The POSIX interface is relatively simple to use and offers ample flexibility for configuring your threads. For more information, see “Using POSIX Threads”
Multiprocessing Services Multiprocessing Services is a legacy C-based interface used by applications transitioning from older versions of Mac OS. This technology is available in Mac OS X only and should be avoided for any new development. Instead, you should use the NSThread class or POSIX threads. If you need more information on this technology, see Multiprocessing Services Programming Guide.

在应用层上,其他平台一样所有线程的行为本质上是相同的。线程启动之后,线程就进入三个状态中的任何一个:运行(running)、就绪(ready)、阻塞(blocked)。如果一个线程当前没有运行,那么它不是处于阻塞,就是等待外部输入,或者已经准备就绪等待分配CPU。线程持续在这三个状态之间切换,直到它最终退出或者进入中断状态。

当你创建一个新的线程,你必须指定该线程的入口点函数(或Cocoa线程时候为入口点方法)。该入口点函数由你想要在该线程上面执行的代码组成。但函数返回的时候,或你显式的中断线程的时候,线程永久停止,且被系统回收。因为线程创建需要的内存和时间消耗都比较大,因此建议你的入口点函数做相当数量的工作,或建立一个运行循环允许进行经常性的工作。

为了获取更多关于线程支持的可用技术并且如何使用它们,请阅读“线程管理部分”。

1.4.2    Run Loops

注:为了便于记忆,文本后面部分翻译Run Loops的时候基本采用原义,而非翻译为“运行循环”。

一个run loop是用来在线程上管理事件异步到达的基础设施。一个run loop为线程监测一个或多个事件源。当事件到达的时候,系统唤醒线程并调度事件到run loop,然后分配给指定程序。如果没有事件出现和准备处理,run loop把线程置于休眠状态。

你创建线程的时候不需要使用一个run loop,但是如果你这么做的话可以给用户带来更好的体验。Run Loops可以让你使用最小的资源来创建长时间运行线程。因为run loop在没有任何事件处理的时候会把它的线程置于休眠状态,它消除了消耗CPU周期轮询,并防止处理器本身进入休眠状态并节省电源。

为了配置run loop,你所需要做的是启动你的线程,获取run loop的对象引用,设置你的事件处理程序,并告诉run loop运行。Cocoa和Carbon提供的基础设施会自动为你的主线程配置相应的run loop。如果你打算创建长时间运行的辅助线程,那么你必须为你的线程配置相应的run loop。

关于run loops的详细信息和如何使用它们的例子会在“Run Loops”部分介绍。

1.4.3    同步工具

线程编程的危害之一是在多个线程之间的资源争夺。如果多个线程在同一个时间试图使用或者修改同一个资源,就会出现问题。缓解该问题的方法之一是消除共享资源,并确保每个线程都有在它操作的资源上面的独特设置。因为保持完全独立的资源是不可行的,所以你可能必须使用锁,条件,原子操作和其他技术来同步资源的访问。

锁提供了一次只有一个线程可以执行代码的有效保护形式。最普遍的一种锁是互斥排他锁,也就是我们通常所说的“mutex”。当一个线程试图获取一个当前已经被其他线程占据的互斥锁的时候,它就会被阻塞直到其他线程释放该互斥锁。系统的几个框架提供了对互斥锁的支持,虽然它们都是基于相同的底层技术。此外Cocoa提供了几个互斥锁的变种来支持不同的行为类型,比如递归。获取更多关于锁的种类的信息,请阅读“锁”部分内容。

除了锁,系统还提供了条件,确保在你的应用程序任务执行的适当顺序。一个条件作为一个看门人,阻塞给定的线程,直到它代表的条件变为真。当发生这种情况的时候,条件释放该线程并允许它继续执行。POSIX级别和基础框架都直接提供了条件的支持。(如果你使用操作对象,你可以配置你的操作对象之间的依赖关系的顺序确定任务的执行顺序,这和条件提供的行为非常相似)。

尽管锁和条件在并发设计中使用非常普遍,原子操作也是另外一种保护和同步访问数据的方法。原子操作在以下情况的时候提供了替代锁的轻量级的方法,其中你可以执行标量数据类型的数学或逻辑运算。原子操作使用特殊的硬件设施来保证变量的改变在其他线程可以访问之前完成。

获取更多关于可用同步工具信息,请阅读“同步工具”部分。

1.4.4    线程间通信

虽然一个良好的设计最大限度地减少所需的通信量,但在某些时候,线程之间的通信显得十分必要。(线程的任务是为你的应用程序工作,但如果从来没有使用过这些工作的结果,那有什么好处呢?)线程可能需要处理新的工作要求,或向你应用程序的主线程报告其进度情况。在这些情况下,你需要一个方式来从其他线程获取信息。幸运的是,线程共享相同的进程空间,意味着你可以有大量的可选项来进行通信。

线程间通信有很多种方法,每种都有它的优点和缺点。“配置线程局部存储”列出了很多你可以在Mac OS X上面使用的通信机制。(异常的消息队列和Cocoa分布式对象,这些技术也可在iOS用来通信)。本表中的技术是按照复杂性的顺序列出。

Table 1-3  Communication mechanisms

Mechanism Description
Direct messaging Cocoa applications support the ability to perform selectors directly on other threads. This capability means that one thread can essentially execute a method on any other thread. Because they are executed in the context of the target thread, messages sent this way are automatically serialized on that thread. For information about input sources, see “Cocoa Perform Selector Sources.”
Global variables, shared memory, and objects Another simple way to communicate information between two threads is to use a global variable, shared object, or shared block of memory. Although shared variables are fast and simple, they are also more fragile than direct messaging. Shared variables must be carefully protected with locks or other synchronization mechanisms to ensure the correctness of your code. Failure to do so could lead to race conditions, corrupted data, or crashes.
Conditions Conditions are a synchronization tool that you can use to control when a thread executes a particular portion of code. You can think of conditions as gate keepers, letting a thread run only when the stated condition is met. For information on how to use conditions, see “Using Conditions.”
Run loop sources A custom run loop source is one that you set up to receive application-specific messages on a thread. Because they are event driven, run loop sources put your thread to sleep automatically when there is nothing to do, which improves your thread’s efficiency. For information about run loops and run loop sources, see “Run Loops.”
Ports and sockets Port-based communication is a more elaborate way to communication between two threads, but it is also a very reliable technique. More importantly, ports and sockets can be used to communicate with external entities, such as other processes and services. For efficiency, ports are implemented using run loop sources, so your thread sleeps when there is no data waiting on the port. For information about run loops and about port-based input sources, see “Run Loops.”
Message queues The legacy Multiprocessing Services defines a first-in, first-out (FIFO) queue abstraction for managing incoming and outgoing data. Although message queues are simple and convenient, they are not as efficient as some other communications techniques. For more information about how to use message queues, see Multiprocessing Services Programming Guide.
Cocoa distributed objects Distributed objects is a Cocoa technology that provides a high-level implementation of port-based communications. Although it is possible to use this technology for inter-thread communication, doing so is highly discouraged because of the amount of overhead it incurs. Distributed objects is much more suitable for communicating with other processes, where the overhead of going between processes is already high. For more information, seeDistributed Objects Programming Topics.

1.1        设计技巧

以下各节帮助你实现自己的线程提供了指导,以确保你代码的正确性。部分指南同时提供如何利用你的线程代码获得更好的性能。任何性能的技巧,你应该在你更改你代码之前、期间、之后总是收集相关的性能统计数据。

1.1.1    避免显式创建线程

手动编写线程创建代码是乏味的,而且容易出现错误,你应该尽可能避免这样做。Mac OS X和iOS通过其他API接口提供了隐式的并发支持。你可以考虑使用异步API,GCD方式,或操作对象来实现并发,而不是自己创建一个线程。这些技术背后为你做了线程相关的工作,并保证是无误的。此外,比如GCD和操作对象技术被设计用来管理线程,比通过自己的代码根据当前的负载调整活动线程的数量更高效。 关于更多GCD和操作对象的信息,你可以查阅“并发编程指南(Concurrency Programming Guid)”。

1.1.2    保持你的线程合理的忙

如果你准备人工创建和管理线程,记得多线程消耗系统宝贵的资源。你应该尽最大努力确保任何你分配到线程的任务是运行相当长时间和富有成效的。同时你不应该害怕中断那些消耗最大空闲时间的线程。线程使用一个平凡的内存量,它的一些有线,所以释放一个空闲线程,不仅有助于降低您的应用程序的内存占用,它也释放出更多的物理内存使用的其他系统进程。线程占用一定量的内存,其中一些是有线的,所以释放空闲线程不但帮助你减少了你应用程序的内存印记,而且还能释放出更多的物理内存给其他系统进程使用。

重要:在你中断你的空闲线程开始之前,你必须总是记录你应用程序当前的性能基线测量。当你尝试修改后,采取额外的测量来确保你的修改实际上提高了性能,而不是对它操作损害。

1.1.3    避免共享数据结构

避免造成线程相关资源冲突的最简单最容易的办法是给你应用程序的每个线程一份它需求的数据的副本。当最小化线程之间的通信和资源争夺时并行代码的效果最好。

创建多线程的应用是很困难的。即使你非常小心,并且在你的代码里面所有正确的地方锁住共享资源,你的代码依然可能语义不安全的。比如,当在一个特定的顺序里面修改共享数据结构的时候,你的代码有可能遇到问题。以原子方式修改你的代码,来弥补可能随后对多线程性能产生损耗的情况。把避免资源争夺放在首位通常可以得到简单的设计同样具有高性能的效果。

1.1.4    多线程和你的用户界面

如果你的应用程序具有一个图形用户界面,建议你在主线程里面接收和界面相关的事件和初始化更新你的界面。这种方法有助于避免与处理用户事件和窗口绘图相关的同步问题。一些框架,比如Cocoa,通常需要这样操作,但是它的事件处理可以不这样做,在主线程上保持这种行为的优势在于简化了管理你应用程序用户界面的逻辑。

有几个显著的例外,它有利于在其他线程执行图形操作。比如,QuickTime API包含了一系列可以在辅助线程执行的操作,包括打开视频文件,渲染视频文件,压缩视频文件,和导入导出图像。类似的,在Carbon和Cocoa里面,你可以使用辅助线程来创建和处理图片和其他图片相关的计算。使用辅助线程来执行这些操作可以极大提高性能。如果你不确定一个操作是否和图像处理相关,那么你应该在主线程执行这些操作。

关于QuickTime线程安全的信息,查阅Technical Note TN2125:“QuickTime的线程安全编程”。关于Cocoa线程安全的更多信息,查阅“线程安全总结”。关于Cocoa绘画信息,查阅Cocoa绘画指南(Cocoa Drawing Guide)。

1.1.5    了解线程退出时的行为

进程一直运行直到所有非独立线程都已经退出为止。默认情况下,只有应用程序的主线程是以非独立的方式创建的,但是你也可以使用同样的方法来创建其他线程。当用户退出程序的时候,通常考虑适当的立即中断所有独立线程,因为通常独立线程所做的工作都是是可选的。如果你的应用程序使用后台线程来保存数据到硬盘或者做其他周期行的工作,那么你可能想把这些线程创建为非独立的来保证程序退出的时候不丢失数据。

以非独立的方式创建线程(又称作为可连接的)你需要做一些额外的工作。因为大部分上层线程封装技术默认情况下并没有提供创建可连接的线程,你必须使用POSIX API来创建你想要的线程。此外,你必须在你的主线程添加代码,来当它们最终退出的时候连接非独立的线程。更多有关创建可连接的线程信息,请查阅“设置线程的脱离状态”部分。

如果你正在编程Cocoa的程序,你也可以通过使用applicationShouldTerminate:的委托方法来延迟程序的中断直到一段时间后或者完成取消。当延迟中断的时候,你的程序需要等待直到任何周期线程已经完成它们的任务且调用了replyToApplicationShouldTerminate:方法。关于更多这些方法的信息,请查阅NSApplication Class Reference。

1.1.6    处理异常

当抛出一个异常时,异常的处理机制依赖于当前调用堆栈执行任何必要的清理。因为每个线程都有它自己的调用堆栈,所以每个线程都负责捕获它自己的异常。如果在辅助线程里面捕获一个抛出的异常失败,那么你的主线程也同样捕获该异常失败:它所属的进程就会中断。你无法捕获同一个进程里面其他线程抛出的异常。

如果你需要通知另一个线程(比如主线程)当前线程中的一个特殊情况,你应该捕捉异常,并简单地将消息发送到其他线程告知发生了什么事。根据你的模型和你正在尝试做的事情,引发异常的线程可以继续执行(如果可能的话),等待指示,或者干脆退出。

注意:在Cocoa里面,一个NSException对象是一个自包含对象,一旦它被引发了,那么它可以从一个线程传递到另外一个线程。

在一些情况下,异常处理可能是自动创建的。比如,Objective-C中的@synchronized包含了一个隐式的异常处理。

1.1.7    干净地中断你的线程

线程自然退出的最好方式是让它达到其主入口结束点。虽然有不少函数可以用来立即中断线程,但是这些函数应仅用于作为最后的手段。在线程达到它自然结束点之前中断一个线程阻碍该线程清理完成它自己。如果线程已经分配了内存,打开了文件,或者获取了其他类型资源,你的代码可能没办法回收这些资源,结果造成内存泄漏或者其他潜在的问题。

关于更多正确退出线程的信息,请查阅“中断线程”部分。

1.1.8    线程安全的库

虽然应用程序开发人员控制应用程序是否执行多个线程,类库的开发者则无法这样控制。当开发类库时,你必须假设调用应用程序是多线程,或者多线程之间可以随时切换。因此你应该总是在你的临界区使用锁功能。

对类库开发者而言,只当应用程序是多线程的时候才创建锁是不明智的。如果你需要锁定你代码中的某些部分,早期应该创建锁对象给你的类库使用,更好是显式调用初始化类库。虽然你也可以使用静态库的初始化函数来创建这些锁,但是仅当没有其他方式的才应该这样做。执行初始化函数需要延长加载你类库的时间,且可能对你程序性能造成不利影响。

注意:永远记住在你的类库里面保持锁和释放锁的操作平衡。你应该总是记住锁定类库的数据结构,而不是依赖调用的代码提供线程安全环境。

如果你真正开发Cocoa的类库,那么当你想在应用程序变成多线程的时候收到通知的话,你可以给NSWillBecomeMultiThreadedNotification 注册一个观察者。不过你不应用依赖于这些收到的通知,因为它们可能在你的类库被调用之前已经被发出了。