文章目录
- 概要
- 整体架构流程
- 技术名词解释
- 技术细节
- 小结
概要
在flutter写界面很快,但是他有些功能并没有,如果要调用拍照,路由等,就要调用android 原生的功能,
也可以使用插件,但是本章节是教如何于原生通信调用拍照功能,并将拍照结果返回到flutter中进行显示
整体架构流程
flutter于原生的通信,首先就是通信建立通道,传输消息句柄过去给anroid原生进行接收
Future<void> _playMusic() async{
await Plat.platform.invokeMethod("playMusic");
}
//由于项目中很多地方需要调用android原生,索性写到了一个类中
class Plat{
static const platform = MethodChannel("com.example.android_message");
}
这里是android原生代码,通道名要一致不然找不到通道就通信不了,就比如你需要打电话,打电话需要什么呢?需要手机和手机卡这就是通道,而有了这个还要有手机号,这样就能通信,下面是原生代码
主Activity
//定义Flutter 与Android通信的通道名称
private static final String CHANNEL = "com.example.android_message";
//配置Flutter引擎
@Override
public void configureFlutterEngine(FlutterEngine flutterEngine) {
GeneratedPluginRegistrant.registerWith(flutterEngine);
new MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), CHANNEL)
.setMethodCallHandler(
(call, result) -> {
//接收判断消息句柄
if (call.method.equals("takePhoto")) {
this.result = result;
takePhone();
}
);
}
private void takePhone(){
Intent intent = new Intent(this,TakePhotoActivity.class);
//用于将子活动完成后获取返回的结果
//REQUEST_CODE_SUB_ACTIVITY 标识Activity返回结果的请求码,
// 当你使用Activity 并希望获取其返回结果时,你可以为启动子 Activity 的请求分配一个唯一的请求码。
startActivityForResult(intent,REQUEST_CODE_SUB_ACTIVITY);
}
拍照功能 由于项目可能需要调用不同的功能,所以都写在主Activity中不太友好,就分开了,
这边创建一个子Activity,然后使用该Intent启动该TakePhotoActivity的子活动,之后调用拍照功能将拍照的结果返回到flutter中,
package com.example.android_message;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.core.content.FileProvider;
import android.Manifest;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.provider.MediaStore;
import android.util.Base64;
import android.widget.Toast;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.UUID;
import io.flutter.embedding.android.FlutterActivity;
public class TakePhotoActivity extends FlutterActivity {
protected static final int REQUEST_IMAGE_CAPTURE = 1;
private String currentPhotoPath;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_take_photo);
onJurisdiction();
}
//拍照功能
// 实现拍照的方法
private void dispatchTakePictureIntent() {
Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
File photoFile = null;
try {
photoFile = createImageFile();
} catch (IOException ex) {
ex.printStackTrace();
backResult("");
return;
}
if (photoFile != null) {
//创建用于存储拍照结果的URI,并添加到拍照意图中
Uri photoURI = FileProvider.getUriForFile(this,
"com.example.android_message.fileprovider",
photoFile);
takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI);
startActivityForResult(takePictureIntent, REQUEST_IMAGE_CAPTURE);
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_IMAGE_CAPTURE && resultCode == RESULT_OK) {
if (currentPhotoPath != null) {
// 将文件移动到指定位置作为永久文件
File tempFile = new File(currentPhotoPath);
//将图片保存到永久文件夹当中返回
String permanentFilePath = savePermanentFile(tempFile);
// 将拍照后的图像返回给 Flutter
File permanentFile = new File(permanentFilePath);
Bitmap bitmap = BitmapFactory.decodeFile(permanentFile.getAbsolutePath());
ByteArrayOutputStream stream = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.PNG, 100, stream);
byte[] byteArray = stream.toByteArray();
String imageString = Base64.encodeToString(byteArray, Base64.DEFAULT);
Intent intent = new Intent();
intent.putExtra("path","");
setResult(RESULT_OK, intent);
backResult(currentPhotoPath);
}
}
}
//当拍照将要返回的时候调用这个方法,再将结果返回的路径给主Activity
private void backResult(String path){
Intent intent = new Intent();
intent.putExtra("path",path);
setResult(RESULT_OK, intent);
finish();
}
private String savePermanentFile(File tempFile) {
// 获取永久文件存储目录
File storageDir = getExternalFilesDir(Environment.DIRECTORY_PICTURES);
// 创建目标文件夹(如果不存在)
if (!storageDir.exists()) {
storageDir.mkdirs();
}
// 生成永久文件名
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(new Date());
String permanentFileName = "IMG_" + timeStamp + ".jpg";
File permanentFile = new File(storageDir, permanentFileName);
String permanentFilePath = permanentFile.getAbsolutePath();
currentPhotoPath = permanentFilePath;
// 将临时文件移动到永久文件
try {
//读写图像 将临时图片写入到永久文件夹中
InputStream in = new FileInputStream(tempFile);
OutputStream out = new FileOutputStream(permanentFile);
byte[] buffer = new byte[1024];
int length;
while ((length = in.read(buffer)) > 0) {
out.write(buffer, 0, length);
}
in.close();
out.close();
} catch (IOException e) {
e.printStackTrace();
return null;
}
// 返回永久文件的路径字符串
return permanentFilePath;
}
//创建保存拍照结果的临时文件
private File createImageFile() throws IOException {
File storageDir = getExternalFilesDir(Environment.DIRECTORY_PICTURES);
String imageFileName = "IMG_" + UUID.randomUUID().toString() + ".jpg";
File imageFile = File.createTempFile(imageFileName, ".jpg", storageDir);
imageFile.setReadable(true, false);
currentPhotoPath = imageFile.getAbsolutePath();
return imageFile;
}
// 添加动态权限
private void onJurisdiction() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
int checkCallPhonePermission = ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA);
// 用户取消打开权限直接返回
if (checkCallPhonePermission != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA}, 0);
return;
} else {
dispatchTakePictureIntent();
}
} else {
dispatchTakePictureIntent();
}
}
// 动态申请权限返回结果
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
switch (requestCode) {
case 0:
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
dispatchTakePictureIntent();
} else {
Toast.makeText(this, "用户没有同意开启这个权限", Toast.LENGTH_SHORT).show();
}
break;
default:
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
}
}
技术细节
需要提到的是,如果你要使用到文件的读取,拍照功能,是需要加权限,和xml文件
在android原生中的xml文件夹下创建一个file_paths,用于定义应用程序对特定文件路径的访问权限
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-files-path name="my_images" path="Pictures" />
</paths>
在AndroidManifest.xml 中的manifest.xml里面添加
<!-- 打开互联网权限 -->
<uses-permission android:name="android.permission.INTERNET" />
<!-- 这行代码声明了应用程序需要读取外部存储器的权限。这个权限允许应用程序读取设备上的照片、视频、音频等文件。-->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<!-- 这行代码声明了应用程序需要写入外部存储器的权限。这个权限允许应用程序在设备上创建、修改和删除文件。-->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<!-- 这行代码表明了 应用程序需要使用相机的权限,这个权限允许应用程序访问设备的相机进行拍照或录像操作-->
<uses-permission android:name="android.permission.CAMERA" />
小结
代码上有很多注释,所以这里就不多赘述了,关于flutter于android之间的通信,就说到这里
总之: 两个设备之间的通信,首先需要一个通道,在需要将消息句柄对上,然后android原生再调用相应功能,但是做这些的前提是,需要加权限,因为手机上访问拍照的时候是需要权限的