在Android上使用电容器4共享图像时出错



我有一个Ionic + Capacitor 2项目,我可以使用@capacitor/share插件与其他应用程序(Instagram, Facebook, WhatsApp等)共享图像,并将fileUri作为url属性的值。Android和iOS都能正常工作。

然而,当项目升级到电容器4(我没有做迁移,但创建了一个空白的Ionic项目已经与电容器4,然后在必要时调整我的代码)完全相同的代码逻辑不工作在我的Android设备(在物理设备上测试与Android 8.0.0和Android 9.0.0,两者具有相同的行为)。对于iOS,图片共享工作正常(在安装iOS 16.1.2的iPhone 11上测试)。

共享对话框正常打开并显示所有可用的应用程序,包括Instagram故事和Facebook故事,也就是说,插件正在识别我正在共享图像,否则这些选项不会出现在应用程序列表中。在选择应用程序时获得的一些结果示例如下:

  • Instagram Stories/Facebook Stories: nothing happened .
  • Instagram Feed: Instagram打开,敬酒显示Unable to load image消息,然后Instagram关闭,焦点返回到我的应用程序。
  • WhatsApp/WhatsApp商业:敬酒显示消息Share failed. Try again

在所有情况下,Error: Share cancelled消息也显示在Android Studio控制台。

AndroidManifest.xml中,我已经拥有android.permission.READ_EXTERNAL_STORAGEandroid.permission.WRITE_EXTERNAL_STORAGE权限,我也将图像文件保存在缓存目录中,所以我不知道为什么会发生这个问题。我测试了一下,应用程序正常地将图像保存在缓存目录中,所以问题只是与共享本身有关。

因为我已经可以确认错误是来自本机部分,而不是来自我的TS代码,我将在这里发布,以便有Android开发知识的人(不一定知道电容器)可以帮助。

SharePlugin.java:

package com.capacitorjs.plugins.share;
import android.app.Activity;
import android.app.PendingIntent;
import android.content.*;
import android.net.Uri;
import android.os.Build;
import android.webkit.MimeTypeMap;
import androidx.activity.result.ActivityResult;
import androidx.core.content.FileProvider;
import com.getcapacitor.JSArray;
import com.getcapacitor.JSObject;
import com.getcapacitor.Plugin;
import com.getcapacitor.PluginCall;
import com.getcapacitor.PluginMethod;
import com.getcapacitor.annotation.ActivityCallback;
import com.getcapacitor.annotation.CapacitorPlugin;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import org.json.JSONException;
@CapacitorPlugin(name = "Share")
public class SharePlugin extends Plugin {
private BroadcastReceiver broadcastReceiver;
private boolean stopped = false;
private boolean isPresenting = false;
private ComponentName chosenComponent;
@Override
public void load() {
broadcastReceiver =
new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
chosenComponent = intent.getParcelableExtra(Intent.EXTRA_CHOSEN_COMPONENT);
}
};
getActivity().registerReceiver(broadcastReceiver, new IntentFilter(Intent.EXTRA_CHOSEN_COMPONENT));
}
@ActivityCallback
private void activityResult(PluginCall call, ActivityResult result) {
if (result.getResultCode() == Activity.RESULT_CANCELED && !stopped) {
call.reject("Share canceled");
} else {
JSObject callResult = new JSObject();
callResult.put("activityType", chosenComponent != null ? chosenComponent.getPackageName() : "");
call.resolve(callResult);
}
isPresenting = false;
}
@PluginMethod
public void canShare(PluginCall call) {
JSObject callResult = new JSObject();
callResult.put("value", true);
call.resolve(callResult);
}
@PluginMethod
public void share(PluginCall call) {
if (!isPresenting) {
String title = call.getString("title", "");
String text = call.getString("text");
String url = call.getString("url");
JSArray files = call.getArray("files");
String dialogTitle = call.getString("dialogTitle", "Share");
if (text == null && url == null && (files == null || files.length() == 0)) {
call.reject("Must provide a URL or Message or files");
return;
}
if (url != null && !isFileUrl(url) && !isHttpUrl(url)) {
call.reject("Unsupported url");
return;
}
Intent intent = new Intent(files != null && files.length() > 1 ? Intent.ACTION_SEND_MULTIPLE : Intent.ACTION_SEND);
if (text != null) {
// If they supplied both fields, concat them
if (url != null && isHttpUrl(url)) text = text + " " + url;
intent.putExtra(Intent.EXTRA_TEXT, text);
intent.setTypeAndNormalize("text/plain");
}
if (url != null && isHttpUrl(url) && text == null) {
intent.putExtra(Intent.EXTRA_TEXT, url);
intent.setTypeAndNormalize("text/plain");
} else if (url != null && isFileUrl(url)) {
JSArray filesArray = new JSArray();
filesArray.put(url);
shareFiles(filesArray, intent, call);
}
if (title != null) {
intent.putExtra(Intent.EXTRA_SUBJECT, title);
}
if (files != null && files.length() != 0) {
shareFiles(files, intent, call);
}
int flags = PendingIntent.FLAG_UPDATE_CURRENT;
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
flags = flags | PendingIntent.FLAG_MUTABLE;
}
// requestCode parameter is not used. Providing 0
PendingIntent pi = PendingIntent.getBroadcast(getContext(), 0, new Intent(Intent.EXTRA_CHOSEN_COMPONENT), flags);
Intent chooser = Intent.createChooser(intent, dialogTitle, pi.getIntentSender());
chosenComponent = null;
chooser.addCategory(Intent.CATEGORY_DEFAULT);
stopped = false;
isPresenting = true;
startActivityForResult(call, chooser, "activityResult");
} else {
call.reject("Can't share while sharing is in progress");
}
}
private void shareFiles(JSArray files, Intent intent, PluginCall call) {
List<Object> filesList;
ArrayList<Uri> fileUris = new ArrayList<>();
try {
filesList = files.toList();
for (int i = 0; i < filesList.size(); i++) {
String file = (String) filesList.get(i);
if (isFileUrl(file)) {
String type = getMimeType(file);
if (type == null || filesList.size() > 1) {
type = "*/*";
}
intent.setType(type);
Uri fileUrl = FileProvider.getUriForFile(
getActivity(),
getContext().getPackageName() + ".fileprovider",
new File(Uri.parse(file).getPath())
);
fileUris.add(fileUrl);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && filesList.size() == 1) {
intent.setDataAndType(fileUrl, type);
intent.putExtra(Intent.EXTRA_STREAM, fileUrl);
}
} else {
call.reject("only file urls are supported");
return;
}
}
if (fileUris.size() > 1) {
intent.putExtra(Intent.EXTRA_STREAM, fileUris);
}
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
} catch (Exception ex) {
call.reject(ex.getLocalizedMessage());
return;
}
}
@Override
protected void handleOnDestroy() {
if (broadcastReceiver != null) {
getActivity().unregisterReceiver(broadcastReceiver);
}
}
@Override
protected void handleOnStop() {
super.handleOnStop();
stopped = true;
}
private String getMimeType(String url) {
String type = null;
String extension = MimeTypeMap.getFileExtensionFromUrl(url);
if (extension != null) {
type = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
}
return type;
}
private boolean isFileUrl(String url) {
return url.startsWith("file:");
}
private boolean isHttpUrl(String url) {
return url.startsWith("http");
}
}

可以看到,它在if (result.getResultCode() == Activity.RESULT_CANCELED && !stopped)之后的行中,返回值Share canceled

我如何进一步调查这个问题,并获得更多关于它发生的原因的信息?

共享一个文件有一个可疑的条件:

Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q

此代码必须运行,否则它将为较低API级别的单个文件发送无效的Intent:

intent.putExtra(Intent.EXTRA_STREAM, fileUrl);

您可以尝试使用FileProvider来共享图像文件,而不是传递文件URI。这是因为,从Android 7.0开始,不再可能使用文件URI共享文件。相反,你应该将文件包装在ContentProvider中,并共享提供者的URI。

要使用FileProvider,你需要将FileProvider元素添加到你的应用程序的AndroidManifest.xml文件中,并指定提供商的android:authorities属性,以及你想要共享的文件的路径。然后,你可以使用FileProvider返回一个Uri,你可以将这个Uri传递给你用来共享图像的Intent。

我将FileProvider添加到你的方法中,你可以尝试一下,但我没有测试它,检查它是否应该工作。

@PluginMethod
public void share(PluginCall call) {
String url = call.getString("url");
String title = call.getString("title");
String message = call.getString("message");
String dialogTitle = call.getString("dialogTitle");
JSArray files = call.getArray("files");
ArrayList<Uri> fileUris = new ArrayList<>();
if (url != null && !url.isEmpty()) {
fileUris.add(Uri.parse(url));
}
if (files != null) {
for (int i = 0; i < files.length(); i++) {
try {
JSObject file = files.getObject(i);
String fileUrl = file.getString("url");
if (fileUrl != null && !fileUrl.isEmpty()) {
File fileToShare = new File(fileUrl);
Uri fileUri = FileProvider.getUriForFile(getContext(), getContext().getPackageName() + ".fileprovider", fileToShare);
fileUris.add(fileUri);
}
} catch (JSONException e) {
call.error("Error getting files to share", e);
return;
}
}
}
if (fileUris.isEmpty()) {
call.error("No files to share");
return;
}
Intent shareIntent = new Intent(Intent.ACTION_SEND_MULTIPLE);
shareIntent.setType("image/*");
shareIntent.putExtra(Intent.EXTRA_SUBJECT, title);
shareIntent.putExtra(Intent.EXTRA_TEXT, message);
shareIntent.putExtra(Intent.EXTRA_TITLE, dialogTitle);
shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
shareIntent.putParcelableArrayList  shareIntent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, fileUris);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
PackageManager packageManager = getContext().getPackageManager();
List<ResolveInfo> resolveInfoList = packageManager.queryIntentActivities(shareIntent, PackageManager.MATCH_DEFAULT_ONLY);
for (ResolveInfo resolveInfo : resolveInfoList) {
String packageName = resolveInfo.activityInfo.packageName;
getContext().grantUriPermission(packageName, fileUris.get(0), Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);
}
}
PendingIntent pendingIntent =
PendingIntent.getBroadcast(getContext(), 0, new Intent(Intent.EXTRA_CHOSEN_COMPONENT), PendingIntent.FLAG_CANCEL_CURRENT);
shareIntent.putExtra(Intent.EXTRA_CHOOSER_REQUIRES_INTENT, pendingIntent);
isPresenting = true;
getActivity().startActivityForResult(Intent.createChooser(shareIntent, dialogTitle), SAVE_IMAGE_REQUEST_CODE);
}

最新更新