开发者

Android实现多进程并发控制的两种方案

目录
  • 一、问题背景
  • 二、实现方案
    • 1、方案1:仅一个进程负责写
      • FileProvider
      • 注册
      • 写入逻辑
    • 2、方案2:通过文件锁的方式
      • 关于 Channel
      • FileChannel
      • FileLock
  • 三、总结

    一、问题背景

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

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

    二、实现方案

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

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

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

    以下是使用ContentProvider的方式:

    FileProvider

    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());
           android } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    

    注册

    <provider
        android:name=".FileProvider"
        android:authorities="com.example.fileprovider"
        android:exported="true" />
    

    写入逻辑

        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那样,读数据和写数据需要不同的数据通道。

    public interface Channel extends Closeable {
    
        /**
         * Tells whether or not this channel is open.
         *
         * @return <tt>true</tt> if, and only if, this candroidhannel 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) 中的一个类,用于对文件进行高效的读写操作。它提供了比传统 FileInputStream 和 FileOutputphpStream 更灵活和高效的文件操作方式。

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

    FileChannel的获取方式:

    通过FileInputStream/FileOutputStream

    // 通过 FileInputStream/FileOutputStream (只读或只写)
    FileInputStream fis = new FileInputStream("file.txt");
    FileChannel readChannel = fis.getChannel();
    
    FileOutputStream fos = new FileOutputStream("file.txt");
    FileChannel writeChannel = fos.getChannel();
    

    通过RandomAccessFile

    // 通过 RandoMACcessFile
    RandomAccessFile raf = new RandomAccessFile("file.txt", "rw");
    FileChannel channel = raf.getChannel();
    

    通过FileChannel.open()

    FileChannel channel = FileChannel.open(Paths.get("file.txt"), StandardOpenOption.READ);
    

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

    FileLock

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

    锁的类型

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

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

    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。

    到此这篇关于Android实现多进程并发控制的两种方案的文章就介绍到这了,更多相关Android多进程并发控制内容请搜索编程客栈(www.devze.com)以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程客栈(www.devze.com)!

    0

    上一篇:

    下一篇:

    精彩评论

    暂无评论...
    验证码 换一张
    取 消

    最新开发

    开发排行榜