深入理解Android数据写入机制:从Java到Kernel全解析
一、Android数据存储概述
1.1 数据存储方式分类
Android提供了多种数据存储方式,主要包括:
- 文件存储:通过Java IO或NIO操作文件
- SharedPreferences:轻量级键值对存储
- SQLite数据库:结构化数据存储
- ContentProvider:跨应用数据共享
- 网络存储:通过网络API存储到远程服务器
1.2 数据写入的核心流程
无论采用哪种存储方式,数据写入的核心流程大致相同:
- 数据准备:将应用层数据转换为可存储格式
- 权限检查:验证应用是否有写入权限
- 打开存储介质:文件、数据库等
- 数据写入:将数据写入存储介质
- 同步确认:确保数据持久化
- 关闭资源:释放打开的资源
1.3 性能考量
数据写入性能受多种因素影响:
- 存储介质类型:闪存、SD卡等
- 写入操作类型:随机写、顺序写
- 同步策略:是否强制同步到磁盘
- 缓冲区大小:合理的缓冲区可减少IO次数
二、Java IO数据写入
2.1 FileOutputStream源码分析
2.1.1 构造函数
// FileOutputStream.java
/**
* 创建一个向指定File对象表示的文件中写入数据的文件输出流
* @param file 要打开的文件
* @param append 如果为true,则将字节写入文件末尾而不是开头
* @throws FileNotFoundException 如果文件存在但为目录,不存在但无法创建,或无法打开
*/
public FileOutputStream(File file, boolean append) throws FileNotFoundException {
String name = (file != null ? file.getPath() : null);
SecurityManager security = System.getSecurityManager();
if (security != null) {
// 安全检查,确保应用有写入权限
security.checkWrite(name);
}
if (name == null) {
throw new NullPointerException();
}
if (file.isInvalid()) {
throw new FileNotFoundException("Invalid file path");
}
// 调用本地方法打开文件
this.fd = new FileDescriptor();
fd.attach(this);
this.append = append;
open(name, append);
}
/**
* 本地方法:打开文件用于写入
*/
private native void open(String name, boolean append) throws FileNotFoundException;
2.1.2 写入方法
// FileOutputStream.java
/**
* 将指定字节写入此文件输出流
* @param b 要写入的字节
* @throws IOException 如果发生I/O错误
*/
public void write(int b) throws IOException {
// 调用本地方法写入单个字节
write(b, append);
}
/**
* 本地方法:写入单个字节
*/
private native void write(int b, boolean append) throws IOException;
/**
* 将b.length个字节从指定byte数组写入此文件输出流
* @param b 数据
* @throws IOException 如果发生I/O错误
*/
public void write(byte b[]) throws IOException {
// 调用带偏移量的write方法
writeBytes(b, 0, b.length, append);
}
/**
* 本地方法:写入字节数组
*/
private native void writeBytes(byte b[], int off, int len, boolean append) throws IOException;
2.1.3 关闭方法
// FileOutputStream.java
/**
* 关闭此文件输出流并释放与此流关联的所有系统资源
* @throws IOException 如果发生I/O错误
*/
public void close() throws IOException {
synchronized (closeLock) {
if (closed) {
return;
}
closed = true;
}
// 调用flush方法确保数据写入
if (channel != null) {
channel.close();
}
// 关闭文件描述符
fd.closeAll(new Closeable() {
public void close() throws IOException {
// 调用本地方法关闭文件
close0();
}
});
}
/**
* 本地方法:关闭文件
*/
private native void close0() throws IOException;
2.2 BufferedOutputStream源码分析
2.2.1 构造函数
// BufferedOutputStream.java
/**
* 创建一个新的缓冲输出流,以将数据写入指定的底层输出流
* @param out 底层输出流
*/
public BufferedOutputStream(OutputStream out) {
this(out, 8192); // 默认缓冲区大小为8192字节
}
/**
* 创建一个新的缓冲输出流,以将数据写入指定的底层输出流,指定缓冲区大小
* @param out 底层输出流
* @param size 缓冲区大小
* @throws IllegalArgumentException 如果size <= 0
*/
public BufferedOutputStream(OutputStream out, int size) {
super(out);
if (size <= 0) {
throw new IllegalArgumentException("Buffer size <= 0");
}
buf = new byte[size]; // 初始化缓冲区
}
2.2.2 写入方法
// BufferedOutputStream.java
/**
* 将指定的字节写入此缓冲的输出流
* @param b 要写入的字节
* @throws IOException 如果发生I/O错误
*/
public synchronized void write(int b) throws IOException {
if (count >= buf.length) {
// 缓冲区已满,刷新缓冲区
flushBuffer();
}
// 将字节写入缓冲区
buf[count++] = (byte)b;
}
/**
* 将指定byte数组中从偏移量off开始的len个字节写入此缓冲的输出流
* @param b 数据
* @param off 数据中的起始偏移量
* @param len 要写入的字节数
* @throws IOException 如果发生I/O错误
*/
public synchronized void write(byte b[], int off, int len) throws IOException {
if (len >= buf.length) {
// 如果要写入的数据长度大于缓冲区大小,直接写入底层流
flushBuffer();
out.write(b, off, len);
return;
}
if (len > buf.length - count) {
// 如果剩余缓冲区空间不足,刷新缓冲区
flushBuffer();
}
// 将数据复制到缓冲区
System.arraycopy(b, off, buf, count, len);
count += len;
}
/**
* 将缓冲区中的数据刷新到底层输出流
*/
private void flushBuffer() throws IOException {
if (count > 0) {
// 调用底层输出流的write方法
out.write(buf, 0, count);
count = 0; // 重置缓冲区指针
}
}
2.3 RandomAccessFile源码分析
2.3.1 构造函数
// RandomAccessFile.java
/**
* 创建从中读取和向其中写入的随机访问文件流,该文件由文件系统中的File对象指定
* @param file 指定要打开的文件的File对象
* @param mode 打开文件的模式:"r"(只读)、"rw"(读写)、"rws"或"rwd"
* @throws FileNotFoundException 如果该文件不存在、是目录而不是常规文件,或者无法打开进行读取或写入
*/
public RandomAccessFile(File file, String mode) throws FileNotFoundException {
String name = (file != null ? file.getPath() : null);
SecurityManager security = System.getSecurityManager();
if (security != null) {
// 安全检查
security.checkRead(name);
if (mode.equals("rw")) {
security.checkWrite(name);
}
}
if (name == null) {
throw new NullPointerException();
}
if (file.isInvalid()) {
throw new FileNotFoundException("Invalid file path");
}
this.fd = new FileDescriptor();
fd.attach(this);
// 调用本地方法打开文件
open(name, mode);
}
/**
* 本地方法:打开文件
*/
private native void open(String name, String mode) throws FileNotFoundException;
2.3.2 写入方法
// RandomAccessFile.java
/**
* 将指定字节写入到此文件
* @param b 要写入的字节
* @throws IOException 如果发生I/O错误
*/
public void write(int b) throws IOException {
// 调用本地方法写入单个字节
write0(b);
}
/**
* 本地方法:写入单个字节
*/
private native void write0(int b) throws IOException;
/**
* 将b.length个字节从指定byte数组写入到此文件,并从当前文件指针开始
* @param b 数据
* @throws IOException 如果发生I/O错误
*/
public void write(byte b[]) throws IOException {
// 调用带偏移量的write方法
write(b, 0, b.length);
}
/**
* 将len个字节从指定byte数组写入到此文件,并从偏移量off开始
* @param b 数据
* @param off 数据中的起始偏移量
* @param len 要写入的字节数
* @throws IOException 如果发生I/O错误
*/
public void write(byte b[], int off, int len) throws IOException {
// 调用本地方法写入字节数组
writeBytes(b, off, len);
}
/**
* 本地方法:写入字节数组
*/
private native void writeBytes(byte b[], int off, int len) throws IOException;
三、NIO数据写入
3.1 FileChannel源码分析
3.1.1 获取FileChannel
// FileOutputStream.java
/**
* 返回与此文件输出流关联的唯一FileChannel对象
* @return 与此文件输出流关联的文件通道
*/
public FileChannel getChannel() {
synchronized (this) {
if (channel == null) {
// 创建FileChannelImpl实例
channel = FileChannelImpl.open(fd, path, false, true, append, this);
}
return channel;
}
}
3.1.2 写入方法
// FileChannelImpl.java
/**
* 将字节序列从给定的缓冲区写入此通道
* @param src 要从中读取字节的缓冲区
* @return 写入的字节数,可能为零
* @throws IOException 如果发生I/O错误
*/
public int write(ByteBuffer src) throws IOException {
ensureOpen();
if (!writable)
throw new NonWritableChannelException();
synchronized (positionLock) {
int n = 0;
int ti = -1;
try {
begin();
ti = threads.add();
if (!isOpen())
return 0;
do {
// 调用本地方法写入数据
n = IOUtil.write(fd, src, -1, nd);
} while ((n == IOStatus.INTERRUPTED) && isOpen());
return IOStatus.normalize(n);
} finally {
threads.remove(ti);
end(n > 0);
assert IOStatus.check(n);
}
}
}
/**
* 本地方法:通过文件描述符写入数据
*/
static native int write(FileDescriptor fd, ByteBuffer src, long position,
NativeDispatcher nd)
throws IOException;
3.2 ByteBuffer源码分析
3.2.1 创建ByteBuffer
// ByteBuffer.java
/**
* 分配一个新的字节缓冲区
* @param capacity 新缓冲区的容量,以字节为单位
* @return 新的字节缓冲区
* @throws IllegalArgumentException 如果capacity为负
*/
public static ByteBuffer allocate(int capacity) {
if (capacity < 0)
throw new IllegalArgumentException();
// 创建HeapByteBuffer实例
return new HeapByteBuffer(capacity, capacity);
}
/**
* 分配一个新的直接字节缓冲区
* @param capacity 新缓冲区的容量,以字节为单位
* @return 新的字节缓冲区
* @throws IllegalArgumentException 如果capacity为负
*/
public static ByteBuffer allocateDirect(int capacity) {
if (capacity < 0)
throw new IllegalArgumentException();
// 创建DirectByteBuffer实例
return new DirectByteBuffer(capacity);
}
3.2.2 写入数据
// ByteBuffer.java
/**
* 将给定的字节写入此缓冲区的当前位置,然后该位置递增
* @param b 要写入的字节
* @return 此缓冲区
*/
public abstract ByteBuffer put(byte b);
/**
* 将src数组中的所有字节写入此缓冲区的当前位置,然后该位置递增
* @param src 要写入的源数组
* @return 此缓冲区
* @throws BufferOverflowException 如果此缓冲区中剩余空间不足
* @throws ReadOnlyBufferException 如果此缓冲区是只读的
*/
public final ByteBuffer put(byte[] src) {
return put(src, 0, src.length);
}
/**
* 将src数组中从offset开始的length个字节写入此缓冲区的当前位置,然后该位置递增
* @param src 要写入的源数组
* @param offset 要写入的第一个字节在数组中的偏移量
* @param length 要写入的字节数
* @return 此缓冲区
* @throws BufferOverflowException 如果此缓冲区中剩余空间不足
* @throws IndexOutOfBoundsException 如果offset和length参数不满足0 <= offset <= offset + length <= src.length
* @throws ReadOnlyBufferException 如果此缓冲区是只读的
*/
public abstract ByteBuffer put(byte[] src, int offset, int length);
四、SharedPreferences数据写入
4.1 Editor实现源码分析
4.1.1 Editor接口
// SharedPreferences.java
/**
* 用于修改SharedPreferences的接口
*/
public interface Editor {
/**
* 向编辑器中放入一个字符串值
* @param key 要关联值的键
* @param value 要存储的字符串值,或null以删除该键的现有映射
* @return 返回此Editor,以便调用可以被链接
*/
Editor putString(String key, @Nullable String value);
/**
* 向编辑器中放入一个int值
* @param key 要关联值的键
* @param value 要存储的int值
* @return 返回此Editor,以便调用可以被链接
*/
Editor putInt(String key, int value);
// 其他类型的put方法...
/**
* 提交在编辑器中所做的所有更改
* @return 如果成功地将新值写入持久存储,则返回true
*/
boolean commit();
/**
* 异步地提交在编辑器中所做的所有更改
*/
void apply();
}
4.1.2 Editor实现类
// SharedPreferencesImpl.java
/**
* Editor接口的实现类
*/
private final class EditorImpl implements Editor {
// 存储待提交的修改
private final Map<String, Object> mModified = Maps.newHashMap();
// 标记是否删除所有内容
private boolean mClear = false;
public EditorImpl() {
}
@Override
public Editor putString(String key, @Nullable String value) {
synchronized (this) {
// 将修改添加到mModified映射中
mModified.put(key, value);
return this;
}
}
@Override
public Editor putInt(String key, int value) {
synchronized (this) {
// 将修改添加到mModified映射中
mModified.put(key, value);
return this;
}
}
// 其他类型的put方法实现...
@Override
public void apply() {
// 先在内存中更新
final MemoryCommitResult mcr = commitToMemory();
// 创建一个Runnable用于将更改写入磁盘
final Runnable awaitCommit = new Runnable() {
@Override
public void run() {
try {
// 等待磁盘写入完成
mcr.writtenToDiskLatch.await();
} catch (InterruptedException ignored) {
}
}
};
// 将任务加入队列,由Handler处理
QueuedWork.add(awaitCommit);
// 创建一个写入磁盘的任务
Runnable postWriteRunnable = new Runnable() {
@Override
public void run() {
// 执行等待任务
awaitCommit.run();
// 从队列中移除任务
QueuedWork.remove(awaitCommit);
}
};
// 将写入磁盘的任务提交给Handler
SharedPreferencesImpl.this.enqueueDiskWrite(mcr, postWriteRunnable);
// 通知监听器
notifyListeners(mcr);
}
@Override
public boolean commit() {
// 先提交到内存
MemoryCommitResult mcr = commitToMemory();
// 将更改写入磁盘
SharedPreferencesImpl.this.enqueueDiskWrite(
mcr, null /* sync write on this thread okay */);
try {
// 等待写入完成
mcr.writtenToDiskLatch.await();
} catch (InterruptedException e) {
return false;
}
// 通知监听器
notifyListeners(mcr);
return mcr.writeToDiskResult;
}
/**
* 将更改提交到内存
*/
private MemoryCommitResult commitToMemory() {
// 创建内存提交结果对象
MemoryCommitResult mcr = new MemoryCommitResult();
synchronized (SharedPreferencesImpl.this) {
// 检查是否需要获取锁
if (mDiskWritesInFlight > 0) {
// 复制当前内容,因为可能会有其他写入操作
mMap = new HashMap<String, Object>(mMap);
}
mcr.mapToWriteToDisk = mMap;
mDiskWritesInFlight++;
boolean hasListeners = mListeners.size() > 0;
if (hasListeners) {
mcr.keysModified = new ArrayList<String>();
mcr.listeners = new HashSet<OnSharedPreferenceChangeListener>(mListeners.keySet());
}
synchronized (this) {
if (mClear) {
// 如果标记为清除所有内容
if (!mMap.isEmpty()) {
mcr.changesMade = true;
mMap.clear();
}
mClear = false;
}
// 处理所有修改
for (Map.Entry<String, Object> e : mModified.entrySet()) {
String k = e.getKey();
Object v = e.getValue();
// 检查值是否需要删除
if (v == this || v == null) {
if (!mMap.containsKey(k)) {
continue;
}
mMap.remove(k);
} else {
// 检查值是否有变化
if (mMap.containsKey(k)) {
Object existingValue = mMap.get(k);
if (existingValue != null && existingValue.equals(v)) {
continue;
}
}
mMap.put(k, v);
}
mcr.changesMade = true;
if (hasListeners) {
mcr.keysModified.add(k);
}
}
// 清空修改记录
mModified.clear();
}
}
return mcr;
}
}
4.2 磁盘写入源码分析
4.2.1 写入方法
// SharedPreferencesImpl.java
/**
* 将更改写入磁盘
*/
private void writeToFile(MemoryCommitResult mcr, boolean isFromSyncCommit) {
// 如果正在等待备份完成,则延迟写入
if (mBackupFile.exists()) {
if (mFile.exists()) {
// 备份文件存在但主文件也存在,这是异常情况,删除备份文件
mBackupFile.delete();
} else {
// 主文件不存在,恢复备份文件
renameTo(mBackupFile, mFile, false);
}
}
// 尝试写入文件
try {
FileOutputStream str = createFileOutputStream(mFile);
if (str == null) {
mcr.setDiskWriteResult(false);
return;
}
// 将Map写入XML文件
XmlUtils.writeMapXml(mcr.mapToWriteToDisk, str);
// 同步到磁盘
FileUtils.sync(str);
str.close();
// 写入成功后,删除备份文件
ContextImpl.setFilePermissionsFromMode(mFile.getPath(), mMode, 0);
try {
final StructStat stat = Os.stat(mFile.getPath());
final int mode = stat.st_mode;
if ((mode & 0007) != 0) {
Log.w(TAG, "File " + mFile + " has insecure permissions ("
+ Integer.toOctalString(mode) + ")");
}
} catch (ErrnoException e) {
// 忽略
}
// 写入成功
mcr.setDiskWriteResult(true);
// 删除备份文件
if (mBackupFile.exists()) {
mBackupFile.delete();
}
return;
} catch (XmlPullParserException e) {
Log.w(TAG, "writeToFile: Got exception:", e);
} catch (IOException e) {
Log.w(TAG, "writeToFile: Got exception:", e);
}
// 写入失败,创建备份文件
if (mFile.exists()) {
if (mFile.canWrite()) {
// 如果文件可写但写入失败,可能是空间不足等原因
Log.w(TAG, "Couldn't write: " + mFile);
}
// 创建备份文件
createBackupFile(mFile);
}
mcr.setDiskWriteResult(false);
}
4.2.2 异步写入处理
// SharedPreferencesImpl.java
/**
* 将磁盘写入任务加入队列
*/
private void enqueueDiskWrite(final MemoryCommitResult mcr,
final Runnable postWriteRunnable) {
// 如果是同步提交,直接在当前线程写入
final boolean isFromSyncCommit = (postWriteRunnable == null);
final Runnable writeToDiskRunnable = new Runnable() {
@Override
public void run() {
synchronized (mWritingToDiskLock) {
// 执行实际的磁盘写入
writeToFile(mcr, isFromSyncCommit);
}
synchronized (SharedPreferencesImpl.this) {
mDiskWritesInFlight--;
}
if (postWriteRunnable != null) {
postWriteRunnable.run();
}
}
};
// 如果是同步提交,直接执行
if (isFromSyncCommit) {
boolean wasEmpty = false;
synchronized (SharedPreferencesImpl.this) {
wasEmpty = mDiskWritesInFlight == 1;
}
if (wasEmpty) {
// 如果只有一个写入任务,直接执行
writeToDiskRunnable.run();
return;
}
}
// 否则,将任务提交到Handler
QueuedWork.queue(writeToDiskRunnable, !isFromSyncCommit);
}
五、SQLite数据库写入
5.1 SQLiteDatabase源码分析
5.1.1 写入方法
// SQLiteDatabase.java
/**
* 向指定表中插入一行数据
* @param table 要插入数据的表名
* @param nullColumnHack 当values参数为空时,指定的列名,用于插入NULL值
* @param values 包含要插入数据的键值对
* @return 新行的行号,如果发生错误则返回-1
*/
public long insert(String table, String nullColumnHack, ContentValues values) {
// 调用带冲突解决策略的insert方法
return insertWithOnConflict(table, nullColumnHack, values, CONFLICT_NONE);
}
/**
* 向指定表中插入一行数据,指定冲突解决策略
* @param table 要插入数据的表名
* @param nullColumnHack 当values参数为空时,指定的列名,用于插入NULL值
* @param values 包含要插入数据的键值对
* @param conflictAlgorithm 冲突解决策略
* @return 新行的行号,如果发生错误则返回-1
*/
public long insertWithOnConflict(String table, String nullColumnHack,
ContentValues values, int conflictAlgorithm) {
acquireReference();
try {
// 检查是否处于只读模式
if (isReadOnly()) {
throw new SQLiteException("attempt to write a readonly database");
}
// 构建SQL语句
StringBuilder sql = new StringBuilder();
sql.append("INSERT");
sql.append(CONFLICT_VALUES[conflictAlgorithm]);
sql.append(" INTO ");
sql.append(table);
sql.append('(');
Object[] bindArgs = null;
int size = (values != null) ? values.size() : 0;
if (size > 0) {
bindArgs = new Object[size];
int i = 0;
for (String colName : values.keySet()) {
sql.append((i > 0) ? "," : "");
sql.append(colName);
bindArgs[i++] = values.get(colName);
}
sql.append(')');
sql.append(" VALUES (");
for (i = 0; i < size; i++) {
sql.append((i > 0) ? ",?" : "?");
}
} else {
// 如果values为空,使用nullColumnHack
sql.append(nullColumnHack == null ? "NULL" : nullColumnHack);
sql.append(')');
sql.append(" VALUES (");
sql.append(nullColumnHack == null ? "NULL" : "?");
}
sql.append(')');
// 执行SQL语句
SQLiteStatement statement = new SQLiteStatement(this, sql.toString(), bindArgs);
try {
return statement.executeInsert();
} finally {
statement.close();
}
} finally {
releaseReference();
}
}
5.1.2 更新方法
// SQLiteDatabase.java
/**
* 更新指定表中的数据
* @param table 要更新数据的表名
* @param values 包含要更新数据的键值对
* @param whereClause 用于过滤要更新的行的SQL WHERE子句(不包括WHERE关键字)
* @param whereArgs 用于替换whereClause中的?占位符的值
* @return 受影响的行数
*/
public int update(String table, ContentValues values, String whereClause,
String[] whereArgs) {
// 调用带冲突解决策略的update方法
return updateWithOnConflict(table, values, whereClause, whereArgs, CONFLICT_NONE);
}
/**
* 更新指定表中的数据,指定冲突解决策略
* @param table 要更新数据的表名
* @param values 包含要更新数据的键值对
* @param whereClause 用于过滤要更新的行的SQL WHERE子句(不包括WHERE关键字)
* @param whereArgs 用于替换whereClause中的?占位符的值
* @param conflictAlgorithm 冲突解决策略
* @return 受影响的行数
*/
public int updateWithOnConflict(String table, ContentValues values, String whereClause,
String[] whereArgs, int conflictAlgorithm) {
acquireReference();
try {
// 检查是否处于只读模式
if (isReadOnly()) {
throw new SQLiteException("attempt to write a readonly database");
}
// 构建SQL语句
StringBuilder sql = new StringBuilder(120);
sql.append("UPDATE ");
sql.append(CONFLICT_VALUES[conflictAlgorithm]);
sql.append(" ");
sql.append(table);
sql.append(" SET ");
// 创建绑定参数
int setValuesSize = (values != null) ? values.size() : 0;
Object[] bindArgs = new Object[setValuesSize +
(whereArgs == null ? 0 : whereArgs.length)];
int i = 0;
// 添加SET子句
if (values != null) {
for (String colName : values.keySet()) {
sql.append((i > 0) ? "," : "");
sql.append(colName);
sql.append("=?");
bindArgs[i++] = values.get(colName);
}
}
// 添加WHERE子句
if (!TextUtils.isEmpty(whereClause)) {
sql.append(" WHERE ");
sql.append(whereClause);
}
// 添加WHERE子句的参数
if (whereArgs != null) {
for (String arg : whereArgs) {
bindArgs[i++] = arg;
}
}
// 执行SQL语句
SQLiteStatement statement = new SQLiteStatement(this, sql.toString(), bindArgs);
try {
return statement.executeUpdateDelete();
} finally {
statement.close();
}
} finally {
releaseReference();
}
}
5.2 SQLiteStatement源码分析
5.2.1 执行写入
// SQLiteStatement.java
/**
* 执行SQL INSERT语句并返回新行的行号
* @return 新行的行号,如果发生错误则返回-1
*/
public long executeInsert() {
acquireReference();
try {
// 调用本地方法执行插入
return nativeExecuteInsert(mStatementPtr);
} finally {
releaseReference();
}
}
/**
* 执行SQL UPDATE或DELETE语句并返回受影响的行数
* @return 受影响的行数
*/
public int executeUpdateDelete() {
acquireReference();
try {
// 调用本地方法执行更新或删除
return nativeExecuteUpdateDelete(mStatementPtr);
} finally {
releaseReference();
}
}
/**
* 本地方法:执行INSERT语句
*/
private native long nativeExecuteInsert(long statementPtr);
/**
* 本地方法:执行UPDATE或DELETE语句
*/
private native int nativeExecuteUpdateDelete(long statementPtr);
六、ContentProvider数据写入
6.1 ContentProvider源码分析
6.1.1 insert方法
// ContentProvider.java
/**
* 向ContentProvider中插入一行数据
* @param uri 插入数据的目标URI
* @param values 包含要插入数据的键值对
* @return 新插入行的URI
*/
public Uri insert(Uri uri, ContentValues values) {
throw new UnsupportedOperationException("insert is not supported");
}
/**
* 向ContentProvider中批量插入多行数据
* @param uri 插入数据的目标URI
* @param values 包含要插入数据的键值对数组
* @return 插入的行数
*/
public int bulkInsert(Uri uri, ContentValues[] values) {
int result = 0;
// 逐个插入数据
for (ContentValues v : values) {
Uri newUri = insert(uri, v);
if (newUri != null) {
result++;
}
}
return result;
}
6.2 ContentResolver源码分析
6.2.1 insert方法
// ContentResolver.java
/**
* 向ContentProvider中插入一行数据
* @param uri 插入数据的目标URI
* @param values 包含要插入数据的键值对
* @return 新插入行的URI
*/
public Uri insert(Uri uri, ContentValues values) {
Preconditions.checkNotNull(uri, "uri");
Preconditions.checkNotNull(values, "values");
// 获取ContentProvider
IContentProvider provider = acquireProvider(uri);
if (provider == null) {
throw new IllegalArgumentException("Unknown URI " + uri);
}
Uri result;
try {
// 调用ContentProvider的insert方法
result = provider.insert(mPackageName, uri, values);
} catch (RemoteException e) {
// 处理远程异常
e.rethrowFromSystemServer();
return null;
} finally {
releaseProvider(provider);
}
return result;
}
/**
* 向ContentProvider中批量插入多行数据
* @param uri 插入数据的目标URI
* @param values 包含要插入数据的键值对数组
* @return 插入的行数
*/
public int bulkInsert(Uri uri, ContentValues[] values) {
Preconditions.checkNotNull(uri, "uri");
Preconditions.checkNotNull(values, "values");
// 获取ContentProvider
IContentProvider provider = acquireProvider(uri);
if (provider == null) {
throw new IllegalArgumentException("Unknown URI " + uri);
}
int result;
try {
// 调用ContentProvider的bulkInsert方法
result = provider.bulkInsert(mPackageName, uri, values);
} catch (RemoteException e) {
// 处理远程异常
e.rethrowFromSystemServer();
return 0;
} finally {
releaseProvider(provider);
}
return result;
}
七、文件系统写入流程
7.1 Linux文件系统写入概述
Android基于Linux内核,文件系统写入最终会调用Linux系统调用。
7.2 关键系统调用
7.2.1 open系统调用
// 打开文件的系统调用
int open(const char *pathname, int flags, mode_t mode);
7.2.2 write系统调用
// 写入数据的系统调用
ssize_t write(int fd, const void *buf, size_t count);
7.2.3 fsync系统调用
// 将文件内容同步到磁盘的系统调用
int fsync(int fd);
7.3 Android文件系统优化
Android对文件系统进行了多种优化,包括:
- 日志式文件系统:如Ext4、F2FS
- 写入缓冲策略:减少磁盘IO
- 预分配空间:避免文件碎片化
- 延迟写入:合并小写入操作
八、数据写入性能优化
8.1 批量写入优化
// 使用事务进行批量写入示例
public void batchInsertData(List<DataItem> dataItems) {
SQLiteDatabase db = getWritableDatabase();
db.beginTransaction();
try {
for (DataItem item : dataItems) {
ContentValues values = new ContentValues();
values.put("column1", item.getValue1());
values.put("column2", item.getValue2());
// 插入数据
db.insert("table_name", null, values);
}
// 设置事务成功
db.setTransactionSuccessful();
} finally {
// 结束事务
db.endTransaction();
}
}
8.2 缓冲区优化
// 使用BufferedOutputStream优化文件写入
public void writeLargeData(byte[] data) {
BufferedOutputStream bos = null;
try {
FileOutputStream fos = new FileOutputStream("large_file.txt");
// 使用8KB缓冲区
bos = new BufferedOutputStream(fos, 8192);
bos.write(data);
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (bos != null) {
bos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
8.3 异步写入优化
// 使用线程池进行异步写入
private ExecutorService mExecutorService = Executors.newSingleThreadExecutor();
public void asyncWriteData(final byte[] data) {
mExecutorService.execute(new Runnable() {
@Override
public void run() {
FileOutputStream fos = null;
try {
fos = new FileOutputStream("data.txt");
fos.write(data);
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (fos != null) {
fos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
});
}
九、数据一致性保证
9.1 事务机制
// SQLite事务示例
public void transferMoney(String fromAccount, String toAccount, double amount) {
SQLiteDatabase db = getWritableDatabase();
db.beginTransaction();
try {
// 从源账户扣款
ContentValues fromValues = new ContentValues();
fromValues.put("balance", getBalance(fromAccount) - amount);
db.update("accounts", fromValues, "account = ?", new String[]{fromAccount});
// 向目标账户存款
ContentValues toValues = new ContentValues();
toValues.put("balance", getBalance(toAccount) + amount);
db.update("accounts", toValues, "account = ?", new String[]{toAccount});
// 设置事务成功
db.setTransactionSuccessful();
} catch (Exception e) {
e.printStackTrace();
} finally {
// 结束事务
db.endTransaction();
}
}
9.2 同步机制
// 使用fsync确保数据持久化
public void writeAndSyncData(byte[] data) {
FileOutputStream fos = null;
try {
fos = new FileOutputStream("critical_data.txt");
fos.write(data);
// 强制同步到磁盘
fos.getFD().sync();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (fos != null) {
fos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
十、常见问题与解决方案
10.1 写入性能问题
问题描述:数据写入缓慢,影响应用响应性能。
可能原因:
- 频繁的小写入操作
- 未使用缓冲区
- 同步写入过于频繁
- 磁盘IO瓶颈
解决方案:
- 批量处理写入操作
- 使用BufferedOutputStream等缓冲区类
- 采用异步写入机制
- 优化数据库操作,使用事务
10.2 数据丢失问题
问题描述:写入后的数据在应用重启后丢失。
可能原因:
- 未调用sync方法确保数据持久化
- 写入操作未完成时应用崩溃
- 磁盘故障
解决方案:
- 在关键写入操作后调用sync方法
- 使用事务确保操作的原子性
- 实现数据恢复机制
- 添加错误处理和日志记录
10.3 并发写入冲突
问题描述:多线程或多进程同时写入导致数据不一致。
可能原因:
- 缺乏同步机制
- 未使用事务
- 文件锁使用不当
解决方案:
- 使用synchronized关键字或Lock实现线程同步
- 在数据库操作中使用事务
- 使用文件锁机制(如FileLock)
- 考虑使用专门的并发数据结构