compose调用系统分享功能图片文件
- 简介
- UI界面
- 提供给外部程序的文件访问权限
- 创建FileProvider
- 设置共享文件夹
- 通用分享工具
- 虚拟机验证结果
- 参考
本系列用于新人安卓基础入门学习笔记,有任何不同的见解欢迎留言
运行环境 jdk17 andriod 34 compose material3
简介
本案例采用 provider来分享当前应用下的文件,其他系统文件直接通过context地址直接获取
本案例是直接 【MediaProvider】content://media/external/images/media,来让其他app直接访问,如果是系统文件请直接忽略provider相关设置
UI界面
package com.example.myapplication.ui
import android.app.Activity
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Delete
import androidx.compose.material.icons.filled.Edit
import androidx.compose.material.icons.filled.MoreVert
import androidx.compose.material.icons.filled.Share
import androidx.compose.material3.BottomAppBar
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.Scaffold
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Text
import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.navigation.NavHostController
import com.example.myapplication.R
import com.example.myapplication.common.ShareUtil
import com.example.myapplication.common.Utils
import com.example.myapplication.common.ui.FullScreenImage
import com.example.myapplication.entity.ImageEntity
import java.io.File
var snackbarHostState = SnackbarHostState()
@Composable
fun ImageDetail(imageEntity: ImageEntity, mainController: NavHostController) {
Scaffold(
snackbarHost = {
SnackbarHost(hostState = snackbarHostState, modifier = Modifier.padding(0.dp))
},
topBar = {
ImageTopBar(imageEntity.name, mainController)
},
bottomBar = {
GetBottomBar(imageEntity.file)
}
) { innerPadding ->
FullScreenImage(imageEntity = imageEntity, modifier = Modifier.padding(innerPadding))
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun GetBottomBar(file: File) {
val scope = rememberCoroutineScope()
val activity = LocalContext.current as Activity
BottomAppBar(
containerColor = MaterialTheme.colorScheme.primaryContainer,
contentColor = MaterialTheme.colorScheme.primary,
) {
Row(
Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceEvenly,
verticalAlignment = Alignment.CenterVertically
) {
val buttonModifier = Modifier.size(70.dp)
val IconModifier = Modifier.size(30.dp)
val message = stringResource(id = R.string.empty_ui)
IconButton(
onClick = { Utils.message(scope, message, snackbarHostState) },
modifier = buttonModifier
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally
) {
Icon(
modifier = IconModifier,
imageVector = Icons.Filled.Edit,
contentDescription = "Localized description"
)
Text(
text = "编辑",
fontSize = 12.sp,
)
}
}
IconButton(
onClick = {
// 打开系统分享
ShareUtil.shareImage(
activity,
"com.example.myapplication.fileprovider",
file.name,
file.path
)
},
modifier = buttonModifier
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally
) {
Icon(
modifier = IconModifier,
imageVector = Icons.Filled.Share,
contentDescription = "Localized description"
)
Text(text = "分享", fontSize = 12.sp)
}
}
IconButton(
onClick = { Utils.message(scope, message, snackbarHostState) },
modifier = buttonModifier
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally
) {
Icon(
modifier = IconModifier,
imageVector = Icons.Filled.Delete,
contentDescription = "Localized description"
)
Text(text = "删除", fontSize = 12.sp)
}
}
IconButton(
onClick = { Utils.message(scope, message, snackbarHostState) },
modifier = buttonModifier
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally
) {
Icon(
modifier = IconModifier,
imageVector = Icons.Filled.MoreVert,
contentDescription = "Localized description"
)
Text(text = "更多", fontSize = 12.sp)
}
}
}
}
}
}
提供给外部程序的文件访问权限
- 这里只适用于外部访问当前app下的数据,本案例是直接 content://media/external/images/media,来让其他app直接访问,如果是系统文件请直接忽略这一段
- Android 7.0之前,文件的Uri以file:///形式提供给其他app访问。
- Android 7.0之后,分享文件的Uri发生了变化。为了安全起见,file:///形式的Uri不能正常访问。官方提供了FileProvider,FileProvider生成的Uri会以content://的形式分享给其他app使用。
content形式的Uri可以让其他app临时获得读取(Read)和写入(Write)权限,只要我们在创建Intent时,使用Intent.setFlags()添加权限。只要接收Uri的app在接收的Activity任务栈中处于活动状态,添加的权限就会一直有效,直到app被任务栈移除
创建FileProvider
<application>
....
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="com.example.myapplication.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/filepath" />
</provider>
</application>
- android:authorities 指定可以查找此内容提供程序的权限。可以使用分号分隔多个授权机构。授权机构名称应该使用java风格的命名约定(如com.google.provider.MyProvider),以避免冲突。通常,此名称与描述提供程序数据结构的类实现相同。
- android:resource 文件的权限清单
- android:exported 设置为false,FileProvider不需要公开。
- android:grantUriPermissions 设置为true,这样就能授权接收端的app临时访问权限了。
设置共享文件夹
这里的绝对路径是 /data/data/{package}
例如我的是 /data/data/com.example.myapplication
在res/xml中创建一个资源文件filepath.xml
<?xml version="1.0" encoding="utf-8"?>
<paths>
<files-path name="Pictures" path="/pictures/"/>
</paths>
/data/data/com.example.myapplication/files/pictures/
通用分享工具
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.os.Build;
import android.provider.MediaStore;
import androidx.core.content.FileProvider;
import java.io.File;
public class ShareUtil {
// 原生通用分享文本
public static void shareText(Activity activity, String title, String text) {
Intent sendIntent = new Intent();
sendIntent.setAction(Intent.ACTION_SEND);
sendIntent.putExtra(Intent.EXTRA_TEXT, text);
sendIntent.setType("text/plain");
activity.startActivityForResult(Intent.createChooser(sendIntent, title), 80001);
}
// 原生通用分享图片
public static void shareImage(Activity activity, String authority, String title, File file){
shareImage(activity, authority, title, file, false);
}
public static void shareImage(Activity activity, String authority, String title, File file, boolean isApp) {
Intent sendIntent = new Intent();
sendIntent.setAction(Intent.ACTION_SEND);
Uri uri = getFileUri(activity, authority, file, isApp);
sendIntent.putExtra(Intent.EXTRA_STREAM, uri);
sendIntent.setType("image/png");
activity.startActivityForResult(Intent.createChooser(sendIntent, title), 80002);
}
// 通用文件
public static void shareFile(Activity activity, String authority, String type, File file, boolean isApp) {
Intent sendIntent = new Intent();
sendIntent.setAction(Intent.ACTION_SEND);
Uri uri = FileProvider.getUriForFile(activity, authority , file);
sendIntent.putExtra(Intent.EXTRA_STREAM, uri);
sendIntent.setType(type);
activity.startActivityForResult(Intent.createChooser(sendIntent, file.getName()), 80002);
}
public static Uri getFileUri(Context context, String authority, File file, boolean isApp) {
Uri uri;
// 低版本直接用 Uri.fromFile
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
uri = Uri.fromFile(file);
} else {
if(isApp){
// 分享当前应用下的共享路径中的
uri = FileProvider.getUriForFile(context, authority , file);
}else {
// 分享外部的文件
uri = getImageContentUri(context, file);
}
}
return uri;
}
public static Uri getImageContentUri(Context context, File imageFile) {
String filePath = imageFile.getAbsolutePath();
Cursor cursor = context.getContentResolver().query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
new String[]{MediaStore.Images.Media._ID}, MediaStore.Images.Media.DATA + "=? ",
new String[]{filePath}, null);
if (cursor != null && cursor.moveToFirst()) {
@SuppressLint("Range") int id = cursor.getInt(cursor.getColumnIndex(MediaStore.MediaColumns._ID));
Uri baseUri = Uri.parse("content://media/external/images/media");
return Uri.withAppendedPath(baseUri, "" + id);
} else {
if (imageFile.exists()) {
ContentValues values = new ContentValues();
values.put(MediaStore.Images.Media.DATA, filePath);
return context.getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
} else {
return null;
}
}
}
}
虚拟机验证结果
参考
Android之FileProvider详解 - 掘金 (juejin.cn)
Android原生分享与指定app分享_android 原生分享链接-CSDN博客