1. Problem background
When there are multiple processes in an App, for example, the main process exists, and the secondary process has two processes, and both processes will write data to file A. However, in our business, we hope to allow only one process to write content to file A at a time. That is, when the main process writes, the auxiliary process has to wait for the main process to finish writing before writing, to prevent the problem of concurrent modifications causing data exceptions.
In practical scenarios, for example, before MMKV is used in our project, KV storage is a self-implemented multi-process concurrent SP.
2. Implementation plan
1. Solution 1: Only one process is responsible for writing
Adjusting all write operations to the same process is equivalent to avoiding the problem of multi-process concurrency.
We can implement this function by providing a ContantProvider or Service.
Here is how to use ContentProvider:
FileProvider
public class FileProvider extends ContentProvider { private static final String AUTHORITY = ""; private static final Uri CONTENT_URI = ("content://" + AUTHORITY + "/file"); // File lock, ensure single process write 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; // No query function is provided } @Override public String getType(Uri uri) { return null; } @Override public Uri insert(Uri uri, ContentValues values) { return null; // No insertion function is provided } @Override public int delete(Uri uri, String selection, String[] selectionArgs) { return 0; // No deletion function is provided } @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { return 0; // No update function is provided } // Custom method: write to file @Override public Bundle call(String method, String arg, Bundle extras) { if ("writeToFile".equals(method)) { String content = ("content"); synchronized (fileLock) { writeToFile(content); } Bundle result = new Bundle(); ("success", true); return result; } return (method, arg, extras); } // Logic of actual writing to the file private void writeToFile(String content) { File file = new File(getContext().getFilesDir(), ""); try (FileOutputStream fos = new FileOutputStream(file, true)) { (()); } catch (IOException e) { (); } } }
register
<provider android:name=".FileProvider" android:authorities="" android:exported="true" />
Write logic
private void writeToFileViaProvider(String content) { Uri uri = ("content:///file"); ContentResolver resolver = getContentResolver(); Bundle extras = new Bundle(); ("content", content); try { Bundle result = (uri, "writeToFile", null, extras); if (result != null && ("success")) { ("FileProvider", "Write successful"); } } catch (Exception e) { ("FileProvider", "Failed to write file", e); } }
The code is relatively simple using Service + Binder, so I won't write it here.
2. Solution 2: Through file locking
File locks mainly use FileChannel and FileLock to control multi-process concurrency.
About Channel
Channel is often translated as a channel, similar to streams in IO, for reading and writing. Instead of reading and writing data, you need different data channels like BIO.
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; }
The channels we use are:
- FileChannel: File channel, used for reading and writing files.
- DatagramChannel: for receiving and sending UDP connections.
- SocketChannel: Understand it as a TCP connection channel, and simply understand it is a TCP client.
- ServerSocketChannel: The server corresponding to TCP is used to listen for requests coming from a certain port.
FileChannel
FileChannel is a class in Java NIO (New I/O) that is used to efficiently read and write files. It offers more traditionalFileInputStream
andFileOutputStream
More flexible and efficient file operation methods.
All NIO operations begin with a channel, which is the destination for data source or data writing. It deals with a Buffer. When reading, the data in the Channel is filled into the Buffer, and when writing, the data in the Buffer is written into the Channel.
How to obtain FileChannel:
By FileInputStream/FileOutputStream
// via FileInputStream/FileOutputStream (read-only or write-only)FileInputStream fis = new FileInputStream(""); FileChannel readChannel = (); FileOutputStream fos = new FileOutputStream(""); FileChannel writeChannel = ();
via RandomAccessFile
// via RandomAccessFileRandomAccessFile raf = new RandomAccessFile("", "rw"); FileChannel channel = ();
pass()
FileChannel channel = ((""), );
Selected in our sample codeFileOutputStream
To get FileChannel.
FileLock
FileLock
A lock representing a file or file area, used to control concurrent access to the same file by multiple processes or threads.
Type of lock
- Shared Lock: Multiple processes can be held at the same time for read operations
- Exclusive Lock: Only held by one process at a time, used for writing operations
Sample code to control multi-process concurrency through file locks:
public class FileWriter { private static final String FILE_PATH = "/path/to/your/"; public void writeToFile(String content) { File file = new File(FILE_PATH); try { FileOutputStream fos = new FileOutputStream(file, true); FileChannel channel = ()) // Get exclusive lock FileLock lock = (); try { // Write to the file (()); } finally { // Release the lock (); } } catch (IOException e) { (); } } }
3. Summary
The above briefly introduces two solutions to control multi-process concurrency.
The logic of the scheme that uses ContentProvider or Service to control all operations in the same process is clear, but the amount of code is relatively large. Especially when using Service, although each of us gives example code above, it can be imagined that no new process needs to write related code, which is more verbose to write.
The ContentProvicer method also has many related implementation solutions in the system, such as updating media files, updating contact data, etc.
The way to use file locks is not easy for students who are only familiar with Android and not Java, so this article also briefly introduces FileChannel and FileLock.
This is the end of this article about two solutions for Android to implement multi-process concurrent control. For more related content on Android multi-process concurrent control, please search for my previous articles or continue browsing the following related articles. I hope everyone will support me in the future!