android 7.0邮件添加附件 exposed beyond app through ClipData.Item.getUri()
使用uniapp官方h5+ api,实现邮件功能,运行到真机后提示No Activity found to handle Intent,手机上安装邮箱应用后,该报错没有了,并且代码可以设置邮件内容,但是添加附件会出现上方报错
var msg = plus.messaging.createMessage(plus.messaging.TYPE_EMAIL);
msg.to = ["1.com"];
msg.cc = ["2.com"];
msg.subject = "测试邮件";
msg.body = "This is Pandora example111111111 test message";
var zipfile = plus.io.convertAbsoluteFileSystem(
"/storage/emulated/0" + this.excelPath + ".zip"
);
msg.addAttachment(plus.io.convertLocalFileSystemURL(zipfile));
plus.messaging.sendMessage(
msg,
function (res) {
console.log("Send success!", res);
},
function (err) {
console.log("Send failed!", err);
}
);
var msg = plus.messaging.createMessage(plus.messaging.TYPE_EMAIL);
msg.to = ["1.com"];
msg.cc = ["2.com"];
msg.subject = "测试邮件";
msg.body = "This is Pandora example111111111 test message";
var zipfile = plus.io.convertAbsoluteFileSystem(
"/storage/emulated/0" + this.excelPath + ".zip"
);
msg.addAttachment(plus.io.convertLocalFileSystemURL(zipfile));
plus.messaging.sendMessage(
msg,
function (res) {
console.log("Send success!", res);
},
function (err) {
console.log("Send failed!", err);
}
);
学习了原生安卓怎么实现该功能,试了试h5+ api参考原生安卓的写法,也能正常运行发送邮件,但是添加附件部分经过尝试也不行
var Intent = plus.android.importClass("android.content.Intent");
var mEmailIntent = new Intent();
var Uri = plus.android.importClass("android.net.Uri");
const uri = Uri.parse("mailto:");
mEmailIntent.setData(uri);
mEmailIntent.putExtra(Intent.EXTRA_EMAIL, ["347891134@qq.com"]);
mEmailIntent.putExtra(Intent.EXTRA_CC, ["347891134@qq.com"]);
mEmailIntent.putExtra(Intent.EXTRA_SUBJECT, "测试邮件");
mEmailIntent.putExtra(
Intent.EXTRA_TEXT,
"This is Pandora example test message"
);
mEmailIntent.setAction(Intent.ACTION_SENDTO);
var main = plus.android.runtimeMainActivity();
main.startActivity(mEmailIntent);
var Intent = plus.android.importClass("android.content.Intent");
var mEmailIntent = new Intent();
var Uri = plus.android.importClass("android.net.Uri");
const uri = Uri.parse("mailto:");
mEmailIntent.setData(uri);
mEmailIntent.putExtra(Intent.EXTRA_EMAIL, ["347891134@qq.com"]);
mEmailIntent.putExtra(Intent.EXTRA_CC, ["347891134@qq.com"]);
mEmailIntent.putExtra(Intent.EXTRA_SUBJECT, "测试邮件");
mEmailIntent.putExtra(
Intent.EXTRA_TEXT,
"This is Pandora example test message"
);
mEmailIntent.setAction(Intent.ACTION_SENDTO);
var main = plus.android.runtimeMainActivity();
main.startActivity(mEmailIntent);
最后通过阅读uniapp官方h5+ api源码地址
结合网上资料,发现原因在于版本问题,Android7.0以上更改了文件url处理方式
package com.wgg.sharefile;
import android.content.Context;
import android.net.Uri;
import android.os.Build;
import android.support.v4.content.FileProvider;
import java.io.File;
public class FileUtils {
public static Uri getUri(Context context,String filePath){
File tempFile = new File(filePath);
//判断版本
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { //如果在Android7.0以上,使用FileProvider获取Uri
try{
return FileProvider.getUriForFile(context, context.getPackageName() + ".provider", tempFile);
// return FileProvider.getUriForFile(context, "uni.UNI899E01B.fileprovider", tempFile);
}catch (Exception e){
e.printStackTrace();
}
} else { //否则使用Uri.fromFile(file)方法获取Uri
return Uri.fromFile(tempFile);
}
return null;
}
}
package com.wgg.sharefile;
import android.content.Context;
import android.net.Uri;
import android.os.Build;
import android.support.v4.content.FileProvider;
import java.io.File;
public class FileUtils {
public static Uri getUri(Context context,String filePath){
File tempFile = new File(filePath);
//判断版本
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { //如果在Android7.0以上,使用FileProvider获取Uri
try{
return FileProvider.getUriForFile(context, context.getPackageName() + ".provider", tempFile);
// return FileProvider.getUriForFile(context, "uni.UNI899E01B.fileprovider", tempFile);
}catch (Exception e){
e.printStackTrace();
}
} else { //否则使用Uri.fromFile(file)方法获取Uri
return Uri.fromFile(tempFile);
}
return null;
}
}
尝试降级安卓包版本
uniapp manifest中设置targetSdkVersion小于23,把安卓包降到低版本,这样安装包时会提示低版本,但是又会导致新问题
/* 应用发布信息 */
"distribute": {
/* android打包配置 */
"android": {
"permissions": [
],
"targetSdkVersion": 22
},
/* 应用发布信息 */
"distribute": {
/* android打包配置 */
"android": {
"permissions": [
],
"targetSdkVersion": 22
},
提示没有权限,参考大神代码,实现的分享插件https://ext.dcloud.net.cn/plugin?id=2307,其中原生安卓代码是这样解决的https://github.com/lifejwang11/FileShare/blob/master/Android/sharefile/src/main/java/com/wgg/sharefile/Share.java
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) {
List<ResolveInfo> resInfoList = activity.getPackageManager().queryIntentActivities(shareIntent, PackageManager.MATCH_DEFAULT_ONLY);
for (ResolveInfo resolveInfo : resInfoList) {
String packageName = resolveInfo.activityInfo.packageName;
activity.grantUriPermission(packageName, shareFileUri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
}
}
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) {
List<ResolveInfo> resInfoList = activity.getPackageManager().queryIntentActivities(shareIntent, PackageManager.MATCH_DEFAULT_ONLY);
for (ResolveInfo resolveInfo : resInfoList) {
String packageName = resolveInfo.activityInfo.packageName;
activity.grantUriPermission(packageName, shareFileUri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
}
}
所以最终还是要通过安卓原生代码来解决,开始学习安卓开发,打包安卓原生插件
Intent
在 Android 开发中,Intent 是一个重要的概念,它用于在不同的组件之间传递消息、执行操作或启动活动(Activity)。Intent 可以在应用内部组件之间进行通信,也可以与其他应用程序进行通信。
在这段代码中,Intent sendIntent = new Intent(Intent.ACTION_SEND);
创建了一个新的 Intent 实例,并指定了其动作为发送操作。这意味着这个 Intent 被用于启动一个发送的动作,通常是启动一个应用程序来发送数据(在这种情况下是多媒体消息)。
Intent 可以包含各种信息,比如操作的类型、数据的 URI、数据的 MIME 类型等。在这个例子中,通过 sendIntent.setType("*/*");
设置了要发送的数据类型为任意类型,表示该 Intent 可以发送任意类型的数据。
一旦创建了 Intent 实例,并设置了必要的信息,就可以使用 startActivity()
方法来启动该 Intent,从而执行相应的操作。在这个例子中,_messaging.mWebview.getActivity().startActivity(sendIntent);
启动了发送 Intent,使得用户可以选择一个适合的应用程序来发送多媒体消息。
总的来说,Intent 是在 Android 中用于执行各种操作和通信的一种重要机制,它允许不同组件之间进行交互,并在 Android 应用程序中实现了丰富的功能。
ACTION_SEND
if( attachMentsSize > 1 ){
mEmailIntent.setAction(Intent.ACTION_SEND_MULTIPLE);
mEmailIntent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, pMessaging.mAttachments);
mEmailIntent.setType("message/rfc822");
}else if( attachMentsSize == 1 ){
mEmailIntent.putExtra(Intent.EXTRA_STREAM, pMessaging.mAttachments.get(0));
mEmailIntent.setType(PdrUtil.getMimeType(pMessaging.mAttachments.get(0).getPath()));
mEmailIntent.setType("message/rfc882");
mEmailIntent.setAction(Intent.ACTION_SEND);
}else{
mEmailIntent.setAction(Intent.ACTION_SENDTO);
}
if( attachMentsSize > 1 ){
mEmailIntent.setAction(Intent.ACTION_SEND_MULTIPLE);
mEmailIntent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, pMessaging.mAttachments);
mEmailIntent.setType("message/rfc822");
}else if( attachMentsSize == 1 ){
mEmailIntent.putExtra(Intent.EXTRA_STREAM, pMessaging.mAttachments.get(0));
mEmailIntent.setType(PdrUtil.getMimeType(pMessaging.mAttachments.get(0).getPath()));
mEmailIntent.setType("message/rfc882");
mEmailIntent.setAction(Intent.ACTION_SEND);
}else{
mEmailIntent.setAction(Intent.ACTION_SENDTO);
}
ACTION_SENDTO
动作通常用于发送纯文本邮件。
ACTION_SEND
动作可以启动一个发送邮件的操作。
ACTION_SEND_MULTIPLE
动作则用于发送多个附件的邮件。
使用 ACTION_SEND
动作的 Intent,可以通过 EXTRA_STREAM
属性来附加一个附件。这样,邮件客户端就可以从 Intent 中获取附件并将其添加到邮件中,然后发送邮件。
mEmailIntent.setType("message/rfc822")
邮件的 MIME 类型设置为 message/rfc822
,这是邮件的标准类型,表示邮件内容遵循 RFC 822 标准。这个设置表明邮件内容是符合邮件格式规范的文本内容。
uts示例
也是一种实现原生插件的写法,https://ext.dcloud.net.cn/plugin?id=9295,但是研究后发现太复杂了,放弃放弃
import Context from "android.content.Context";
import BatteryManager from "android.os.BatteryManager";
import {
GetBatteryInfo,
GetBatteryInfoOptions,
GetBatteryInfoSuccess,
GetBatteryInfoResult,
GetBatteryInfoSync,
} from "../interface.uts";
import IntentFilter from "android.content.IntentFilter";
import Intent from "android.content.Intent";
/**
* 异步获取电量
*/
export const getBatteryInfo: GetBatteryInfo = function (
options: GetBatteryInfoOptions
) {
const context = UTSAndroid.getAppContext();
if (context != null) {
const manager = context.getSystemService(
Context.BATTERY_SERVICE
) as BatteryManager;
const level = manager.getIntProperty(
BatteryManager.BATTERY_PROPERTY_CAPACITY
);
let ifilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
let batteryStatus = context.registerReceiver(null, ifilter);
let status = batteryStatus?.getIntExtra(BatteryManager.EXTRA_STATUS, -1);
let isCharging =
status == BatteryManager.BATTERY_STATUS_CHARGING ||
status == BatteryManager.BATTERY_STATUS_FULL;
const res: GetBatteryInfoSuccess = {
errMsg: "getBatteryInfo:ok",
level,
isCharging: isCharging,
};
options.success?.(res);
options.complete?.(res);
} else {
const res = new UniError(
"uni-getBatteryInfo",
1001,
"getBatteryInfo:fail getAppContext is null"
);
options.fail?.(res);
options.complete?.(res);
}
};
/**
* 同步获取电量示例
*/
export const getBatteryInfoSync: GetBatteryInfoSync =
function (): GetBatteryInfoResult {
const context = UTSAndroid.getAppContext();
if (context != null) {
const manager = context.getSystemService(
Context.BATTERY_SERVICE
) as BatteryManager;
const level = manager.getIntProperty(
BatteryManager.BATTERY_PROPERTY_CAPACITY
);
let ifilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
let batteryStatus = context.registerReceiver(null, ifilter);
let status = batteryStatus?.getIntExtra(BatteryManager.EXTRA_STATUS, -1);
let isCharging =
status == BatteryManager.BATTERY_STATUS_CHARGING ||
status == BatteryManager.BATTERY_STATUS_FULL;
const res: GetBatteryInfoResult = {
level: level,
isCharging: isCharging,
};
return res;
} else {
/**
* 无有效上下文
*/
const res: GetBatteryInfoResult = {
level: -1,
isCharging: false,
};
return res;
}
};
import Context from "android.content.Context";
import BatteryManager from "android.os.BatteryManager";
import {
GetBatteryInfo,
GetBatteryInfoOptions,
GetBatteryInfoSuccess,
GetBatteryInfoResult,
GetBatteryInfoSync,
} from "../interface.uts";
import IntentFilter from "android.content.IntentFilter";
import Intent from "android.content.Intent";
/**
* 异步获取电量
*/
export const getBatteryInfo: GetBatteryInfo = function (
options: GetBatteryInfoOptions
) {
const context = UTSAndroid.getAppContext();
if (context != null) {
const manager = context.getSystemService(
Context.BATTERY_SERVICE
) as BatteryManager;
const level = manager.getIntProperty(
BatteryManager.BATTERY_PROPERTY_CAPACITY
);
let ifilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
let batteryStatus = context.registerReceiver(null, ifilter);
let status = batteryStatus?.getIntExtra(BatteryManager.EXTRA_STATUS, -1);
let isCharging =
status == BatteryManager.BATTERY_STATUS_CHARGING ||
status == BatteryManager.BATTERY_STATUS_FULL;
const res: GetBatteryInfoSuccess = {
errMsg: "getBatteryInfo:ok",
level,
isCharging: isCharging,
};
options.success?.(res);
options.complete?.(res);
} else {
const res = new UniError(
"uni-getBatteryInfo",
1001,
"getBatteryInfo:fail getAppContext is null"
);
options.fail?.(res);
options.complete?.(res);
}
};
/**
* 同步获取电量示例
*/
export const getBatteryInfoSync: GetBatteryInfoSync =
function (): GetBatteryInfoResult {
const context = UTSAndroid.getAppContext();
if (context != null) {
const manager = context.getSystemService(
Context.BATTERY_SERVICE
) as BatteryManager;
const level = manager.getIntProperty(
BatteryManager.BATTERY_PROPERTY_CAPACITY
);
let ifilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
let batteryStatus = context.registerReceiver(null, ifilter);
let status = batteryStatus?.getIntExtra(BatteryManager.EXTRA_STATUS, -1);
let isCharging =
status == BatteryManager.BATTERY_STATUS_CHARGING ||
status == BatteryManager.BATTERY_STATUS_FULL;
const res: GetBatteryInfoResult = {
level: level,
isCharging: isCharging,
};
return res;
} else {
/**
* 无有效上下文
*/
const res: GetBatteryInfoResult = {
level: -1,
isCharging: false,
};
return res;
}
};
学习如何打包原生插件
https://juejin.cn/post/7194743378830409783
https://juejin.cn/post/7195018409543729189/
理解Android Studio中的Gradle
https://zhuanlan.zhihu.com/p/139685763
Android Studio的gradle sync是什么?
https://www.zhihu.com/question/369909080
和Maven一样是项目管理工具,可以对代码进行构建、依赖管理。
不用Maven、Gradle这些类似工具你只能手动加载包到你的Libs下,麻烦且容易出问题。
现在Web开发一般都会采用Maven,而Android开发由于Google推出的AS是指定的Gradle自然也就采用了它。
Gradle sync就是开始执行Gradle脚本,一般会将声明中的包库进行下载,由于不可描述的原因在国内的确存在fail的情况,作为程序员请先学会科学的上网方式。
安装Gradle
下载慢解决方案:使用浏览器下载到本地,然后配置Android Studio使用本地路径的Gradle:
1.https://gradle.org/releases/直接官方下载binary-only版本,格式是这样的:gradle-7.2-bin,这种包就是所需的。
2.修改Android Studio使用本地路径的Gradle,然后重新同步(同步指的就是点击右上角的小象图标,就会触发gradle),具体操作:解压gradle压缩包,file>setttings>gradle中使用本地路径解压后的gradle,默认置Android Studio使用的高版本java,可以安装7.2,7.2以上的版本没有尝试,参考文章:https://blog.csdn.net/C_biubiubiu/article/details/109863975。
没有assembleRelease
file -> setting -> Experimental -> 勾上 Configure all Gradle tasks during Gradle Sync (this can make Gradle Sync slower)选项,然后再选择file -> Sync Project......就有了
assembleRelease报错
org.gradle.jvmargs=-Xmx1536M \
--add-exports=java.base/sun.nio.ch=ALL-UNNAMED \
--add-opens=java.base/java.lang=ALL-UNNAMED \
--add-opens=java.base/java.lang.reflect=ALL-UNNAMED \
--add-opens=java.base/java.io=ALL-UNNAMED \
--add-exports=jdk.unsupported/sun.misc=ALL-UNNAMED
org.gradle.jvmargs=-Xmx1536M \
--add-exports=java.base/sun.nio.ch=ALL-UNNAMED \
--add-opens=java.base/java.lang=ALL-UNNAMED \
--add-opens=java.base/java.lang.reflect=ALL-UNNAMED \
--add-opens=java.base/java.io=ALL-UNNAMED \
--add-exports=jdk.unsupported/sun.misc=ALL-UNNAMED
需要更改build.gradle为对应的sdk版本
compileSdkVersion 30
compileSdkVersion 30
我的安卓插件
package com.example.mylibrary;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import io.dcloud.feature.uniapp.annotation.UniJSMethod;
import io.dcloud.feature.uniapp.bridge.UniJSCallback;
import io.dcloud.feature.uniapp.common.UniModule;
import android.app.Activity;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.net.Uri;
import android.os.Build;
import androidx.core.content.FileProvider;
import java.io.File;
import java.util.List;
// 一定要继承UniModule
public class testModule extends UniModule {
// 使用UniJSMethod注解,才能使用js调用
@UniJSMethod(uiThread = true)
public void email (JSONObject options, UniJSCallback callback) {
try{
if (mUniSDKInstance.getContext() instanceof Activity) {
Activity context = (Activity) mUniSDKInstance.getContext();
String filePath = options.getString("filePath");
Uri streamPath = FileProvider.getUriForFile(context, context.getPackageName() + ".provider", new File(filePath));
Intent mEmailIntent = new Intent();
Uri uri = Uri.parse("mailto:");
JSONArray to = options.getJSONArray( "to");
//创建一个与JSONArray 长度相同的String数组
String[] strings_to = new String[to.size()];
//使用JSONArray 中的toArray进行转换
String[] strings_arr_to = to.toArray(strings_to);
mEmailIntent.putExtra(Intent.EXTRA_EMAIL, strings_arr_to);
JSONArray cc = options.getJSONArray( "cc");
String[] strings_cc = new String[cc.size()];
String[] strings_arr_cc = cc.toArray(strings_cc);
mEmailIntent.putExtra(Intent.EXTRA_CC, strings_arr_cc);
mEmailIntent.putExtra(Intent.EXTRA_SUBJECT, options.getString("subject"));
mEmailIntent.putExtra(
Intent.EXTRA_TEXT,
options.getString("body")
);
mEmailIntent.putExtra(Intent.EXTRA_STREAM, streamPath);
mEmailIntent.setAction(Intent.ACTION_SEND);
mEmailIntent.setData(uri);
mEmailIntent.setType("*/*");
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) {
List<ResolveInfo> resInfoList = context.getPackageManager().queryIntentActivities(mEmailIntent, PackageManager.MATCH_DEFAULT_ONLY);
for (ResolveInfo resolveInfo : resInfoList) {
String packageName = resolveInfo.activityInfo.packageName;
context.grantUriPermission(packageName, streamPath, Intent.FLAG_GRANT_READ_URI_PERMISSION);
}
}
context.startActivity(Intent.createChooser(mEmailIntent, "邮件"));
callback.invoke(new JSONObject() {{
put("code", 200);
put("result", "成功");
}});
}
}catch (Exception e){
e.printStackTrace();
callback.invoke(new JSONObject() {{
put("code", -1);
put("result", e.getMessage());
}});
}
}
}
package com.example.mylibrary;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import io.dcloud.feature.uniapp.annotation.UniJSMethod;
import io.dcloud.feature.uniapp.bridge.UniJSCallback;
import io.dcloud.feature.uniapp.common.UniModule;
import android.app.Activity;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.net.Uri;
import android.os.Build;
import androidx.core.content.FileProvider;
import java.io.File;
import java.util.List;
// 一定要继承UniModule
public class testModule extends UniModule {
// 使用UniJSMethod注解,才能使用js调用
@UniJSMethod(uiThread = true)
public void email (JSONObject options, UniJSCallback callback) {
try{
if (mUniSDKInstance.getContext() instanceof Activity) {
Activity context = (Activity) mUniSDKInstance.getContext();
String filePath = options.getString("filePath");
Uri streamPath = FileProvider.getUriForFile(context, context.getPackageName() + ".provider", new File(filePath));
Intent mEmailIntent = new Intent();
Uri uri = Uri.parse("mailto:");
JSONArray to = options.getJSONArray( "to");
//创建一个与JSONArray 长度相同的String数组
String[] strings_to = new String[to.size()];
//使用JSONArray 中的toArray进行转换
String[] strings_arr_to = to.toArray(strings_to);
mEmailIntent.putExtra(Intent.EXTRA_EMAIL, strings_arr_to);
JSONArray cc = options.getJSONArray( "cc");
String[] strings_cc = new String[cc.size()];
String[] strings_arr_cc = cc.toArray(strings_cc);
mEmailIntent.putExtra(Intent.EXTRA_CC, strings_arr_cc);
mEmailIntent.putExtra(Intent.EXTRA_SUBJECT, options.getString("subject"));
mEmailIntent.putExtra(
Intent.EXTRA_TEXT,
options.getString("body")
);
mEmailIntent.putExtra(Intent.EXTRA_STREAM, streamPath);
mEmailIntent.setAction(Intent.ACTION_SEND);
mEmailIntent.setData(uri);
mEmailIntent.setType("*/*");
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) {
List<ResolveInfo> resInfoList = context.getPackageManager().queryIntentActivities(mEmailIntent, PackageManager.MATCH_DEFAULT_ONLY);
for (ResolveInfo resolveInfo : resInfoList) {
String packageName = resolveInfo.activityInfo.packageName;
context.grantUriPermission(packageName, streamPath, Intent.FLAG_GRANT_READ_URI_PERMISSION);
}
}
context.startActivity(Intent.createChooser(mEmailIntent, "邮件"));
callback.invoke(new JSONObject() {{
put("code", 200);
put("result", "成功");
}});
}
}catch (Exception e){
e.printStackTrace();
callback.invoke(new JSONObject() {{
put("code", -1);
put("result", e.getMessage());
}});
}
}
}
FileProvider 路径配置策略的理解
https://blog.csdn.net/u013553529/article/details/83900704
https://juejin.cn/post/6844903476892270605
选择文件
h5+ api获取图片路径
// 从相册中选择图片
console.log("从相册中选择多张图片:");
plus.gallery.pick(
function (e) {
for (var i in e.files) {
console.log(e.files[i]);
}
},
function (e) {
console.log("取消选择图片");
},
{ filter: "image", multiple: true }
);
// 从相册中选择图片
console.log("从相册中选择多张图片:");
plus.gallery.pick(
function (e) {
for (var i in e.files) {
console.log(e.files[i]);
}
},
function (e) {
console.log("取消选择图片");
},
{ filter: "image", multiple: true }
);