不同进程之间也可以通过读写相同的文件来实现跨进程通信,例如进程A将数据写入文件,进程B通过读取这个文件来获取数据。由于Android系统是继续Linux开发,不同于Windows,Linux无法通过对文件添加排斥锁的方式来解决并发读写的问题,因此在使用共享文件进行跨进程通信时,需要注意潜在的并发读写问题。尽管如此,通过共享文件来进行进程间通信依然是个不错的方式,因为Java已经为我们提供了完备的类库来实现将基本数据类型和对象序列化到文件系统中,并从文件系统中恢复它们。

同之前一样,我们还是通过写一个小Demo来演示这个功能,本Demo实现了从一个应用中将对象(作者自定的User类型)写入(序列化)到文件,并在另一个应用中恢复它。

WriteToFileActivity代码如下:

package com.itachi.android.writetofile;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;

import com.itachi.android.model.User;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;

public class WriteToFileActivity extends AppCompatActivity {
    private static final String TAG = "WriteToFileActivity";

    private Button mWriteButton;
    private EditText mUsername;
    private EditText mUserAge;
    private EditText mUserSex;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_write_to_file);
        mWriteButton = (Button) findViewById(R.id.write_to_file);
        mUsername = (EditText) findViewById(R.id.user_name);
        mUserAge = (EditText) findViewById(R.id.user_age);
        mUserSex = (EditText) findViewById(R.id.user_sex);
        mWriteButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                User user = constructUser();
                writeUserToFile(user);
            }
        });
    }

    private User constructUser() {
        String username = mUsername.getText().toString();
        int userAge = Integer.valueOf(mUserAge.getText().toString());
        String sex = mUserSex.getText().toString();
        return new User(username, userAge, sex);
    }

    private void writeUserToFile(User user) {
        File dir = new File(FileUtils.getDirPath());
        if (!dir.exists()) {
            dir.mkdirs();
        }
        File userFile = new File(FileUtils.getFilePath());
        ObjectOutputStream out = null;
        try {
            out = new ObjectOutputStream(new FileOutputStream(userFile));
            out.writeObject(user);
            Toast.makeText(WriteToFileActivity.this, "User data written successfully", Toast.LENGTH_SHORT).show();
            Log.d(TAG, "write user:" + user + " to file:" + userFile);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (out != null) {
                try {
                    out.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

Manifest中要声明读写SD卡(外部存储)权限,如下:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.itachi.android.writetofile">
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.WriteToFile">
        <activity android:name=".WriteToFileActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

 ReadFromFileAcivity代码如下:

package com.itachi.android.readfromfile;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;

import com.itachi.android.model.User;
import com.itachi.android.readfromfile.util.FileUtils;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;

public class ReadFromFileActivity extends AppCompatActivity {
    private static final String TAG = "ReadFromFileActivity";

    private Button mReadButton;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_read_from_file);
        mReadButton = (Button) findViewById(R.id.read_from_file);
        mReadButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                File userFile = getUserFile();
                User user = readUserFromFile(userFile);
                Log.d(TAG, "Read user:" + user + " from file:" + userFile);
                Toast.makeText(ReadFromFileActivity.this, "Read " + user, Toast.LENGTH_SHORT).show();
            }
        });
    }

    private File getUserFile() {
        File dir = new File(FileUtils.getDirPath());
        if (!dir.exists()) {
            dir.mkdirs();
        }
        File userFile = new File(FileUtils.getFilePath());
        return userFile;
    }

    private User readUserFromFile(File file) {
        User user = null;
        ObjectInputStream in = null;
        try {
            in = new ObjectInputStream(new FileInputStream(file));
            user = (User) in.readObject();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (in != null) {
                try {
                    in.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return user;
    }
}

同样的,在Manifest文件中我们也要申明读写外部存储的权限,如下:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.itachi.android.readfromfile">
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:requestLegacyExternalStorage="true"
        android:theme="@style/Theme.ReadFromFile">
        <activity android:name=".ReadFromFileActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

在上面的Demo中,我们从WriteToFile应用中,向外部存储中写入(序列化)了一个User类对象,并从ReadFromFile应用中读取(反序列化)了这个对象。在写这个共享文件Demo的时候,发现了如下几个需要注意的点:

1.我们对文件进行读写的类对象,需要是同一个对象,也就是全限定名要相同,不可以在A应用中某个包下申明一个User类,然后再在B应用1中的某个包下申明一个User类,尽管这两个User类可能代码完全一样,但事实上,它们还是截然不同的两个类。文中的Demo是通过将User类打包成jar包的形式导入到两个项目中来进行引用。

2.在Android 6后,读写外部存储的权限改为了动态权限,但是我在写这个Demo的时候,偷了点懒,嘿嘿,直接申明在manifest文件中了,然后安装到手机后,在应用设置里面,手动允许了这个权限,毕竟本文主要还是集中在共享文件上,权限问题,那就有空再写一篇讲讲吧。

3.在Android 11以及以后得版本中,谷歌对于读写外部文件的权限又做了其他的改动,导致这个Demo在Android 11及以上版本的手机上运行时,就算申明了读写权限,但是读取文件依然会报Permission denied。Demo中时通过将项目的Target SDK Version改为29(小于30即可)来解决这个问题。关于这个方面的改动,我还没来得及了解,后面有空补上。

4.在Android 10上,谷歌引入了沙盒机制来对外部存储进行保护,因此在ReadFromFile应用中,还需要在manifest文件的application节点下添加android:requestLegacyExternalStorage="true"来确保能够读取到外部存储。同前面两个注意事项一样,本文主要用于介绍文件共享,权限的事情,就留给权限的文章吧!

Demo的演示效果如下:

本文演示的Demo主要是借助了Java中提供的对象输入,输出流来实现,除此之外,Android也提供了一种基于文件系统的轻量级存储——SharedPreferences,想了解它,可以参考作者的另一篇博文:SharedPreferences牛刀小试

Logo

更多推荐