为什么不能往Android的Application对象里存储数据

在一个App里面总有一些数据需要在多个地方用到。这些数据可能是一个 session token,一次费时计算的结果等。通常为了避免activity之间传递对象的开销 ,这些数据一般都会保存到持久化存储里面

有人建议将这些数据保存到 Application 对象里面,这样这些数据对所有应用内的activities可用。这种方法简单,优雅而且……完全扯淡。

假设把你的数据都保存到Application对象里面去了,那么你的应用最后会以一个NullPointerException 异常crash掉。

一个简单的测试案例

代码

Application 对象:

// access modifiers omitted for brevity
class MyApplication extends Application {
 
    String name;
 
    String getName() {
        return name;
    }
 
    void setName(String name) {
        this.name = name;
    }
}

第一个activity, 我们往application对象里面存储了用户姓名:

// access modifiers omitted for brevity
class WhatIsYourNameActivity extends Activity {
 
    void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.writing);
 
        // Just assume that in the real app we would really ask it!
        MyApplication app = (MyApplication) getApplication();
        app.setName("Developer Phil");
        startActivity(new Intent(this, GreetLoudlyActivity.class));
 
    }
 
}

 

第二个activity,我们调用第一个activity设置并存在application里面的用户姓名:

// access modifiers omitted for brevity
class GreetLoudlyActivity extends Activity {
 
    TextView textview;
 
    void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
 
        setContentView(R.layout.reading);
        textview = (TextView) findViewById(R.id.message);
    }
 
    void onResume() {
        super.onResume();
 
        MyApplication app = (MyApplication) getApplication();
        textview.setText("HELLO " + app.getName().toUpperCase());
    }
}

 

测试场景

  1. 用户启动app。
  2. 在 WhatIsYourNameActivity里面,要求用户输入姓名,并存储到 MyApplication。
  3. 在 GreetLoudlyActivity里面,你从MyApplication 对象中获得用户姓名,并且显示。
  4. 用户按home键离开这个app。
  5. 几个小时后,Android系统为了回收内存kill掉了这个app。到目前为止,一切尚好。接下来就是crash的部分了…
  6. 用户重新打开这个App。
  7. Android系统创建一个新的 MyApplication 实例并恢复 GreetLoudlyActivity
  8. GreetLoudlyActivity 从新的 MyApplication 实例中获取用户姓名,可得到的为空,最后导致NullPointerException。

为什么会Crash?

在上面这个例子中,app会crash得原因是这个 Application 对象是全新的,所以这个name 变量里面的值为 null,当调用String#toUpperCase() 方法时就导致了NullPointerException。

整个问题的核心在于:application 对象不会一直呆着内存里面,它会被kill掉。与大家普遍的看法不同之处在于,实际上app不会重新开始启动。Android系统会创建一个新的Application 对象,然后启动上次用户离开时的activity以造成这个app从来没有被kill掉得假象。

你以为你的application可以保存数据,却没想到你的用户在没有打开activity A 之前就就直接打开了 activity B ,于是你就收到了一个 crash 的 surprise。

有哪些替代方法呢?

这里没啥神奇的解决方法,你可以试试下面几种方法:

  • 直接将数据通过intent传递给 Activity 。
  • 使用官方推荐的几种方式将数据持久化到磁盘上。
  • 在使用数据的时候总是要对变量的值进行非空检查。

如果模拟App被Kill掉

更新: Daniel Lew指出,kill app更简单的方式就是使用DDMS里面“停止进程” 。你在调试你的应用的时候可以使用这招。

为了测试这个,你必须使用一个Android模拟器或者一台root过的Android手机。

  1. 使用home按钮退出app。
  2. 在终端里:
    # find the process id
    adb shell ps
    # then find the line with the package name of your app
     
    # Mac/Unix: save some time by using grep:
    adb shell ps | grep your.app.package
     
    # The result should look like:
    # USER      PID   PPID  VSIZE  RSS     WCHAN    PC         NAME
    # u0_a198   21997 160   827940 22064 ffffffff 00000000 S your.app.package
     
    # Kill the app by PID
    adb shell kill -9 21997
     
    # the app is now killed

     

  3. 长按home按钮回到之前的app。
    你现在是出于一个新的application实例中了。

总结

不要在application对象里面储存数据,这容易出错,导致你的app crash。
要么将你后面要用的数据保存到磁盘上面或者保存到intent得extra里面直接传递给activity 。

这些结论不但对application对象有用,对你app里面的单例对象(singleton)或者公共静态变量(public static)同样适用。

本文翻译自:http://www.developerphil.com/dont-store-data-in-the-application-object/

Android系统中Parcelable和Serializable的区别

进行Android开发的时候,我们都知道不能将对象的引用传给Activities或者Fragments,我们需要将这些对象放到一个Intent或者Bundle里面,然后再传递。
通过Android的API,我们知道有两种选择,即在传递对象时,需要对我们的对象进行 Parcelable 或者Serializable化。作为Java开发者,相信大家对Serializable 机制有一定了解,那为什么还需要 Parcelable呢?

为了回答这个问题,让我们分别来看看这两者的差异。

Serializable, 简单易用

// access modifiers, accessors and constructors omitted for brevity
public class SerializableDeveloper implements Serializable
    String name;
    int yearsOfExperience;
    List<Skill> skillSet;
    float favoriteFloat;
 
    static class Skill implements Serializable {
        String name;
        boolean programmingRelated;
    }
}

serializable的迷人之处在于你只需要对某个类以及它的属性实现Serializable 接口即可。Serializable 接口是一种标识接口(marker interface),这意味着无需实现方法,Java便会对这个对象进行高效的序列化操作。

这种方法的缺点是使用了反射,序列化的过程较慢。这种机制会在序列化的时候创建许多的临时对象,容易触发垃圾回收。

Parcelable, 速度至上

// access modifiers, accessors and regular constructors ommited for brevity
class ParcelableDeveloper implements Parcelable {
    String name;
    int yearsOfExperience;
    List<Skill> skillSet;
    float favoriteFloat;
 
    ParcelableDeveloper(Parcel in) {
        this.name = in.readString();
        this.yearsOfExperience = in.readInt();
        this.skillSet = new ArrayList<Skill>();
        in.readTypedList(skillSet, Skill.CREATOR);
        this.favoriteFloat = in.readFloat();
    }
 
    void writeToParcel(Parcel dest, int flags) {
        dest.writeString(name);
        dest.writeInt(yearsOfExperience);
        dest.writeTypedList(skillSet);
        dest.writeFloat(favoriteFloat);
    }
 
    int describeContents() {
        return 0;
    }
 
 
    static final Parcelable.Creator<ParcelableDeveloper> CREATOR
            = new Parcelable.Creator<ParcelableDeveloper>() {
 
        ParcelableDeveloper createFromParcel(Parcel in) {
            return new ParcelableDeveloper(in);
        }
 
        ParcelableDeveloper[] newArray(int size) {
            return new ParcelableDeveloper[size];
        }
    };
 
    static class Skill implements Parcelable {
        String name;
        boolean programmingRelated;
 
        Skill(Parcel in) {
            this.name = in.readString();
            this.programmingRelated = (in.readInt() == 1);
        }
 
        @Override
        void writeToParcel(Parcel dest, int flags) {
            dest.writeString(name);
            dest.writeInt(programmingRelated ? 1 : 0);
        }
 
        static final Parcelable.Creator<Skill> CREATOR
            = new Parcelable.Creator<Skill>() {
 
            Skill createFromParcel(Parcel in) {
                return new Skill(in);
            }
 
            Skill[] newArray(int size) {
                return new Skill[size];
            }
        };
 
        @Override
        int describeContents() {
            return 0;
        }
    }
}

 

根据 google 工程师的说法,这些代码将会运行地特别快。原因之一就是我们已经清楚地知道了序列化的过程,而不需要使用反射来推断。同时为了更快地进行序列化,对象的代码也需要高度优化。

因此,很明显实现Parcelable并不容易。实现Parcelable接口需要写大量的模板代码,这使得对象代码变得难以阅读和维护。

速度测试

当然,我们还是想知道到底Parcelable相对于Serializable要快多少。

测试方法

  • 通过将一个对象放到一个bundle里面然后调用Bundle#writeToParcel(Parcel, int)方法来模拟传递对象给一个activity的过程,然后再把这个对象取出来。
  • 在一个循环里面运行1000 次。
  • 两种方法分别运行10次来减少内存整理,cpu被其他应用占用等情况的干扰。
  • 参与测试的对象就是上面代码中的SerializableDeveloper 和 ParcelableDeveloper。
  • 在多种Android软硬件环境上进行测试
    • LG Nexus 4 – Android 4.2.2
    • Samsung Nexus 10 – Android 4.2.2
    • HTC Desire Z – Android 2.3.3

结果

parcelable-vs-serializable
parcelable-vs-serializable
Nexus 10

Serializable: 1.0004ms,  Parcelable: 0.0850ms – 提升10.16倍。

Nexus 4

Serializable: 1.8539ms – Parcelable: 0.1824ms – 提升11.80倍。

Desire Z

Serializable: 5.1224ms – Parcelable: 0.2938ms – 提升17.36倍。

由此可以得出: Parcelable 比 Serializable快了10多倍。有趣的是,即使在Nexus 10这样性能强悍的硬件上,一个相当简单的对象的序列化和反序列化的过程要花将近一毫秒。

总结

如果你想成为一个优秀的软件工程师,你需要多花点时间来实现 Parcelable ,因为这将会为你对象的序列化过程快10多倍,而且占用较少的资源。

但是大多数情况下, Serializable 的龟速不会太引人注目。你想偷点懒就用它吧,不过要记得serialization是一个比较耗资源的操作,尽量少使用。

如果你想要传递一个包含许多对象的列表,那么整个序列化的过程的时间开销可能会超过一秒,这会让屏幕转向的时候变得很卡顿。

 

本文翻译自:http://www.developerphil.com/parcelable-vs-serializable/

Android的进程与线程

本文翻译自Android官方文档

当一个Android应用程序组件启动时候,如果此时这个程序的其他组件没有正在运行,那么系统会为这个程序以单一线程的形式启动一个新的Linux 进程。默认情况下,同一应用程序下的所有组件都运行再相同的进程和线程(一般称为程序的“主”线程)中。如果一个应用组件启动但这个应用的进程已经存在了(因为这个应用的其他组件已经在之前启动了),那么这个组件将会在这个进程中启动,同时在这个应用的主线程里面执行。然而,你也可以让你的应用里面的组件运行在不同的进程里面,也可以为任何进程添加额外的线程。

这片文章讨论了Android程序里面的进程和线程如何运作的。

进程


默认情况下,同一程序的所有组件都运行在相同的进程里面,大多数的应用都是这样的。然而,如果你发现你需要让你的程序里面的某个组件运行在特定的进程里面,你可以在manifest 文件里面设置。

manifest 文件里面为每一个组件元素—<activity><service><receiver>, 和<provider>—提供了 android:process 属性。通过设置这个属性你可以让组件运行在特定的进程中。你可以设置成每个组件运行在自己的进程中,也可以让一些组件共享一个进程而其他的不这样。你还可以设置成不同应用的组件运行在同一个进程里面—这样可以让这些应用共享相同的Linux user ID同时被相同的证书所认证。

<application> 元素也支持 android:process 属性,设置这个属性可以让这个应用里面的所有组件都默认继承这个属性。

Android 可能在系统剩余内存较少,而其他直接服务用户的进程又要申请内存的时候shut down 一个进程, 这时这个进程里面的组件也会依次被kill掉。当这些组件有新的任务到达时,他们对应的进程又会被启动。

在决定哪些进程需要被kill的时候,Android系统会权衡这些进程跟用户相关的重要性。比如,相对于那些承载这可见的activities的进程,系统会更容易的kill掉那些承载不再可见activities的进程。决定是否终结一个进程取决于这个进程里面的组件运行的状态。下面我们会讨论kill进程时所用到的一些规则。

进程的生命周期

作为一个多任务的系统,Android 当然系统能够尽可能长的保留一个应用进程。但是由于新的或者更重要的进程需要更多的内存,系统不得不逐渐终结老的进程来获取内存。为了声明哪些进程需要保留,哪些需要kill,系统根据这些进程里面的组件以及这些组件的状态为每个进程生成了一个“重要性层级” 。处于最低重要性层级的进程将会第一时间被清楚,接着时重要性高一点,然后依此类推,根据系统需要来终结进程。

在这个重要性层级里面有5个等级。下面的列表按照重要性排序展示了不同类型的进程(第一种进程是最重要的,因此将会在最后被kill):

  1. Foreground 进程 一个正在和用户进行交互的进程。 如果一个进程处于下面的状态之一,那么我们可以把这个进程称为 foreground 进程:

    一般说来,任何时候,系统中只存在少数的 foreground 进程。 只有在系统内存特别紧张以至于都无法继续运行下去的时候,系统才会通过kill这些进程来缓解内存压力。在这样的时候系统必须kill一些 (Generally, at that point, the device has reached a memory paging state,这句如何翻译较好呢)foreground 进程来保证 用户的交互有响应。

  2. Visible 进程 一个进程没有任何 foreground 组件, 但是它还能影响屏幕上的显示。 如果一个进程处于下面的状态之一,那么我们可以把这个进程称为 visible 进程:
    • 进程包含了一个没有在foreground 状态的 Activity ,但是它仍然被用户可见 (它的 onPause() 方法已经被调用)。这种情况是有可能出现的,比如,一个 foreground activity 启动了一个 dialog,这样就会让之前的 activity 在dialog的后面部分可见。
    • 进程包含了一个绑定在一个visible(或者foreground)activity的 Service 。

    一个 visible 进程在系统中是相当重要的,只有在为了让所有的foreground 进程正常运行时才会考虑去kill visible 进程。

  3. Service 进程 一个包含着已经以 startService() 方法启动的 Service 的进程,同时还没有进入上面两种更高级别的种类。尽管 service 进程没有与任何用户所看到的直接关联,但是它们经常被用来做用户在意的事情(比如在后台播放音乐或者下载网络数据),所以系统也只会在为了保证所有的foreground and visible 进程正常运行时kill掉 service 进程。
  4. Background 进程 一个包含了已不可见的activity的 进程 (这个 activity 的 onStop() 已经被调用)。这样的进程不会直接影响用户的体验,系统也可以为了foreground 、visible 或者 service 进程随时kill掉它们。一般说来,系统中有许多的 background 进程在运行,所以将它们保持在一个LRU (least recently used)列表中可以确保用户最近看到的activity 所属的进程将会在最后被kill。如果一个 activity 正确的实现了它的生命周期回调函数,保存了自己的当前状态,那么kill这个activity所在的进程是不会对用户在视觉上的体验有影响的,因为当用户回退到这个 activity时,它的所有的可视状态将会被恢复。查看 Activities 可以获取更多如果保存和恢复状态的文档。
  5. Empty 进程 一个不包含任何活动的应用组件的进程。 这种进程存在的唯一理由就是缓存。为了提高一个组件的启动的时间需要让组件在这种进程里运行。为了平衡进程缓存和相关内核缓存的系统资源,系统需要kill这些进程。

Android是根据进程中组件的重要性尽可能高的来评级的。比如,如果一个进程包含来一个 service 和一个可见 activity,那么这个进程将会被评为 visible 进程,而不是 service 进程。

另外,一个进程的评级可能会因为其他依附在它上面的进程而被提升—一个服务其他进程的进程永远不会比它正在服务的进程评级低的。比如,如果进程A中的一个 content provider 正在为进程B中的客户端服务,或者如果进程A中的一个 service 绑定到进程B中的一个组件,进程A的评级会被系统认为至少比进程B要高。

因为进程里面运行着一个 service 的评级要比一个包含background activities的进程要高,所以当一个 activity 启动长时操作时,最好启动一个 service 来做这个操作,而不是简单的创建一个worker线程—特别是当这个长时操作可能会拖垮这个activity。比如,一个需要上传图片到一个网站的activity 应当开启一个来执行这个上传操作。这样的话,即使用户离开来这个activity也能保证上传动作在后台继续。使用 service 可以保证操作至少处于”service process” 这个优先级,无论这个activity发生了什么。这也是为什么 broadcast receivers 应该使用 services 而不是简单的将耗时的操作放到线程里面。

线程


当一个应用启动的时候,系统会为它创建一个线程,称为“主线程”。这个线程很重要因为它负责处理调度事件到相关的 user interface widgets,包括绘制事件。你的应用也是在这个线程里面与来自Android UI toolkit (包括来自 android.widget 和 android.view 包的组件)的组件进行交互。因此,这个主线程有时候也被称为 UI 线程。

系统没有为每个组件创建一个单独的线程。同一进程里面的所有组件都是在UI 线程里面被实例化的,系统对每个组件的调用都是用过这个线程进行调度的。所以,响应系统调用的方法(比如 onKeyDown() 方法是用来捕捉用户动作或者一个生命周期回调函数)都运行在进程的UI 线程里面。

比如,当用户点击屏幕上的按钮,你的应用的UI 线程会将这个点击事件传给 widget,接着这个widget设置它的按压状态,然后发送一个失效的请求到事件队列。这个UI 线程对请求进行出队操作,然后处理(通知这个widget重新绘制自己)。

当你的应用与用户交互对响应速度的要求比较高时,这个单线程模型可能会产生糟糕的效果(除非你很好的实现了你的应用)。特别是,当应用中所有的事情都发生在UI 线程里面,那些访问网络数据和数据库查询等长时操作都会阻塞整个UI线程。当整个线程被阻塞时,所有事件都不能被传递,包括绘制事件。这在用户看来,这个应用假死了。甚至更糟糕的是,如果UI 线程被阻塞几秒(当前是5秒)以上,系统将会弹出臭名昭著的 “application not responding” (ANR) 对话框。这时用户可能选择退出你的应用甚至卸载。

另外,Android的UI 线程不是线程安全的。所以你不能在一个worker 线程操作你的UI—你必须在UI线程上对你的UI进行操作。这有两条简单的关于Android单线程模型的规则:

  1. 不要阻塞 UI 线程
  2. 不要在非UI线程里访问 Android UI toolkit

Worker 线程

由于上面对单一线程模型的描述,保证应用界面的及时响应同时UI线程不被阻塞变得很重要。如果你不能让应用里面的操作短时被执行玩,那么你应该确保把这些操作放到独立的线程里(“background” or “worker” 线程)。

比如,下面这段代码在一个额外的线程里面下载图片并在一个 ImageView显示:

public void onClick(View v){
    new Thread(new Runnable(){
        public void run(){
            Bitmap b = loadImageFromNetwork("http://example.com/image.png");
            mImageView.setImageBitmap(b);
        }
    }).start();}

 

起先这段代码看起来不错,因为它创建一个新的线程来处理网络操作。然而,它违反来单一线程模型的第二条规则: 不在非UI线程里访问 Android UI toolkit—这个例子在一个worker线程修改了 ImageView 。这会导致不可预期的结果,而且还难以调试。

为了修复这个问题,Android提供了几个方法从非UI线程访问Android UI toolkit 。详见下面的这个列表:

那么,你可以使用 View.post(Runnable) 方法来修改之前的代码:

public void onClick(View v){
    new Thread(new Runnable(){
        public void run(){
            final Bitmap bitmap = loadImageFromNetwork("http://example.com/image.png");
            mImageView.post(new Runnable(){
                public void run(){
                    mImageView.setImageBitmap(bitmap);
                }
            });
        }
    }).start();}

 

现在这个方案的线程安全的:这个网络操作在独立线程中完成后,UI线程便会对ImageView 进行操作。

然而,随着操作复杂性的增长,代码会变得越来越复杂,越来越难维护。为了用worker 线程处理更加复杂的交互,你可以考虑在worker线程中使用Handler ,用它来处理UI线程中的消息。也许最好的方案就是继承 AsyncTask 类,这个类简化了需要同UI进行交互的worker线程任务的执行。

使用 AsyncTask

AsyncTask 能让你在UI上进行异步操作。它在一个worker线程里进行一些阻塞操作然后把结果交给UI主线程,在这个过程中不需要你对线程或者handler进行处理。

使用它,你必须继承 AsyncTask 并实现 doInBackground() 回调方法,这个方法运行在一个后台线程池里面。如果你需要更新UI,那么你应该实现onPostExecute()这个方法从 doInBackground() 取出结果,然后在 UI 线程里面运行,所以你可以安全的更新你的UI。你可以通过在UI线程调用 execute()方法来运行这个任务。

比如,你可以通过使用 AsyncTask来实现之前的例子:

public void onClick(View v){
    new DownloadImageTask().execute("http://example.com/image.png");
}
private class DownloadImageTask extends AsyncTask<String,Void,Bitmap>{
    /** The system calls this to perform work in a worker thread and
      * delivers it the parameters given to AsyncTask.execute() */
    protected Bitmap doInBackground(String... urls){
        return loadImageFromNetwork(urls[0]);
    }
    
    /** The system calls this to perform work in the UI thread and delivers
      * the result from doInBackground() */
    protected void onPostExecute(Bitmap result){
        mImageView.setImageBitmap(result);
    }}

 

现在UI是安全的了,代码也更加简单了,因为AsyncTask把worker线程里做的事和UI线程里要做的事分开了。

你应该阅读一下 AsyncTask 的参考文档以便更好的使用它。下面就是一个对 AsyncTask 如何作用的快速的总览:

注意: 你在使用worker线程的时候可能会碰到的另一个问题就是因为runtime configuration change (比如用户改变了屏幕的方向)导致你的activity不可预期的重启,这可能会kill掉你的worker线程。为了解决这个问题你可以参考 Shelves 这个项目。

线程安全的方法

在某些情况下,你实现的方法可能会被多个线程所调用,因此你必须把它写出线程安全的。

Android在横竖屏切换时到底发生了什么?

在之前的一篇文章我们深入loopers和handler进行分析,看它们是如何同Android主线程相关联的。

今天,我们将继续深入Android主线程同Android组件生命周期的交互。

Activity同orientation changes之间的关系

首先来看看Activity的生命周期和它处理configuration changes 的神奇之处。

这篇文章主要来自于一段类似下面的代码:

public class MyActivity extends Activity {
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    Handler handler = new Handler(Looper.getMainLooper());
    handler.post(new Runnable() {
      public void run() {
        doSomething();
      }
    });
  }

  void doSomething() {
    // Uses the activity instance
  }
}

通过上面的代码我们知道, doSomething() 方法会在Activity因为一个configuration change导致 onDestroy() 方法会被调用之后执行。之后,你也不能再使用Activity这个实例了。

基于orientation changes的refresher

设备的 orientation 回来任意时刻发生改变。我们会在一个Activity被创建的时候通过Activity#setRequestedOrientation(int)方法来模拟一个orientation change。

你能预测当这个Activity处于portrait模式时log输出吗?

public class MyActivity extends Activity {
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    Log.d("Square", "onCreate()");
    if (savedInstanceState == null) {
      Log.d("Square", "Requesting orientation change");
      setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
    }
  }

  protected void onResume() {
    super.onResume();
    Log.d("Square", "onResume()");
  }

  protected void onPause() {
    super.onPause();
    Log.d("Square", "onPause()");
  }

  protected void onDestroy() {
    super.onDestroy();
    Log.d("Square", "onDestroy()");
  }
}

 

如果你了解 Android 生命周期, 你的预测结果可能是下面的答案:

onCreate()
Requesting orientation change
onResume()
onPause()
onDestroy()
onCreate()
onResume()

 

Android的生命周期正常运转,Activity被created,resumed,然后这个时候orientation change 发生了,Activity被 paused, destroyed,接着一个新的Activity被created 和 resumed。

Orientation changes 和Android主线程

此处有一个重要的细节:一个orientation change 导致Activity 被重新创建是通过向Android主线程的消息队列发送了一个简单的消息。

请看下面通过反射来读取主线程消息队列里面内容的spy代码:

public class MainLooperSpy {
  private final Field messagesField;
  private final Field nextField;
  private final MessageQueue mainMessageQueue;

  public MainLooperSpy() {
    try {
      Field queueField = Looper.class.getDeclaredField("mQueue");
      queueField.setAccessible(true);
      messagesField = MessageQueue.class.getDeclaredField("mMessages");
      messagesField.setAccessible(true);
      nextField = Message.class.getDeclaredField("next");
      nextField.setAccessible(true);
      Looper mainLooper = Looper.getMainLooper();
      mainMessageQueue = (MessageQueue) queueField.get(mainLooper);
    } catch (Exception e) {
      throw new RuntimeException(e);
    }
  }

  public void dumpQueue() {
    try {
      Message nextMessage = (Message) messagesField.get(mainMessageQueue);
      Log.d("MainLooperSpy", "Begin dumping queue");
      dumpMessages(nextMessage);
      Log.d("MainLooperSpy", "End dumping queue");
    } catch (IllegalAccessException e) {
      throw new RuntimeException(e);
    }
  }

  public void dumpMessages(Message message) throws IllegalAccessException {
    if (message != null) {
      Log.d("MainLooperSpy", message.toString());
      Message next = (Message) nextField.get(message);
      dumpMessages(next);
    }
  }
}

从上面可以看出,一个消息队列仅仅只是一个链表,每一个消息都有一个指向下一个消息的引用。

我们可以通过上面的代码来记录orientation change发生之后消息队列里面的内容:

public class MyActivity extends Activity {
  private final MainLooperSpy mainLooperSpy = new MainLooperSpy();

  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    Log.d("Square", "onCreate()");
    if (savedInstanceState == null) {
      Log.d("Square", "Requesting orientation change");
      setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
      mainLooperSpy.dumpQueue();
    }
  }
}

最后的输出是:

onCreate()
Requesting orientation change
Begin dumping queue
{ what=118 when=-94ms obj={1.0 208mcc15mnc en_US ldltr sw360dp w598dp h335dp 320dpi nrml land finger -keyb/v/h -nav/h s.44?spn} }
{ what=126 when=-32ms obj=ActivityRecord{41fd2b48 token=android.os.BinderProxy@41fcce50 no component name} }
End dumping queue

我们可以通过ActivityThread 类的源码知道数字 118 和 126 代表的消息是:

public final class ActivityThread {
  private class H extends Handler {
    public static final int CONFIGURATION_CHANGED   = 118;
    public static final int RELAUNCH_ACTIVITY       = 126;
  }
}

一个orientation change 会往Android主线程的消息队列里面添加一个 CONFIGURATION_CHANGED 和一个 RELAUNCH_ACTIVITY 消息。

让我们会退一步来看看到底发生了什么:

当Activity第一次启动的时候,主线程的消息队列是空的。当前要执行的消息就是 LAUNCH_ACTIVITY,即创建一个Activity实例,先后调用 onCreate() 方法和 onResume() 方法。接下来只有主线程的looper才能处理消息队列里面下一个消息。

当设备检测到有一个 orientation change 时,会有一个 RELAUNCH_ACTIVITY 消息被推送到主线程的消息队列。

当这个消息被处理的时候它会:

  • 在老的Activity实例上调用 onSaveInstanceState(), onPause(), onDestroy() 方法,
  • 创建一个新的Activity实例,
  • 在新的Activity实例上调用 onCreate()onResume() 方法。

上面这些都是一个消息要处理的。与此同时发生所有消息都会在新的Activity执行完 onResume() 方法后才被调用的。

全面测试

如果你在一个orientation change发生期间在 onCreate() 方法中向主线程消息队列发送消息会怎么样?这会有两种情况,在orientation change发生之前和之后:

public class MyActivity extends Activity {
  private final MainLooperSpy mainLooperSpy = new MainLooperSpy();

  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    Log.d("Square", "onCreate()");
    if (savedInstanceState == null) {
      Handler handler = new Handler(Looper.getMainLooper());
      handler.post(new Runnable() {
        public void run() {
          Log.d("Square", "Posted before requesting orientation change");
        }
      });
      Log.d("Square", "Requesting orientation change");
      setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
      handler.post(new Runnable() {
        public void run() {
          Log.d("Square", "Posted after requesting orientation change");
        }
      });
      mainLooperSpy.dumpQueue();
    }
  }

  protected void onResume() {
    super.onResume();
    Log.d("Square", "onResume()");
  }

  protected void onPause() {
    super.onPause();
    Log.d("Square", "onPause()");
  }

  protected void onDestroy() {
    super.onDestroy();
    Log.d("Square", "onDestroy()");
  }
}

输出结果为:

onCreate()
Requesting orientation change
Begin dumping queue
{ what=0 when=-129ms }
{ what=118 when=-96ms obj={1.0 208mcc15mnc en_US ldltr sw360dp w598dp h335dp 320dpi nrml land finger -keyb/v/h -nav/h s.46?spn} }
{ what=126 when=-69ms obj=ActivityRecord{41fd6b68 token=android.os.BinderProxy@41fd0ae0 no component name} }
{ what=0 when=-6ms }
End dumping queue
onResume()
Posted before requesting orientation change
onPause()
onDestroy()
onCreate()
onResume()
Posted after requesting orientation change

有上面的结果我们可得知:在执行到onCreate()方法最后时,消息队列里面包含四个消息。第一个就是orientation change 发生之前发送的消息,接着两个消息为orientation change相关的,最后一个消息就是orientation change发生之后要发送的消息。最后的logs 表明这些消息都是按顺序依次被执行的。

而且,任何在orientation change 之前发送的消息都会在Activity离开时调用的 onPause() 之前被处理,任何在orientation change 之后发送的消息都会在新的Activity调用的 onResume() 之后被处理。

这个实验告诉我们当你发送一个消息的时候,你不能保证当时存在的Activity实例在消息处理完之后还会存在(即使你是在 onCreate() 或者 onResume()方法里面发送的)。如过你在Activity A发送的消息B里面还持有一个view C或者一个 Activity D的引用,那么这个Activity A在消息 B被处理之前是不会被垃圾回收的。

那么我们可以如何解决这样的问题呢?What could you do?

The real fix解决方案

当你处于主线程的时候不要调用 handler.post() 方法。大部分情况下, handler.post() 被用来处理顺序问题。为了保证你的应用架构稳定请慎用handler.post()

如果你真的需要使用handler.post()

保证你的消息不要持有一个Activity实例的引用?Make sure your message does not hold a reference to an activity, as you would do for a background operation.

如果你真的需要使用handler.post()还要保持Activity引用

在Activity的onPause()方法里面通过 handler.removeCallbacks() 方法从消息队列移除这个消息。

如果你真的需要让你的消息被及时处理

使用 handler.postAtFrontOfQueue() 方法保证消息在 onPause() 发送,那么这个消息会在 onPause()之前被处理。但是你的代码将会变得很难懂。说真的,别这样干。

关于runOnUiThread()的一些话

你注意到我们创建一个handler然后使用 handler.post() 方法而不是直接调用 Activity.runOnUiThread()方法吗?

原因是这样的:

public class Activity {
  public final void runOnUiThread(Runnable action) {
    if (Thread.currentThread() != mUiThread) {
      mHandler.post(action);
    } else {
      action.run();
    }
  }
}

不像 handler.post(), runOnUiThread() 在当前线程为主线程的情况下是不会发送这个runnable的,它会同步地调用 run() 方法。

Services

有一个误解一直需要解除:service 不是 运行在一个后台线程。

所有的 service 生命周期方法 (onCreate(), onStartCommand(), 等等) 都是运行在主线程上面 (就是那个执行你的Activities各种有趣动画的线程)。

无论你是出于一个 service 还是 activity,长时间任务必须要在后台线程进行处理。这个后台线程可以和你的应用生命周期一样长,即使你的Activities已经销毁了。

然而,Android可以在任意时刻决定 kill 一个应用进程。一个 service 是一种请求系统让我们可以存活更久,同时在系统要kill 进程的时候可以让这个service知道。

注意: 当onBind() 接受到一个其他进程的调用并返回一个IBinder ,那么这个方法会在后台线程里面执行。

你可以花时间读读 Service documentation –相当不错。

IntentService

IntentService 为在后台线程依次执行一个Intent 队列里面所有Intent提供了一个简单的方式。

public class MyService extends IntentService {
  public MyService() {
    super("MyService");
  }

  protected void onHandleIntent(Intent intent) {
    // This is called on a background thread.
  }
}

在它内部,使用了一个 Looper 在一个专用 HandlerThread 的来处理这些Intents。当这个service 被销毁的时候,这个Looper会让你结束当前intent的处理,然后终结这个后台线程。

总结

大部分的Android 生命周期方法都是在主线程被调用的。你可以把这些回调函数当成发送给一个looper 队列的简单消息。

在此文结束之前还是要像大多数的Android开发博客那样强调:不要阻塞Android主线程。

你是不是想知道阻塞了主线程到底意味着什么?哈哈,这是下一个主题了。

 

本文翻译自:Square技术博客

Android 主线程之旅——PSVM(public static void main)

本文翻译自Square官方技术博客

当碰到与Android主线程交互相关的Bug时,我决定好好去看看Android的主线程究竟是怎么一回事。这篇文章就是描述我的Android主线程之旅的第一部分。

PSVM

public class BigBang {
  public static void main(String... args) {
    // The Java universe starts here.
  }
}

 

众所周知,所有的Java程序的入口都是 public static void main() 方法。这对所有的Java 桌面程序、J2EE以及Android程序都是成立的。

当Android启动时,它会开启一个叫做 ZygoteInit 的Linux进程。这个进程是一个Dalvik 虚拟机,它会在一个线程上面加载Android SDK里面大部分的常用类,然后等待。

当Android启动一个新的Android程序时,Android系统会 fork 这个 ZygoteInit 进程。接着子进程里面的线程会停止等待,然后调用ActivityThread.main()方法。

1411804069283-zygote

Wikipedia上面的一个Zygote。根据Wikipedia的定义,一个 Zygote 就是一个受精卵细胞。

Loopers

在继续深入之前,我们需要来看一看Looper这个类。

使用looper可以连续地为一个线程处理它的消息。

每一个looper都有一个消息队列(一个 MessageQueue)。

每一个looper都有一个处理消息队列里面所有消息的loop()方法,这个方法会在消息队列为空时阻塞。

Looper.loop() 方法里面的代码类似这样:

void loop() {
  while(true) {
    Message message = queue.next(); // 消息队列为空时阻塞
    dispatchMessage(message);
    message.recycle();
  }
}

 

每一个looper都会和一个线程绑定。要创建一个新的looper并将它同当前的线程绑定起来,你必须要调用Looper.prepare()方法。这些looper都被存储在Looper 类里面的静态ThreadLocal变量里面。你可以通过调用Looper.myLooper()方法来获取与当前线程相关联的Looper。

当然实际情况不要这么复杂,其实HandlerThread类已经帮你做了所有事情:

HandlerThread thread = new HandlerThread("SquareHandlerThread");
thread.start(); // starts the thread.
Looper looper = thread.getLooper();

 

HandlerThread的内部代码类似于这样:

class HandlerThread extends Thread {
  Looper looper;
  public void run() {
    Looper.prepare(); // 创建一个Looper对象并将它保存到一个ThreadLocal 对象里面。
    looper = Looper.myLooper(); // 从ThreadLocal 里面获取Looper以便后来的使用。
    Looper.loop(); // Loop forever.
  }
}

 

Handlers

handler 天生就是 looper 的好伙伴。

一个 handler 有两个作用:

  • 从任意线程发送消息给一个looper的消息队列;
  • 处理相关联的looper发过来的消息。
// Each handler is associated to one looper.
Handler handler = new Handler(looper) {
  public void handleMessage(Message message) {
    // 处理给定looper相关联的线程上面的消息
    if (message.what == DO_SOMETHING) {
      // do something
    }
  }
};
//创建一个与这个handler相关联的message
Message message = handler.obtainMessage(DO_SOMETHING);

// 将message 添加到looper 的消息队列queue.
// 这个方法能在任意线程上调用
handler.sendMessage(message);

 

你可以为一个looper关联多个handler。looper会把message发送到message.target(它就是一个handler)这里。

一个常用简单的用法就是使用handler来发送一个Runnable对象:

//创建包含一个runnable引用的message,然后将这个message添加到这个looper的消息队列
handler.post(new Runnable() {
  public void run() {
    // 这会在与handler相关联的looper对应的线程上运行
  }
});

 

一个handler也可以在没有设置looper的情况下被创建。:

// 千万不要这样做
Handler handler = new Handler();

handler的无参构造函数会调用方法来获取与当前线程相关联的looper。这个时候你要注意,可能当前线程不是你的handler想关联的线程。

大部分时间,你只需要创建一个在主线程上面发送消息的handler就行了:

Handler handler = new Handler(Looper.getMainLooper());

Back to PSVM

让我们再来看看 ActivityThread.main()这个方法。下面就是这个方法的一些内部实现:

public class ActivityThread {
  public static void main(String... args) {
    Looper.prepare();

    // 你可以在任意时刻调用Looper.getMainLooper()方法来获取主线程的looper
    Looper.setMainLooper(Looper.myLooper());

    // 发送第一波消息给主线程的 looper.
    // { ... }

    Looper.loop();
  }
}

现在你知道为什么这个线程被称为主线程了吧:) .

注意: 主线程最先做的几件事情之一就是创建Application对象,然后调用Application.onCreate()方法。

在下一篇文章中,我们将会分析Android生命周期和主线程之间的关系以及bug是怎么出现的。

Android Studio 0.8.12发布

刚刚Google发布了新版本的Android Studio。这次更新的要点如下:

  • 全新的AVD(Android Virtual Devices)管理器。全新的AVD管理器和Android Studio结合的更加紧密了。比如,当你尝试去运行一个应用的时候,Android Studio会快速的为你创建一个Nexus 5 或者 Nexus 7 的AVD。下图为全新的AVD 列表,你可以从这里浏览编辑AVD(你可以知道每个AVD使用了多少的磁盘空间),你还可以从这里启动AVD。
    avd5由于IDE提供不同种类(手机、平板、穿戴、电视)的Android设备原型,你可以基于这些设备原型很容易地创建自己的AVD。
    avd2
    一旦你选中了硬件,你可以为这个硬件配置特定的架构和Android版本:
    avd3
    你还可以对这个AVD进行更加高级的设置,比如AVD的别名,默认的手机朝向,GPU的参数或者AVD外部存储的大小:
    avd1
  • 从这个版本开始,当你创建一个新项目,Android Studio会默认使用HTTPS代替之前HTTP来进行Gradle wrapper的相关工作。当然,对于之前使用HTTP的项目我们会给出相应的提示。
    https
  • 如果你更喜欢代码界面来编辑布局XML 文件 (不是使用图形化界面),那么你现在可以点击工具栏上面『Options』图标 然后选中『Prefer XML editor』即可:
    prefer-editor
  • 字符串编辑器现在可以直接进行翻译编辑了:
    open-translations-editor
  • 现在Android 项目视图 已被设为默认视图。你可以通过右键点击项目菜单进行置换。
  • 你可以在XML文件里面导入tools namespace:你只需写上『tools:』前缀,然后Android Studio便会提供相应的namespace:
    import-prefix

注意:由于Android Studio 升级的Patch机制中出现了几个比较严重的bug,所以这次的升级需要重新下载安装包。

下载地址:

新闻来源:http://tools.android.com/recent/androidstudio0812released