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);
}
}
}
效果:
选择第一个,
点击确定拨打...