深入理解Android数据写入机制:从Java到Kernel全解析(14)

138 阅读17分钟

深入理解Android数据写入机制:从Java到Kernel全解析

一、Android数据存储概述

1.1 数据存储方式分类

Android提供了多种数据存储方式,主要包括:

  • 文件存储:通过Java IO或NIO操作文件
  • SharedPreferences:轻量级键值对存储
  • SQLite数据库:结构化数据存储
  • ContentProvider:跨应用数据共享
  • 网络存储:通过网络API存储到远程服务器

1.2 数据写入的核心流程

无论采用哪种存储方式,数据写入的核心流程大致相同:

  1. 数据准备:将应用层数据转换为可存储格式
  2. 权限检查:验证应用是否有写入权限
  3. 打开存储介质:文件、数据库等
  4. 数据写入:将数据写入存储介质
  5. 同步确认:确保数据持久化
  6. 关闭资源:释放打开的资源

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 写入性能问题

问题描述:数据写入缓慢,影响应用响应性能。

可能原因

  1. 频繁的小写入操作
  2. 未使用缓冲区
  3. 同步写入过于频繁
  4. 磁盘IO瓶颈

解决方案

  1. 批量处理写入操作
  2. 使用BufferedOutputStream等缓冲区类
  3. 采用异步写入机制
  4. 优化数据库操作,使用事务

10.2 数据丢失问题

问题描述:写入后的数据在应用重启后丢失。

可能原因

  1. 未调用sync方法确保数据持久化
  2. 写入操作未完成时应用崩溃
  3. 磁盘故障

解决方案

  1. 在关键写入操作后调用sync方法
  2. 使用事务确保操作的原子性
  3. 实现数据恢复机制
  4. 添加错误处理和日志记录

10.3 并发写入冲突

问题描述:多线程或多进程同时写入导致数据不一致。

可能原因

  1. 缺乏同步机制
  2. 未使用事务
  3. 文件锁使用不当

解决方案

  1. 使用synchronized关键字或Lock实现线程同步
  2. 在数据库操作中使用事务
  3. 使用文件锁机制(如FileLock)
  4. 考虑使用专门的并发数据结构