说一个很常见的现象,我们在使用Android手机的过程中,肯定都会遇到从一个App中跳转到另一个App的场景。例如,在支付宝中跳转到淘宝中,并显示淘宝中某个商品的详情页。这个本质上就是从一个进程中的某个Activity跳转到了另一个进程中的某个Activity,借助Bundle,我们就可以在这个过程中跨进程的传递一些数据信息(例如,现在很常见的,我们是否在特定的网页浏览的指定秒数,这个浏览结果,我们就可以通过Bundle在不同进程间进行传递)。
在Android四大组件(Activity, Service, Broadcast Receive, Content Provider)中,除了Content Provider之外,其他三个都可以在Intent中添加Bundle数据。Bundle能够顺利的从一个进程被传递到另一个进程,主要得益于Bundle能够被序列化,也就是Bundle类实现了Parcelable接口。其底层原理也是将Bundle及其存储的数据通过序列化和反序列化来实现。所以,能够存储在Bundle中的数据(其类型)也必须能够被序列化。例如,Java基本数据类型,实现了Parcelable或Serializable接口的对象或一些Android支持的特殊对象。其实通过查看Bundle类的(put或get)方法,我们就能够知道哪些类能够被支持在Bundle中传输。
写了个小Demo实现借用Bundle跨进程传递数据,本Demo实现了从BundleTest应用中的OriginActivity跳转到AndroidView应用中的TargetActivity,并通过Bundle传递一段字符串。
OriginActivity的代码如下:
package com.itachi.android.bundletest;
import androidx.appcompat.app.AppCompatActivity;
import android.content.ComponentName;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
public class OriginActivity extends AppCompatActivity {
private static final String EXTRA_KEY = "EXTRA_KEY";
private static final String EXTRA_MESSAGE = "Message from Origin Activity";
private Button mOriginButton;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_origin);
mOriginButton = (Button)findViewById(R.id.origin_button);
mOriginButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
startTargetActivity();
}
});
}
private void startTargetActivity() {
Intent intent = new Intent();
// 如果需要跳转的目标Activity为非主Activity 即未在Manifest文件中声明为启动Activity
// 则目标Activity需要声明android:exported="true"
intent.setComponent(new ComponentName("com.itachi.android.androidview",
"com.itachi.android.androidview.TargetActivity"));
Bundle bundle = new Bundle();
bundle.putString(EXTRA_KEY, EXTRA_MESSAGE);
intent.putExtras(bundle);
startActivity(intent);
}
}
TargetActivity的代码如下:
package com.itachi.android.androidview;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
public class TargetActivity extends AppCompatActivity {
private static final String EXTRA_KEY = "EXTRA_KEY";
private TextView mTargetTextView;
private Button mTargetButton;
private String message = "";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_target);
mTargetTextView = (TextView) findViewById(R.id.target_textview);
mTargetButton = (Button) findViewById(R.id.target_button);
message = getIntent().getExtras().getString(EXTRA_KEY);
mTargetButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mTargetTextView.setText(message);
}
});
}
}
在上面的Demo中,我们通过OriginActivity启动了TargetActivity,并将字符串数据传递给了TargetAcivity,实现了从进程com.itachi.android.bundletest到进程com.itachi.android.androidview的跨进程通信。
再举个我们可能都用到过的功能,现在很多App都带有广告推送功能,一些App可能会推荐你看一个广告并且在达到特定的观看时长后,给予用户一定的“奖励”。那么用户在某个(例如ActivityA)进入广告页(ActivityB)后,观看的时长怎么返回给原界面(ActivityA)呢 ?我们可以通过startActivity(intent)方法将Bundle从ActivityA传递到ActivityB,反过来,怎么在用户从ActivityA(用户点击back键或者利用手势导航)返回ActivityB时,传递数据呢?
这里就要介绍Activity类中的几个方法,如下:
1.startActivityForResult(Intent intent, int requestCode)
2.onActivityResult(int requestCode, int resultCode, Intent data)
3.setResult(int resultCode, Intent data)
我们可以通过startActivityForResult方法启动新的Activity,其中的参数requestCode用于标识请求的是请求的是哪个Activity。在新启动的Activity中,我们可以通过setResult方法,将数据打包到intent中。在返回上一个Activity后,会调用原Activity的onActivityResult方法,进而实现了将数据返回到之前的Activity。
针对前面的例子,我们可以这样实现:
1.ActivityA通过startActivityForResult(Intent intent, int requestCode)启动ActivityB(需要利用requestCode标识ActivityB)
2.ActivityB启动后,通过setResult(int resultCode, Intent data)将需要返回的数据(比如是否看完了广告)打包到intent中
3.用户返回上一个Activity,系统回调ActivityA的onActivityResult(int requestCode, int resultCode, Intent data)方法,将数据解析到ActivityA中。
我们再在前面的Demo中,加入这个功能,修改后的代码如下:
OriginActivity
package com.itachi.android.bundletest;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import android.content.ComponentName;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
public class OriginActivity extends AppCompatActivity {
private static final String TAG = "OriginActivity";
private static final String EXTRA_KEY = "EXTRA_KEY";
private static final String EXTRA_MESSAGE = "Message from Origin Activity";
private Button mOriginButton;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_origin);
mOriginButton = (Button)findViewById(R.id.origin_button);
mOriginButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
startTargetActivity();
}
});
}
private void startTargetActivity() {
Intent intent = new Intent();
// 如果需要跳转的目标Activity为非主Activity 即未在Manifest文件中声明为启动Activity
// 则目标Activity需要声明android:exported="true"
intent.setComponent(new ComponentName("com.itachi.android.androidview",
"com.itachi.android.androidview.TargetActivity"));
Bundle bundle = new Bundle();
bundle.putString(EXTRA_KEY, EXTRA_MESSAGE);
intent.putExtras(bundle);
startActivityForResult(intent, 1);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
Log.v(TAG, "onActivityResult..." + ", request:" + requestCode + ", resultCode:" + resultCode);
super.onActivityResult(requestCode, resultCode, data);
switch (requestCode) {
case 1:
if (resultCode == RESULT_OK) {
String message = data.getExtras().getString(EXTRA_KEY);
Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
}
}
}
@Override
protected void onResume() {
Log.v(TAG, "onResume...");
super.onResume();
}
}
TargetActivity
package com.itachi.android.androidview;
import androidx.appcompat.app.AppCompatActivity;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
public class TargetActivity extends AppCompatActivity {
private static final String TAG = "TargetActivity";
private static final String EXTRA_KEY = "EXTRA_KEY";
private static final String MESSAGE_OK = "Message from Target Activity";
private TextView mTargetTextView;
private Button mTargetButton;
private String message = "";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_target);
mTargetTextView = (TextView) findViewById(R.id.target_textview);
mTargetButton = (Button) findViewById(R.id.target_button);
message = getIntent().getExtras().getString(EXTRA_KEY);
mTargetButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mTargetTextView.setText(message);
}
});
}
@Override
public void onBackPressed() {
// 此方法会在点击返回键(Back键)时被回调
Log.v(TAG, "onBackPressed...");
Intent intent = new Intent();
Bundle bundle = new Bundle();
bundle.putString(EXTRA_KEY, MESSAGE_OK);
intent.putExtras(bundle);
setResult(RESULT_OK, intent);
super.onBackPressed();
}
@Override
protected void onPause() {
Log.v(TAG, "onPause...");
super.onPause();
}
}
演示效果如下:
需要注意,setResult方法需要在Activity的finish流程之前调用,否则前一个Activity的onActivityResult方法无法获取到返回的intent。