Android不同应用之间的数据传值

前言

不同应用之间的传值可以使用以下几种方式:

  • Intent传值:使用隐式Intent,但需要确保接收方应用可以响应该Intent。

  • Content Provider 通过Content Provider可以在不同的应用之间共享数据。

    一个应用可以将数据暴露给其他应用,并提供读写权限,其他应用可以通过ContentResolver访问这些数据。

  • 文件共享:两个应用之间可以通过文件共享的方式传递数据。

    一个应用可以将数据写入文件,另一个应用可以读取该文件来获取数据。

怎样选择:

假如A是数据的提供方,B是数据的接收方,

如果B一定是A唤起的并且传值的可以使用Intent传值方式

如果B也能自己打开,还要获取A的值,就使用Content Provider方式。

最后再考虑文件共享的方式。

Intent传值

使用Intent在不同的应用之间传递数据,可以通过Intent的putExtra()方法添加数据,并通过startActivity()或startActivityForResult()方法启动另一个应用。对于跨应用传值,可以使用隐式Intent,但需要确保接收方应用可以响应该Intent。

接收方

接收方应用(在AndroidManifest.xml中声明接收Intent的过滤器):

1
2
3
4
5
<receiver android:name=".MyBroadcastReceiver" android:exported="true">
<intent-filter>
<action android:name="com.example.ACTION_SEND_DATA" />
</intent-filter>
</receiver>

在接收方应用中的BroadcastReceiver中处理传递过来的数据:

1
2
3
4
5
6
7
public class MyBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
String value = intent.getStringExtra("key");
// 处理传递过来的数据
}
}

打开Activity

BroadcastReceiver 里启动 Activity 只有一条铁律:

必须给 Intent 加 FLAG_ACTIVITY_NEW_TASK,否则在 Android 10+ 会直接抛 android.util.AndroidRuntimeException: Calling startActivity() from outside of an Activity context requires the FLAG_ACTIVITY_NEW_TASK flag.

示例:

1
2
3
4
5
6
7
8
9
10
11
12
class MyReceiver : BroadcastReceiver() {
override fun onReceive(ctx: Context, intent: Intent) {
val i = Intent(ctx, TargetActivity::class.java).apply {
// 必须加
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
// 如果还想把已存在的 Activity 提到前台而不是新建,可再加
// addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP)
putExtra("key", "value") // 可选传参
}
ctx.startActivity(i)
}
}

发送方

发送方应用:

1
2
3
4
5
Intent intent = new Intent();
intent.setPackage(null)
intent.setAction("com.example.ACTION_SEND_DATA");
intent.putExtra("key", "value");
startActivity(intent);

注意

兼容SDK34

如果发送方 targetSdk ≥ 34 且没有

1
intent.setPackage("xxx")

系统会只把广播派给“同一应用” 的 Receiver,别的 App 即使 action 相同也收不到。
想让外部 App 也能收到,需要发送方加

1
intent.setPackage(null)

并在接收方 App 的 manifest 里打开

1
<receiver android:exported="true" …>

action命名规则

必须是“合法 Java 包名风格”的字符串:

  • 只能包含 A-Z a-z 0-9 _ . 这 5 种字符
  • 必须以 字母 开头
  • 不能出现连续的点
  • 不能是空串

例:

  • com.example.ACTION_SEND_DATA
  • my_action
  • com.example.action-send-data(含连字符)
  • 9action(数字开头)

唯一性规则(运行期匹配)

  • 全局范围内必须保证 不跟别人撞名
  • 推荐用 “反域名 + 模块 + 动作” 的写法,就像给广播起个“包名”。

例:cn.myapp.inventory.action.STOCK_CHANGED

Content Provider

Content Provider:通过Content Provider可以在不同的应用之间共享数据。

一个应用可以将数据暴露给其他应用,并提供读写权限,其他应用可以通过ContentResolver访问这些数据。

这里使用ContentProvider来作为数据的存储地方。

注意:

Content Provider 提供数据的时候,如果提供方APP没有启动,获取方也是获取不到数据的。

提供方配置

在数据提供方应用中,定义一个Content Provider并在AndroidManifest.xml中注册:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.Context;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.net.Uri;
import android.text.TextUtils;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import java.util.Iterator;
import java.util.Objects;

public class ZSpProvider extends ContentProvider {
private static final String AUTHORITY = "com.xhkjedu.mc.sharedpreferencesprovider";
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/spData");

public static final String PARAM_KEY = "key";

public static final String PARAM_VALUE = "value";

private SharedPreferences mStore;

public static Cursor query(Context context, String... keys) {
return context.getContentResolver().query(CONTENT_URI, keys, null, null, null);
}

public static String getString(Context context, String key) {
return getString(context, key, null);
}

public static String getString(Context context, String key, String defValue) {
Cursor cursor = query(context, key);
if (cursor == null) {
return "";
}
String ret = defValue;
if (cursor.moveToNext()) {
ret = cursor.getString(0);
if (TextUtils.isEmpty(ret)) {
ret = defValue;
}
}
cursor.close();
return ret;
}

public static int getInt(Context context, String key, int defValue) {
Cursor cursor = query(context, key);
int ret = defValue;
if (cursor.moveToNext()) {
try {
ret = cursor.getInt(0);
} catch (Exception ignored) {

}
}
cursor.close();
return ret;
}

public static Uri save(Context context, ContentValues values) {
return context.getContentResolver().insert(CONTENT_URI, values);
}

public static Uri save(Context context, String key, String value) {
ContentValues values = new ContentValues(1);
values.put(key, value);
return save(context, values);
}

public static Uri save(Context context, String key, int value) {
ContentValues values = new ContentValues(1);
values.put(key, value);
return save(context, values);
}

public static Uri remove(Context context, String key) {
return save(context, key, null);
}

private Cursor createSingleCursor(String key, String value) {
MatrixCursor cursor = new MatrixCursor(new String[]{key}, 1);
if (!TextUtils.isEmpty(value)) {
cursor.addRow(new Object[]{value});
}
return cursor;
}

private Cursor createCursor(String[] keys, String[] values) {
MatrixCursor cursor = new MatrixCursor(keys, 1);
cursor.addRow(values);
return cursor;
}


private String getValue(String key, String defValue) {
return mStore.getString(key, defValue);
}

private void save(ContentValues values) {
String key;
String value;
Iterator<String> iterator = values.keySet().iterator();
SharedPreferences.Editor editor = mStore.edit();
while (iterator.hasNext()) {
key = iterator.next();
value = values.getAsString(key);
if (!TextUtils.isEmpty(key)) {
if (value != null) {
editor.putString(key, value);
} else {
editor.remove(key);
}
}
}
editor.apply();
}

private void save(String key, String value) {
SharedPreferences.Editor editor = mStore.edit();
if (value != null) {
editor.putString(key, value);
} else {
editor.remove(key);
}
editor.apply();
}

private void remove(String key) {
SharedPreferences.Editor editor = mStore.edit();
editor.remove(key);
editor.apply();
}

@Override
public boolean onCreate() {
String DB_NAME = "SPData";
mStore = Objects.requireNonNull(getContext()).getSharedPreferences(DB_NAME, Context.MODE_PRIVATE);
return true;
}

@Nullable
@Override
public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
int size = projection == null ? 0 : projection.length;
if (size > 0) {
String[] values = new String[size];
for (int i = 0; i < size; i++) {
values[i] = getValue(projection[i], null);
}
return createCursor(projection, values);
}
String key = uri.getQueryParameter(PARAM_KEY);
String value = null;
if (!TextUtils.isEmpty(key)) {
value = getValue(key, null);
}
return createSingleCursor(key, value);
}

@Nullable
@Override
public String getType(@NonNull Uri uri) {
return "";
}

@Nullable
@Override
public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
if (values != null && values.size() > 0) {
save(values);
} else {
String key = uri.getQueryParameter(PARAM_KEY);
String value = uri.getQueryParameter(PARAM_VALUE);
if (!TextUtils.isEmpty(key)) {
save(key, value);
}
}
return null;
}

@Override
public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
String key = selection != null ? selection : uri.getQueryParameter(PARAM_KEY);
if (!TextUtils.isEmpty(key)) {
remove(key);
return 1;
}
return 0;
}

@Override
public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) {
if (values != null && values.size() > 0) {
save(values);
return values.size();
}
String key = uri.getQueryParameter(PARAM_KEY);
String value = uri.getQueryParameter(PARAM_VALUE);
if (!TextUtils.isEmpty(key)) {
save(key, value);
return 1;
}
return 0;
}
}

在AndroidMenifest.xml中使用<provider>标签来设置ContentProvider

1
2
3
4
5
<provider
android:exported="true"
android:name="com.xhkjedu.xh_control_appstore.provider.ZSpProvider"
android:authorities="cn.psvmc.myapp.sharedpreferencesprovider">
</provider>

注:

android:exported="true"后才能在其他应用中访问。

AUTHORITY配置要和android:authorities保持一致即可,不一定要和项目包名一致。

提供方保存数据

1
ZSpProvider.save(this, "myblog", "www.psvmc.cn");

获取方配置

获取方同样添加上面的类,不用添加到AndroidMenifest.xml

但是要在AndroidMenifest.xmlapplication同级添加提供方的包名

1
2
3
<queries>
<package android:name="com.xhkjedu.manageapp"/>
</queries>

其中com.xhkjedu.manageapp是提供方的包名,一定要添加该配置否则获取不到数据。

获取数据

1
2
val vaue = ZSpProvider.getString(this, "myblog")
Log.i(TAG, "onCreate: $vaue")

文件共享

文件共享:两个应用之间可以通过文件共享的方式传递数据。一个应用可以将数据写入文件,另一个应用可以读取该文件来获取数据。

示例代码:

发送方应用:

1
2
3
4
5
6
7
8
9
String data = "Hello, this is data to be shared";
File file = new File(getExternalFilesDir(null), "shared_data.txt");
try {
FileOutputStream fos = new FileOutputStream(file);
fos.write(data.getBytes());
fos.close();
} catch (IOException e) {
e.printStackTrace();
}

接收方应用:

1
2
3
4
5
6
7
8
9
10
11
File file = new File(getExternalFilesDir(null), "shared_data.txt");
try {
FileInputStream fis = new FileInputStream(file);
byte[] buffer = new byte[(int) file.length()];
fis.read(buffer);
fis.close();
String data = new String(buffer);
// 处理读取到的数据
} catch (IOException e) {
e.printStackTrace();
}