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。

相关推荐
轻刀快马20 分钟前
跨越软硬件的共鸣(二):从 Cache 写策略看 Redis 与 DB 的一致性博弈
java·开发语言·redis·计算机组成原理
折哥的程序人生 · 物流技术专研21 分钟前
Java 23 种设计模式:从踩坑到精通 | 装饰器模式 —— 比继承更灵活的扩展方式,你用过吗?
java·装饰器模式·java面试·结构型模式·java设计模式·javaio·从踩坑到精通
ltlovezh23 分钟前
ROI 编码学习指南:Android 与 FFmpeg 的真实实现边界
android·ffmpeg·音视频开发
lili001228 分钟前
2026 企业 AI 选型新范式:OpenRouter Fusion 证明多模型融合性价比远超单模型,企业该如何重构技术栈? - 微元算力(weytoken)
java·人工智能·python·重构·ai编程
shushangyun_30 分钟前
汽车服务行业B2B平台+AI解决方案哪家专业:2026年最新测评
java·运维·网络·数据库·人工智能·汽车
A.说学逗唱的Coke33 分钟前
【大模型专题】Spring AI Alibaba × Skill 整合实战:让 AI 真正“会干活
java·人工智能·spring
大黄说说1 小时前
深入理解 Go 协程 Goroutine:并发编程的核心精髓
java·数据库·python
许彰午1 小时前
38_Java设计模式之装饰器模式
java·设计模式·装饰器模式
折哥的程序人生 · 物流技术专研1 小时前
Java 23 种设计模式:从踩坑到精通 | 组合模式 —— 树形结构处理,部分与整体一视同仁
java·组合模式·java面试·springsecurity·结构型模式·java设计模式·从踩坑到精通
郝学胜-神的一滴1 小时前
完全二叉树与堆底层原理深度剖析 | 手写C++大顶堆实现
java·开发语言·数据结构·c++·python·算法