Android 多进程并发控制如何实现

一、问题背景

当一个App中存在多个进程时例如存在 主进程,辅进程两个进程,两个进程都会去向A文件中写入数据。但是我们业务中希望每次仅允许有一个进程向A文件写入内容。即当主进程写入时,辅进程要等待主进程写完之后才可以写入,防止出现并发修改导致数据异常的问题。

在实际的场景上,例如在我们的项目中未使用MMKV之前,KV存储是自行实现的多进程并发的SP。

二、实现方案

1、方案1:仅一个进程负责写

将所有的写入操作调整到同一个进程中,这样就相当于规避了多进程并发问题。

我们可以通过提供一个ContantProvider或者Service来是实现这个功能。

以下是使用ContentProvider的方式:

FileProvider

java 复制代码
public class FileProvider extends ContentProvider {
    private static final String AUTHORITY = "com.example.fileprovider";
    private static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/file");

    // 文件锁,确保单进程写入
    private static final Object fileLock = new Object();

    @Override
    public boolean onCreate() {
        return true;
    }

    @Override
    public Cursor query(Uri uri, String[] projection, 
        String selection, String[] selectionArgs, String sortOrder) {
        return null; // 不提供查询功能
    }

    @Override
    public String getType(Uri uri) {
        return null;
    }

    @Override
    public Uri insert(Uri uri, ContentValues values) {
        return null; // 不提供插入功能
    }

    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        return 0; // 不提供删除功能
    }

    @Override
    public int update(Uri uri, ContentValues values, 
        String selection, String[] selectionArgs) {
        return 0; // 不提供更新功能
    }

    // 自定义方法:写入文件
    @Override
    public Bundle call(String method, String arg, Bundle extras) {
        if ("writeToFile".equals(method)) {
            String content = extras.getString("content");
            synchronized (fileLock) { 
                writeToFile(content);
            }
            Bundle result = new Bundle();
            result.putBoolean("success", true);
            return result;
        }
        return super.call(method, arg, extras);
    }

    // 实际写入文件的逻辑
    private void writeToFile(String content) {
        File file = new File(getContext().getFilesDir(), "A.txt");
        try (FileOutputStream fos = new FileOutputStream(file, true)) {
            fos.write(content.getBytes());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

注册

ini 复制代码
<provider
    android:name=".FileProvider"
    android:authorities="com.example.fileprovider"
    android:exported="true" />

写入逻辑

java 复制代码
    private void writeToFileViaProvider(String content) {
        Uri uri = Uri.parse("content://com.example.fileprovider/file");
        ContentResolver resolver = getContentResolver();
        
        Bundle extras = new Bundle();
        extras.putString("content", content);
        
        try {
            Bundle result = resolver.call(uri, "writeToFile", null, extras);
            if (result != null && result.getBoolean("success")) {
                Log.d("FileProvider", "Write successful");
            }
        } catch (Exception e) {
            Log.e("FileProvider", "Failed to write file", e);
        }
    }

使用Service + Binder的方式,代码比较简单,这里就不写了。

2、方案2:通过文件锁的方式

文件锁主要是利用FileChannel、FileLock来控制多进程并发。

关于 Channel

Channel 经常翻译为通道,类似 IO 中的流,用于读取和写入。不用像BIO那样,读数据和写数据需要不同的数据通道。

java 复制代码
public interface Channel extends Closeable {

    /**
     * Tells whether or not this channel is open.
     *
     * @return <tt>true</tt> if, and only if, this channel is open
     */
    public boolean isOpen();

    /**
     * Closes this channel.
     */
    public void close() throws IOException;

}

我们常用的Channel有:

  • FileChannel:文件通道,用于文件的读和写。
  • DatagramChannel:用于UDP连接的接收和发送。
  • SocketChannel:把它理解为TCP连接通道,简单理解就是TCP客户端。
  • ServerSocketChannel:TCP对应的服务端,用于监听某个端口进来的请求。

FileChannel

FileChannel 是 Java NIO (New I/O) 中的一个类,用于对文件进行高效的读写操作。它提供了比传统 FileInputStreamFileOutputStream 更灵活和高效的文件操作方式。

所有的NIO操作始于通道,通道是数据来源或数据写入的目的地。其与 Buffer 打交道,读操作的时候将 Channel 中的数据填充到 Buffer 中,而写操作时将 Buffer 中的数据写入到 Channel 中。

FileChannel的获取方式:

通过FileInputStream/FileOutputStream

java 复制代码
// 通过 FileInputStream/FileOutputStream (只读或只写)
FileInputStream fis = new FileInputStream("file.txt");
FileChannel readChannel = fis.getChannel();

FileOutputStream fos = new FileOutputStream("file.txt");
FileChannel writeChannel = fos.getChannel();

通过RandomAccessFile

java 复制代码
// 通过 RandomAccessFile
RandomAccessFile raf = new RandomAccessFile("file.txt", "rw");
FileChannel channel = raf.getChannel();

通过FileChannel.open()

java 复制代码
FileChannel channel = FileChannel.open(Paths.get("file.txt"), StandardOpenOption.READ);

在我们示例代码中选择了使用 FileOutputStream 来获取FileChannel。

FileLock

FileLock 表示文件或文件区域的锁,用于控制多个进程或线程对同一文件的并发访问。

锁的类型

  • 共享锁 (Shared Lock) :多个进程可同时持有,用于读操作
  • 排他锁 (Exclusive Lock) :一次只能由一个进程持有,用于写操作

通过文件锁的方式控制多进程并发的 示例代码:

java 复制代码
public class FileWriter {

    private static final String FILE_PATH = "/path/to/your/file.txt";

    public void writeToFile(String content) {
        File file = new File(FILE_PATH);
        try {
            FileOutputStream fos = new FileOutputStream(file, true);
            FileChannel channel = fos.getChannel()) 
            // 获取独占锁
            FileLock lock = channel.lock();
            try {
                // 写入文件
                fos.write(content.getBytes());
            } finally {
                // 释放锁
                lock.release();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

三、总结

以上简单介绍了一下两种控制多进程并发的方案。

其中使用ContentProvider或者Service的方式将所有的操作控制在同一个进程中的方案逻辑清晰,但是代码量比较多。尤其是使用Service的方式,虽然上面我们每个给出示例代码,但是可以想象没新增一个进程都需要写相关的代码,写起来就比较啰嗦了。

而ContentProvicer的方式,系统中也有很多相关的实现方案,例如更新媒体文件,更新联系人数据等。

使用文件锁的方式对于仅熟悉Android不熟悉Java的同学不容易想到,所以本篇也同时简单介绍了一下FileChannel以及FileLock。

相关推荐
WebInfra8 分钟前
🔥 Midscene 重磅更新:支持 AI 驱动的 Android 自动化
android·前端·测试
陈璆鸣24 分钟前
【java+Mysql】学生信息管理系统
java·mysql·用户登录·学生信息·成绩信息
奔跑吧 android29 分钟前
【android bluetooth 框架分析 02】【Module详解 12】【 BidiQueue、BidiQueueEnd、Queue介绍】
android·queue·bluetooth·bt·aosp13·bidiqueue·bidiqueueend
东阳马生架构39 分钟前
Sentinel源码—7.参数限流和注解的实现
java
johnrui39 分钟前
JAVA设计模式:注解+模板+接口
java·windows·设计模式
常年游走在bug的边缘1 小时前
Spring Boot 集成 tess4j 实现图片识别文本
java·spring boot·后端·图片识别
魔道不误砍柴功1 小时前
Java 2025:解锁未来5大技术趋势,Kotlin融合&AI新篇
java·人工智能·kotlin
小可爱的大笨蛋2 小时前
十倍开发效率 - IDEA 插件之RestfulBox - API
java·ide·intellij-idea
虽千万人 吾往矣2 小时前
golang context源码
android·开发语言·golang
爱的叹息2 小时前
【java实现+4种变体完整例子】排序算法中【桶排序】的详细解析,包含基础实现、常见变体的完整代码示例,以及各变体的对比表格
java·开发语言·排序算法