android-5.0-lollipopandroid-sdcardrandomaccessfilefilechanneldocumentfile

How to get random access to a file on SD-Card by means of API presented for Lollipop?


My application uses Java class RandomAccessFile to read/write bytes to a file on SD card randomly by means of realization of SeekableByteChannel interface. Now I need rewrite it for Android 5.0 with new Lollipop API.

I have found the only way to read:

InputStream inputStream = getContentResolver().openInputStream(uri);

and write:

ParcelFileDescriptor pfd = getActivity().getContentResolver().openFileDescriptor(uri, "w");
FileOutputStream fileOutputStream = new FileOutputStream(pfd.getFileDescriptor());

from/to a file in new API.

I would like to have an ability to set channel in some random position and read/write bytes to that position. Is it possible to do that in new SDK 21? Does new SDK imply this way obtaining of channels:

FieInputChannel fieInputChannel = fileInputStream.getChannel();
FieOutputChannel fieOutputChannel = fileOutputStream.getChannel();

or some other approach?


Solution

  • It seems the only way to get a random read/write access to a file on SD card for SDK 21 (Lollipop) is as follows:

    import android.content.Context;
    import android.net.Uri;
    import android.os.ParcelFileDescriptor;
    import com.jetico.bestcrypt.FileManagerApplication;
    
    import java.io.FileInputStream;
    import java.io.FileNotFoundException;
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.nio.ByteBuffer;
    import java.nio.channels.FileChannel;
    
    public class SecondaryCardChannel {//By analogy with the java.nio.channels.SeekableByteChannel
        private FileChannel fileChannel;
        private ParcelFileDescriptor pfd;
        private boolean isInputChannel;
        private long position;
    
        public SecondaryCardChannel(Uri treeUri, Context context) {
            try {
                pfd = context.getContentResolver().openFileDescriptor(treeUri, "rw");
                FileInputStream fis = new FileInputStream(pfd.getFileDescriptor());
                fileChannel = fis.getChannel();
                isInputChannel = true;
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            }
        }
    
        public int read(ByteBuffer buffer) {
            if (!isInputChannel) {
                try {
                    fileChannel.close();
                    FileInputStream fis = new FileInputStream(pfd.getFileDescriptor());
                    fileChannel = fis.getChannel();
                    isInputChannel = true;
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            try {
                fileChannel.position(position);
                int bytesRead = fileChannel.read(buffer);
                position = fileChannel.position();
                return bytesRead;
            } catch (IOException e) {
                e.printStackTrace();
                return -1;
            }
        }
    
        public int write(ByteBuffer buffer) {
            if (isInputChannel) {
                try {
                    fileChannel.close();
                    FileOutputStream fos = new FileOutputStream(pfd.getFileDescriptor());
                    fileChannel = fos.getChannel();
                    isInputChannel = false;
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            try {
                fileChannel.position(position);
                int bytesWrite = fileChannel.write(buffer);
                position = fileChannel.position();
                return bytesWrite;
            } catch (IOException e) {
                e.printStackTrace();
                return -1;
            }
        }
    
        public long position() throws IOException {
            return position;
        }
    
        public SecondaryCardChannel position(long newPosition) throws IOException {
            position = newPosition;
            return this;
        }
    
        public long size() throws IOException {
            return fileChannel.size();
        }
    
        public SecondaryCardChannel truncate(long size) throws IOException {
            fileChannel.truncate(size);
            return this;
        }
    }
    

    EDIT 15/03/2017: Little bit optimized version looks like

    import android.content.Context;
    import android.net.Uri;
    import android.os.ParcelFileDescriptor;
    
    import java.io.FileInputStream;
    import java.io.FileNotFoundException;
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.nio.ByteBuffer;
    import java.nio.channels.FileChannel;
    
    public class SecondaryCardChannel {
        private ParcelFileDescriptor pfdInput, pfdOutput;
        private FileInputStream fis;
        private FileOutputStream fos;
        private long position;
    
        public SecondaryCardChannel(Uri treeUri, Context context) {
            try {
                pfdInput = context.getContentResolver().openFileDescriptor(treeUri, "r");
                pfdOutput = context.getContentResolver().openFileDescriptor(treeUri, "rw");
                fis = new FileInputStream(pfdInput.getFileDescriptor());
                fos = new FileOutputStream(pfdOutput.getFileDescriptor());
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            }
        }
    
        public int read(ByteBuffer buffer) {
            try {
                FileChannel fch = fis.getChannel();
                fch.position(position);
                int bytesRead = fch.read(buffer);
                position = fch.position();
                return bytesRead;
            } catch (IOException e) {
                e.printStackTrace();
                return -1;
            }
        }
    
        public int write(ByteBuffer buffer) {
            try {
                FileChannel fch = fos.getChannel();
                fch.position(position);
                int bytesWrite = fch.write(buffer);
                position = fch.position();
                return bytesWrite;
            } catch (IOException e) {
                e.printStackTrace();
                return -1;
            }
        }
    
        public long position() throws IOException {
            return position;
        }
    
        public SecondaryCardChannel position(long newPosition) throws IOException {
            position = newPosition;
            return this;
        }
    
        public long size() throws IOException {
            return fis.getChannel().size();
        }
    
        public void force(boolean metadata) throws IOException {
            fos.getChannel().force(metadata);
            pfdOutput.getFileDescriptor().sync();
        }
    
        public long truncate(long size) throws Exception {
            FileChannel fch = fos.getChannel();
            try {
                fch.truncate(size);
                return fch.size();
            } catch (Exception e){ // Attention! Truncate is broken on removable SD card of Android 5.0
                e.printStackTrace();
                return -1;
            }
        }
    
        public void close() throws IOException {
            FileChannel fch = fos.getChannel();
            fch.close();
    
            fos.close();
            fis.close();
            pfdInput.close();
            pfdOutput.close();
        }
    }