Android开发权威指南(第二版)
上QQ阅读APP看书,第一时间看更新

5.4 在不同Activity之间传递数据

源代码目录:src/ch05/TransmitData

如果读者以前使用其他语言实现过窗口切换的功能,就会意识到在窗口切换的过程中可能伴随着数据的传递。在Activity之间切换时也不可避免要进行数据传递。例如,在单击列表中的某个列表项时,需要编辑与这个列表项相关的数据。这时就需要再显示一个Activity,并将相应的数据传递给这个Activity,这就是一个典型的数据传递的过程。

在Android中传递数据的方法非常多。本节将介绍4种比较常用的数据传递方法。这4种数据传递方法如下:

通过Intent传递数据。

通过静态(static)变量传递数据。

通过剪贴板(Clipboard)传递数据。

通过全局对象传递数据。

下面来分别看一下这4种数据传递的方法。

5.4.1 使用Intent传递数据

这是最常用的一种数据传递的方法。通过Intent.putExtra方法可以将简单类型的数据或可序列化的对象保存在Intent对象中,然后在目标Activity中使用getXxx(getInt、getString等)方法获得这些数据。将数据保存到Intent对象,并显示另一个用来显示这些数据的Activity的代码如下:

源代码文件:src/ch05/TransmitData/src/mobile/android/transmit/data/TransmitDataActivity.java

public void onClick(View view)

{

  Intent intent = null;

  switch (view.getId())

  {

    case R.id.button1:

      intent = new Intent(this, MyActivity1.class);

      // 保存String类型的值

      intent.putExtra("intent_string", "通过Intent传递的字符串");

      // 保存interger类型的值

      intent.putExtra("intent_integer", 300);

      Data data = new Data();

      data.id = 1000;

      data.name = "Android";

      // 保存可序列化的对象

      intent.putExtra("intent_object", data);

      // 显示用于接收数据的Activity

      startActivity(intent);

      break;

    ……

  }

}

上面的代码中涉及一个Data类,这个类是可序列化的,也就是实现了java.io.Serializable接口的类。Data类的代码如下:

源代码文件:src/ch05/TransmitData/src/mobile/android/transmit/data/Data.java

package mobile.android.transmit.data;

import java.io.Serializable;

public class Data implements Serializable

{

  public int id;

  public String name;

}

在MyActivity1类中获取通过Intent对象传递过来的3个值(String、Integer和Data类型的值)的代码如下:

源代码文件:src/ch05/TransmitData/src/mobile/android/transmit/data/MyActivity1.java

public class MyActivity1 extends Activity

{

  @Override

  protected void onCreate(Bundle savedInstanceState)

  {

    super.onCreate(savedInstanceState);

    setContentView(R.layout.myactivity);

    TextView textView = (TextView)findViewById(R.id.textview);

    // 获得String类型的值

    String intentString = getIntent().getStringExtra("intent_string");

    // 获得Integer类型的值

    int intentInteger = getIntent().getExtras().getInt("intent_integer");

    // 获得Data类型的值

    Data data = (Data)getIntent().getExtras().get("intent_object");

 

    StringBuffer sb = new StringBuffer();

    sb.append("intent_string:");

    sb.append(intentString);

    sb.append("\n");

    sb.append("intent_integer:");

    sb.append(intentInteger);

    sb.append("\n");

    sb.append("data.id:");

    sb.append(data.id);

    sb.append("\n");

    sb.append("data.name:");

    sb.append(data.name);

    sb.append("\n");

    // 在屏幕上输出传递过来的值

    textView.setText(sb.toString());

  }

 

}

运行程序后,单击如图5-25所示的界面(测试其他传递数据的方式也在该界面上完成)的第一个按钮,会显示如图5-26所示的输出信息。

 

▲图5-25 TransmitData 程序的主界面

 

▲图5-26 显示从Intent 对象中获取的数据

5.4.2 使用静态变量传递数据

虽然使用Intent对象可以很方便地在窗口之间传递数据,这也是官方推荐的数据传递方式。但Intent也有其局限性,就是Intent无法传递不能序列化的对象,也就是没有实现java.io.Serializable接口的类创建的对象。例如,存储图像数据的Bitmap对象就无法通过Intent对象进行传递。如果传递自定义类的对象,也必须实现java.io.Serializable接口才可以。如果这些类没有源代码,而且也没有实现java.io.Serializable接口,使用Intent对象就没有任何办法传递这些类的对象了即使类实现了java.io.Serializable接口,类成员变量也必须是可序列化的才可以使用Intent对象传递。

不过天无绝人之路,Intent对象不行,还是有其他方法的。静态变量就是一种非常方便、易用的传递数据的方法。例如,要向MyActivity2传递3个值:id、name和data,就需要在MyActivity2类(在其他Java类中也可以)中定义3个静态变量,代码如下:

源代码文件:src/ch05/TransmitData/src/mobile/android/transmit/data/MyActivity2.java

public class MyActivity2 extends Activity

{

  // 下面定义了3个静态变量

  public static String name;

  public static int id;

  public static Data data;

  @Override

  protected void onCreate(Bundle savedInstanceState)

  {

    // TODO Auto-generated method stub

    super.onCreate(savedInstanceState);

    setContentView(R.layout.myactivity);

    TextView textView = (TextView)findViewById(R.id.textview);

 

    StringBuffer sb = new StringBuffer();

    sb.append("name:");

    sb.append(name);

    sb.append("\n");

    sb.append("id:");

    sb.append(id);

    sb.append("\n");

    sb.append("data.id:");

    sb.append(data.id);

    sb.append("\n");

    sb.append("data.name:");

    sb.append(data.name);

    sb.append("\n");

    // 输出静态变量的值

    textView.setText(sb.toString());

  }

}

要想在MyActivity2中获取这些静态变量的值,就需要在显示MyActivity2之前(调用startActivity方法之前)为这些静态变量赋值,代码如下:

源代码文件:src/ch05/TransmitData/src/mobile/android/transmit/data/TransmitDataActivity.java

public void onClick(View view)

{

  Intent intent = null;

  switch (view.getId())

  {

    ……

    case R.id.button2:

      intent = new Intent(this, MyActivity2.class);

      // 下面的代码为MyActivity2中的3个静态变量赋值

      MyActivity2.id = 3000;

      MyActivity2.name = "保时捷";

      MyActivity2.data = new Data();

      MyActivity2.data.id = 1000;

      MyActivity2.data.name = "Android";

      startActivity(intent);

      break;

    ……

  }

}

单击主界面的“使用静态变量传递数据”按钮,会显示如图5-27所示的输出信息。

 

▲图5-27 显示通过静态变量传递过来的值

5.4.3 使用剪贴板传递数据

在Activity之间传递数据还可以利用一些技巧,例如,不管是Windows,还是Linux(Android、Meego或其他基于Linux内核的操作系统),都会支持一种叫剪贴板的技术。也就是某一个程序将一些数据复制到内存的某一区域,然后其他的任何程序都可以从这一区域中获取数据。那么在Activity之间传递数据也可以利用这种技术。

由于剪贴板只能存储简单类型数据或可序列化的对象,对于某些不可序列化的对象如果可以将其转换成字节流(字节数组),也可以将这些对象保存到剪贴板中,例如Bitmap对象(保存图像的对象)是不可序列化的,但可以将其保存的图像数据转换为字节数组,这样就可以将其保存到剪贴板中了。

下面的代码演示了如何通过剪贴板在两个Activity之间传递数据。在TransmitDataActivity窗口中会向剪贴板存储一个可序列化的Data对象,在本例中并不是直接将Data对象保存在剪贴板,而是将Data对象转换为Base64编码后以字符串形式存储在剪贴板中。

源代码文件:src/ch05/TransmitData/src/mobile/android/transmit/data/TransmitDataActivity.java

public void onClick(View view)

{

  Intent intent = null;

  switch (view.getId())

  {

    ……

    case R.id.button3:

      intent = new Intent(this, MyActivity3.class);

      // 获取剪贴板对象(ClipboardManager)

      ClipboardManager clipboard = (ClipboardManager)

          getSystemService(Context.CLIPBOARD_SERVICE);

      // 创建Data对象

      Data clipboardData = new Data();

      // 设置Data对象中字段的值

      clipboardData.id = 6666;

      clipboardData.name = "通过Clipboard传递的数据";

      // 创建字节数组输出流对象,用于将Data对象转换为字节流

      ByteArrayOutputStream baos = new ByteArrayOutputStream();

      // 用于保存Data对象生成的Base64格式的字符串

      String base64Str = "";

      try

      {

        ObjectOutputStream oos = new ObjectOutputStream(baos);

        // 将Data对象写入对象输出流

        oos.writeObject(clipboardData);

        // 将字节流进行Base64编码

        base64Str = Base64.encodeToString(baos.toByteArray(),

            Base64.DEFAULT);

        oos.close();

      }

      catch (Exception e)

      {

      }

      // 获取存储文本数据的剪贴板数据对象(ClipData)

      ClipData clipData = ClipData.newPlainText("data", base64Str);

      // 设置主剪贴板

      clipboard.setPrimaryClip(clipData);

      // 显示MyActivity3窗口

      startActivity(intent);

      break;

    ……

  }

}

  

注意

由于Base64类是从Android 2.2开始支持的,因此,Android 2.1及更低版本的Android无法通过Android SDK API进行Base64编码和解码。因此,需要使用第三方的类库(例如,common httpclient)才可以进行Base64编码。

在MyActivity3窗口类的onCreate方法中会从剪贴板中获取Base64编码字符串,并将该编码字符串还原为Data对象。MyActivity3类的代码如下:

源代码文件:src/ch05/TransmitData/src/mobile/android/transmit/data/MyActivity3.java

public class MyActivity3 extends Activity

{  

  @Override

  protected void onCreate(Bundle savedInstanceState)

  {

    super.onCreate(savedInstanceState);

    setContentView(R.layout.myactivity);

    TextView textView = (TextView) findViewById(R.id.textview);

    // 获取剪贴板对象(ClipboardManager)

    ClipboardManager clipboard = (ClipboardManager)

         getSystemService(Context.CLIPBOARD_SERVICE);

    // 从剪贴板中获取Base64编码字符串

    String base64Str = clipboard.getPrimaryClip().getItemAt(0).getText()

        .toString();

    // 将Base64编码字符串解码成字节数组

    byte[] buffer = Base64.decode(base64Str, Base64.DEFAULT);

    ByteArrayInputStream bais = new ByteArrayInputStream(buffer);

    try

    {

      ObjectInputStream ois = new ObjectInputStream(bais);

      // 将字节流还原成Data对象

      Data data = (Data) ois.readObject();

      // 将Base64编码原文和Data对象的字段值显示在TextView控件中

      textView.setText(base64Str + "\n\ndata.id:" + data.id

          + "\ndata.name:" + data.name);

    }

    catch (Exception e)

    {

    }

  }

}

现在运行程序,单击主界面的“使用剪贴板传递数据”按钮,会显示如图5-28所示的信息。

 

▲图5-28 显示通过剪贴板传递过来的数据

扩展学习:获取系统管理对象

在读写剪贴板之前都需要使用下面的代码获取剪贴板管理对象。

ClipboardManager clipboard = (ClipboardManager)

          getSystemService(Context.CLIPBOARD_SERVICE);

  ClipboardManager对象用于管理剪贴板的数据,已经可以完成各种对剪贴板的操作。这种获取管理对象的方式很常用。在Context类中定义了若干个常量,分别对应系统的不同管理对象,例如,Context.ACCOUNT_SERVICE(账户管理对象)、Context.ACTIVITY_SERVICE(窗口管理对象)、Context.AUDIO_SERVICE(音频管理对象)等。下面是获得这3个管理对象的代码,在后面的章节中会逐渐使用到这些以及更多的管理对象。

// 获取账户管理对象

AccountManager accountManager = (AccountManager)

                getSystemService(Context.ACCOUNT_SERVICE);

// 获取窗口管理对象

ActivityManager activityManager = (ActivityManager)

                 getSystemService(Context.ACTIVITY_SERVICE);

// 获取音频管理对象

AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);

5.4.4 使用全局对象传递数据

虽然使用静态变量可以传递任意类型的数据,但官方并不建议这样做。如果在类中大量使用静态变量(尤其是使用很占资源的变量,例如,Bitmap对象)可能会导致内存溢出,而且还可能因为静态变量在很多类中出现而造成代码难以维护和混乱的状况,因此,在本节介绍一种更优雅的数据传递方式,这就是全局对象。这种方式可完全取代静态变量。

使用过Java EE的读者对Java Web的4个作用域一定非常清楚,这4个作用域从小到大分别是Page、Request、Session和Application,其中Application域在应用程序的任何地方都可以访问,除非将Web服务器停止,否则这个Application域将一直存在。Android中的全局对象类似于Java Web中的这个Application域,除非将Android应用程序彻底清除出内存,否则全局对象将一直可以访问。

接下来看一下如何使用全局对象。全局对象所对应的类必须从android.app.Application继承。下面就是一个典型的全局类。

源代码文件:src/ch05/TransmitData/src/mobile/android/transmit/data/MyApp.java

package mobile.android.transmit.data;

import android.app.Application;

public class MyApp extends Application

{

  public String country;

  public Data data = new Data();

}

 

注意

全局类中不需要定义静态变量,只需要定义普通成员变量即可,但全局类中必须要有一个无参数的构造方法,或不编写构造方法(系统会自动建立一个无参数的构造方法)。全局类和窗口类是一样的,由系统使用全局类自动创建全局对象,因此,全局类必须要有一个无参数的构造方法。

在编写完全局类后,还需要通过<application>标签的android:name属性指定全局类名,否则系统是不会自动创建全局对象的。

源代码文件:src/ch05/TransmitData/AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>

<manifest xmlns:android="http://schemas.android.com/apk/res/android"

   package="mobile.android.ch04.transmit.data"

   android:versionCode="1"

   android:versionName="1.0">  <!-- 通过android:name属性指定全局类(MyApp)

  <application android:name=".MyApp" android:icon="@drawable/icon" android:label="  @string/app_name">

  ……

  <uses-sdk android:minSdkVersion="9" />

</manifest>

无论读写全局对象,都必须先获取全局对象,通过Activity.getApplicationContext方法可以很容易地在程序的任何位置获取全局对象,代码如下:

MyApp myApp = (MyApp) getApplicationContext();

下面的代码首先获得了MyApp对象,然后为MyApp对象中的字段赋值。

源代码文件:src/ch05/TransmitData/src/mobile/android/transmit/data/TransmitDataActivity.java

public void onClick(View view)

{

  Intent intent = null;

  switch (view.getId())

  {

    … …

    case R.id.button4:

      // 获取MyApp对象

      MyApp myApp = (MyApp) getApplicationContext();

      myApp.country = "美国";

      myApp.data.id = 1234;

      myApp.data.name = "飞碟";

      intent = new Intent(this, MyActivity4.class);

      startActivity(intent);

      break;

  }

}

在MyActivity4中获得了MpyApp对象,并在TextView控件中显示MyApp对象中字段的值。

源代码文件:src/ch05/TransmitData/src/mobile/android/transmit/data/MyActivity4.java

public class MyActivity4 extends Activity

{  

  @Override

  protected void onCreate(Bundle savedInstanceState)

  {

    super.onCreate(savedInstanceState);

    setContentView(R.layout.myactivity);

    TextView textView = (TextView) findViewById(R.id.textview);

    // 获取MyApp对象

    MyApp myApp = (MyApp) getApplicationContext();

    // 在TextView控件中显示MyApp对象中字段的值

    textView.setText("MyApp.country:" + myApp.country + "\nMyApp.data.id:"

        + myApp.data.id + "\nMyApp.data.name" + myApp.data.name);

  }

}

现在运行程序,单击主界面的“使用全局对象传递数据”按钮,会显示如图5-29所示的输出信息。

 

▲图5-29 使用全局对象传递数据

归纳总结:前面介绍了4种在不同窗口之间传递数据的方法。虽然这些方法在某种程度上可以互相取代,但根据具体情况使用不同的数据传递方法会使程序更便于维护,更加健壮。对于向其他窗口传递简单类型(int、String、short、Boolean等)或可序列化的对象时,建议使用Intent对象进行数据传递。如果传递不可序列化的对象,可以采用静态变量或全局对象的方式,不过按照官方的建议,最好采用全局对象的方式。另外,如果想使某些数据长时间驻留内存,以便程序随时取用,最好采用全局对象的形式,当然,如果数据不复杂,也可以采用静态变量。至于剪贴板,如果不是非常特殊的情况,并不建议使用。因为这可能会影响到其他的程序(因为其他的程序可能会使用到剪贴板)。