Activity数据传递与跳转

引子

之前我们了解到,Activity可以有多个,且可以运行于不同进程中。那么如何进行消息传递呢?

这就需要涉及一个对象——Intent

Intent翻译过来为“意图”,它是一种运行时绑定(run-time binding)机制,可以应用于两个应用间的通讯交互,也能够应用于在同一个应用下不同组件的交互(activity、service、broadcast receiver),如下图,Intent相当于一个媒介,使用它,我们可以更加轻松的在组件之间进行通信。

这部分具体内容可以我的另一篇的文章,故不再赘述。

https://blog.csdn.net/nishigesb123/article/details/88911044

建议先阅读👆的文章,再阅读本文,有助于理解。


Activity传递数据一般有两种方法。

直接通过Bundle对象来传递

使用显式Intent完成界面跳转

首先我们在布局文件中添加一个输入框和按钮(按钮需准备对应的点击事件方法)。

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <EditText
        android:id="@+id/editText"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:layout_constraintTop_toTopOf="parent"
        android:gravity="center"
        android:hint="请输入要发送的信息"
         />

    <Button
        android:id="@+id/button"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="发送消息"
        android:onClick="sendInfo"
        app:layout_constraintTop_toBottomOf="@+id/editText" />
</android.support.constraint.ConstraintLayout>

然后,我们准备一个额外的Activity来用于承担被跳转者(接收方)的角***r>

为了能够顺利的显示接收的内容,我们需要再Main2Activity的布局文件中定义一个Textview

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".Main2Activity">

    <TextView
        android:id="@+id/info"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:layout_constraintTop_toTopOf="parent"
        android:gravity="center"
        android:text="No date"/>
</android.support.constraint.ConstraintLayout>

然后我们来完成对应的事件方法,我们使用显式的Intent来完成界面的跳转

(关于Intent的显式与隐式可以参考本文最上方提供的链接的文章)

    public void sendInfo(View view){
        Intent intent = new Intent(this,Main2Activity.class);
        startActivity(intent);
    }

 如此便已经实现了两个Activity之间的跳转。


当然我们的目标是进行数据传递,所以这还不够。这就要用到本节标题里所说的Bundle了。

关于Bundle在之前的文章有所提及:https://blog.csdn.net/nishigesb123/article/details/88877609

API中对Bundle的描述如下:

A mapping from String values to various Parcelable types.

即String值到各种Parcelable类型数据的映射,目前简单的理解为可以用于在activity间传递数据的携带数据的map即可。

 

形象点,我们可以把Bundle理解为一个快递,里面封装着你想要发送的若干货物。

国际惯例,我们new一个bundle对象

Bundle data = new Bundle();

然后我们放数据,data.put....可以看到支持放入的数据类型非常多,就像一个快递盒也可以装好多好多的东西。

我们这里选择putStirng,参数即我们的EditText的内容(其实有两个参数,要还设置一个参数名称)

所以我们还要先获取EditText的内容,这个在好久好久以前的文章里有,而且学到这里了,相信大家对如何获取EditText的内容已经很熟悉了,就不赘述了。

至此,我们已经配置完了bundle,即“快递”已经打包好了,但是“快递员”还没有联系上。

所以我们需要把“快递”交给“快递员”,即把bundle放进intent

intent.putExtra("名字任取",data) 两个参数,后者即你的bundle,前者是一个name参数,理论上可以任取。

 

如此一来“发送者”便已经配置完成了

 

MainActivity完整代码:
package com.example.a3_29sendmessage;

import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.EditText;

public class MainActivity extends AppCompatActivity {
    private EditText editText;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        editText = findViewById(R.id.editText);
    }

    public void sendInfo(View view){
        Intent intent = new Intent(this,Main2Activity.class);
        Bundle data = new Bundle();
        String info = editText.getText().toString();

        data.putString("myinfo",info);
        intent.putExtra("data",data);
        startActivity(intent);
    }

}

接下来我们配置“接收者”即Main2Activity

当然第一步还是要获取你的textview实例,就不提了。

然后是关键,我们需要收快递,怎么收呢?我们就要使用getIntent来联系派件员。

Intent intent = getIntent()  无参数

快递员到你家楼下了,你从他的手上拿你的快递,他带着一车的快递,你需要告诉他你的快递信息(name),他从一堆的快递中找出你的快递,交给你,这就是getBundleExtra

intent.getBundleExtra("data"); 参数为你在上一个Activity里putExtra的Bundle的name

于是快递到手了,你要开箱,一个快递盒里的东西也可能不止一件,于是你拿出了一个名叫myinfo的小纸条,发现上面有一串某某人写的内容。

data.getString("myinfo");  参数为你在上一个Activity里putString的信息的name(实际上叫key)

 

最后在textview里显示出拿到的数据即可。

 

完整Main2Activity代码:

package com.example.a3_29sendmessage;

import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.TextView;

public class Main2Activity extends AppCompatActivity {

    private TextView textView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main2);
        textView = findViewById(R.id.info);
        Intent intent = getIntent();
        Bundle data = intent.getBundleExtra("data");
        String info = data.getString("myinfo");
        textView.setText(info);
    }
}

效果如下:(第二个Activity成功接受了前者发送的消息)

 附录

上面的Demo还有简化的版本,即抛弃Bundle的过程,直接在Intent里捎一个String。

发送者:

Bundle data = new Bundle();
data.putString("myinfo",info);
intent.putExtra("data",data);

上述三行写成👇的形式

intent.putExtra("myinfo",info);

接收者:

Bundle data = intent.getBundleExtra("data");
String info = data.getString("myinfo");

 上述两行写成👇的形式

String info = intent.getStringExtra("myinfo");

此外, 一个Intent可以携带多种和多个数据,一个bundle也是...多写几个put和get就好,这个不难理解,就不一一测试了。


使用Intent定义的Bundle对象来传递

实际上在Intent内部就已经为我们准备好了一个bundle,下面提供两种方式实现

方法一

测试前,我们准备一个按钮,并未它定义点击事件。

   <Button
        android:id="@+id/button1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="传递对象一"
        android:onClick="sendObj1"
        app:layout_constraintTop_toBottomOf="@+id/button" />

再准备一个类,作为发送的对象。并在这个类里任意书写一些属性。

接下来我们取完成点击事件。

先new一个我们刚才建的对象,并未它的属性赋一定的值。

        Test test = new Test();
        test.id = 1;
        test.name = "测试1";
        test.number = 123;
        test.type = "类型1";

接下来的步骤和前面类似,new一个intent,然后putExtra,这里我们观察一下putExtra支持的参数。

我们发现,它支持Serializable value,我们这里就是要使用这一组参数,然后我们回到Test类,使其实现序列化接口,并重写toString方法(为了方便待会一并输出)。

Test类完整代码如下:

package com.example.a3_29sendmessage;

import java.io.Serializable;

public class Test implements Serializable {
    String name;
    int id;
    int number;
    String type;

    @Override
    public String toString() {
        return "Test{" +
                "name='" + name + '\'' +
                ", id=" + id +
                ", number=" + number +
                ", type='" + type + '\'' +
                '}';
    }
}

Tips:ide右键Generate菜单可以提供快速toString

MainActivity完整代码:

package com.example.a3_29sendmessage;

import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.EditText;

public class MainActivity extends AppCompatActivity {
    private EditText editText;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        editText = findViewById(R.id.editText);
    }

    public void sendInfo(View view){
        Intent intent = new Intent(this,Main2Activity.class);

        String info = editText.getText().toString();

/*        Bundle data = new Bundle();
        data.putString("myinfo",info);
        intent.putExtra("data",data);*/

        intent.putExtra("myinfo",info);
        startActivity(intent);
    }

    public void  sendObj1(View view){
        Test test = new Test();
        test.id = 1;
        test.name = "测试1";
        test.number = 123;
        test.type = "类型1";

        Intent intent = new Intent(this,Main2Activity.class);
        intent.putExtra("Test",test);
        startActivity(intent);
    }

}

接下来完成接收方,对应的,我们使用getSerializableExtra来接收数据。并在输出时调用toString()。

完整代码如下:

package com.example.a3_29sendmessage;

import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.TextView;

public class Main2Activity extends AppCompatActivity {

    private TextView textView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main2);
        textView = findViewById(R.id.info);
        Intent intent = getIntent();
/*        Bundle data = intent.getBundleExtra("data");
        String info = data.getString("myinfo");*/
        String info = intent.getStringExtra("myinfo");

        Test test = (Test)intent.getSerializableExtra("Test");

        textView.setText(info+"/"+test.toString());
    }
}

效果如下:(因为没有把info给去掉,所以前面会有个null,可以把👆代码中setText(info+"/"+test.toString())改为setText(test.toString())


方法二

法一涉及序列化,性能上可能有一定的缺陷(实际上这是Java的方式)。安卓自己有一套写法传递对象,可以规避序列化带来的性能开销。

一个Button,步骤类似就直接放代码。

    <Button
        android:id="@+id/button2"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="传递对象二"
        android:onClick="sendObj2"
        app:layout_constraintTop_toBottomOf="@+id/button1" />

同样要一个类,但不实现序列化,而是implements Parcelable,并重写对应方法。

关于对象创建器的写法可以参考API,只需要进行少量的修改,姑且当个模板记住把。

可以参考:https://developer.android.google.cn/reference/android/os/Parcelable.html

package com.example.a3_29sendmessage;

import android.os.Parcel;
import android.os.Parcelable;

public class Test2 implements Parcelable {
    String name;
    int id;
    int number;
    String type;

    @Override
    public String toString() {
        return "Test2{" +
                "name='" + name + '\'' +
                ", id=" + id +
                ", number=" + number +
                ", type='" + type + '\'' +
                '}';
    }

    @Override
    public int describeContents() {
        //描述内容,保持原来的return 0即可
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        //把要传递的数据写入
        dest.writeString(name);
        dest.writeString(type);
        dest.writeInt(id);
        dest.writeInt(number);
    }
    //对象的创建器
    public static final Parcelable.Creator<Test2> CREATOR
            = new Parcelable.Creator<Test2>() {
        public Test2 createFromParcel(Parcel in) {
            Test2 test2 =  new Test2() ;
            test2.name = in.readString();
            test2.type = in.readString();
            test2.id = in.readInt();
            test2.number = in.readInt();
            return test2;
            //return new Test2(in);
        }

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

然后MainActivity基本一致

    public void  sendObj2(View view){
        Test2 test2 = new Test2();
        test2.id = 2;
        test2.name = "测试2";
        test2.number = 456;
        test2.type = "类型2";

        Intent intent = new Intent(this,Main2Activity.class);
        intent.putExtra("Test2",test2);
        startActivity(intent);
    }

Main2Activity也基本一致

package com.example.a3_29sendmessage;

import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.TextView;

public class Main2Activity extends AppCompatActivity {

    private TextView textView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main2);
        textView = findViewById(R.id.info);
        Intent intent = getIntent();
/*        Bundle data = intent.getBundleExtra("data");
        String info = data.getString("myinfo");*/
        String info = intent.getStringExtra("myinfo");

        //Test test = (Test)intent.getSerializableExtra("Test");
        Test2 test2 = intent.getParcelableExtra("Test2");
        textView.setText(test2.toString());
        //textView.setText(info+"/"+test.toString());
    }
}

效果如下:


Activity处理返回结果

Android提供了一个机制:activityA跳转到其他activityB时,返回时,activityB可以接受到其他activityA返回的值。利用这种机制,也是可以进行数据传递的。

 

之前的代码已经有点长了,所以这次新建两个Activty(也可以干脆重新建工程),一个作为接收方, 一个作为发送方。

方便起见,在MainActivity里做了一个按钮,并且能够跳转到发送方的Activity,或者干脆在配置文件清单里把发送方作为启动的Activity也是可以的。

 

在发送方的布局中,我们创建一个listview

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".Main3Activity">

    <ListView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/Listview">
    </ListView>
</android.support.constraint.ConstraintLayout>

同时完成其ListView的配置.... ListView相关可以参考以前的文章...就不赘述了

package com.example.a3_29sendmessage;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ListView;

public class Main3Activity extends AppCompatActivity {

    private ListView listView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main3);
        listView = findViewById(R.id.Listview);

        final String[] items = {"t0","t1","t2","t3","t4","t5","t6","t7","t8","t9"};
        ArrayAdapter<String> adapter = new ArrayAdapter<String>(
                this,android.R.layout.simple_list_item_1,android.R.id.text1,items);
        listView.setAdapter(adapter);
        listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {

            }
        });
    }

}

接收方的布局中,我们定义一个edittext和两个button

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".Main4Activity">

    <EditText
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:layout_constraintTop_toTopOf="parent"
        android:id="@+id/new_editText"/>
    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:text="选择"
        android:onClick="check"
        app:layout_constraintTop_toBottomOf="@id/new_editText"
        android:id="@+id/new_button"/>
    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:text="确认"
        android:onClick="confirm"
        app:layout_constraintTop_toBottomOf="@id/new_button"
        android:id="@+id/new_confirm"/>

</android.support.constraint.ConstraintLayout>

 

然后我们先完成接收方的点击事件

    public void check(View view){
        Intent intent = new Intent(this,Main3Activity.class);
        //除了startActivity外唯二的启动Activity的方法,该方法可以返回结果。
        startActivityForResult(intent,REQUESTCODE_1);
    }

这里涉及startActivityForResult,它是除了startActivity外唯二的启动Activity的方法,该方法可以返回结果,具有如下参数:

requestCode为请求编码,用来标识数据来源于哪个Activity,自定义即可。

接下是关键,重写onActivityResult方法,来处理返回结果。

    //重写该方法处理返回的结果
    @Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
    }

 

然而我们的结果...还没有处理好,所以我们得回过头去写发送方Listview的item的点击事件(往上拉看代码那块还是空的)

        listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                String item = items[position];
                Intent intent = new Intent();
                intent.putExtra("item",item);
                setResult(RESULT_OK,intent);//设置返回结果
                finish();//结束当前Activty
            }
        });

这里则涉及一个setRequest()参数如下,

resultCode为结果编码,系统提供一定的可选常量,我们选择RESULT_OK即可。

完成后就可以去完成处理返回结果的方法的重写了

    //重写该方法处理返回的结果
    @Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if(requestCode==REQUESTCODE_1&&resultCode==RESULT_OK){
            String item  = data.getStringExtra("item");
            editText.setText(item);
        }
    }

效果

点击第三个按钮进入测试

点击选择,选择t5,

t5的数据成功返回!


模拟电话簿

在👆部分的代码基础上做略微的调整,实现一个简易的电话簿。

实际上就是把没有写的确认按钮的点击事件写完(confirm)

 

值得注意的是需要在配置清单文件中加入一行,来允许程序请求打电话的权限。

<uses-permission android:name="android.permission.CALL_PHONE"></uses-permission>

然后实际测试中还是会遇到报错,这是版本导致的安卓对权限的控制有一定调整,可以考虑再请求前做出一个判断,如果没有权限就跳转并提示让用户手动开启权限,本例中不涉及。

 

布局和原来不变。

Main3Activity为发送方(打电话界面)完整代码:

package com.example.a3_29sendmessage;

import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ListView;

public class Main3Activity extends AppCompatActivity {

    private ListView listView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main3);
        listView = findViewById(R.id.Listview);

        final String[] items = {
                "86136870",
                "86136871",
                "86136872",
                "86136873",
                "86136874",
                "86136875",
                "86136876",
                "86136877",
                "86136878",
                "86136879"};
        ArrayAdapter<String> adapter = new ArrayAdapter<String>(
                this,android.R.layout.simple_list_item_1,android.R.id.text1,items);
        listView.setAdapter(adapter);
        listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                String item = items[position];
                Intent intent = new Intent();
                intent.putExtra("item",item);
                setResult(RESULT_OK,intent);//设置返回结果
                finish();//结束当前Activty
            }
        });
    }

}

Main4Activity为接收方(电话簿列表)

package com.example.a3_29sendmessage;

import android.content.Intent;
import android.net.Uri;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.EditText;

public class Main4Activity extends AppCompatActivity {
    private EditText editText;
    private static final int REQUESTCODE_1 = 0x1;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main4);
        editText = findViewById(R.id.new_editText);
    }

    public void check(View view){
        Intent intent = new Intent(this,Main3Activity.class);
        //除了startActivity外唯二的启动Activity的方法,该方法可以返回结果。
        startActivityForResult(intent,REQUESTCODE_1);
    }

    public void confirm(View view){
        String number = editText.getText().toString();
        Intent intent = new Intent();
        intent.setAction(Intent.ACTION_CALL);
        intent.setData(Uri.parse("tel:"+number));
        startActivity(intent);
    }

    //重写该方法处理返回的结果
    @Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if(requestCode==REQUESTCODE_1&&resultCode==RESULT_OK){
            String item  = data.getStringExtra("item");
            editText.setText(item);
        }

    }
}

 

效果:

选择第一个,点击确定拨打...