Android_存储

SQLite是Android内置的一个小型、关系型、属于文本型的数据库。 Android提供了对 SQLite数据库的完全支持,应用程序中的任何类都可以通过名称来访问任何的数据库,但是应用程序之外的就不能访问。

Android中,通过SQLiteOpenHelper类来实现对SQLite数据库的操作。

1. SharedPreferences

适用范围**:**保存少量的数据,且这些数据的格式非常简单:字符串型、基本类型的值。比如应用程序的各种配置信息(如是否打开音效、是否使用震动效果、小游戏的玩家积分等),解锁口 令密码等

核心原理**:**保存基于XML文件存储的key-value键值对数据,通常用来存储一些简单的配置信息。通过DDMS的File Explorer面板,展开文件浏览树,很明显SharedPreferences数据总是存储在/data/data//shared_prefs目录下。SharedPreferences对象本身只能获取数据而不支持存储和修改,存储修改是通过SharedPreferences.edit()获取的内部接口Editor对象实现。 SharedPreferences本身是一 个接口,程序无法直接创建SharedPreferences实例,只能通过Context提供的getSharedPreferences(String name, int mode)方法来获取SharedPreferences实例,该方法中name表示要操作的xml文件名,第二个参数具体如下:

​ *Context.MODE_PRIVATE*: 指定该SharedPreferences数据只能被本应用程序读、写。

​ *Context.MODE_WORLD_READABLE*: 指定该SharedPreferences数据能被其他应用程序读,但不能写。

Context.MODE_WORLD_WRITEABLE: 指定该SharedPreferences数据能被其他应用程序读,写

Editor有如下主要重要方法:

​ ***SharedPreferences.Editor clear():***清空SharedPreferences里所有数据

​ *SharedPreferences.Editor putXxx(String key , xxx value):* 向SharedPreferences存入指定key对应的数据,其中xxx 可以是boolean,float,int等各种基本类型据

​ *SharedPreferences.Editor remove():* 删除SharedPreferences中指定key对应的数据项

​ *boolean commit():* 当Editor编辑完成后,使用该方法提交修改

switch(v.getId()){
    case R.id.btnSet:
        //步骤1:获取输入值
        String code = txtCode.getText().toString().trim();
        //步骤2-1:创建一个SharedPreferences.Editor接口对象,lock表示要写入的XML文件名,MODE_WORLD_WRITEABLE写操作
        SharedPreferences.Editor editor = getSharedPreferences("lock", MODE_WORLD_WRITEABLE).edit();
        //步骤2-2:将获取过来的值放入文件
        editor.putString("code", code);
        //步骤3:提交
        editor.commit();
        Toast.makeText(getApplicationContext(), "口令设置成功", Toast.LENGTH_LONG).show();
        break;
    case R.id.btnGet:
        //-- 如果使用其他应用程序的context
        // Context pvCount = createPackageContext("com.tony.app", Context.CONTEXT_IGNORE_SECURITY);这里的com.tony.app就是其他程序的包名
        //  SharedPreferences read = pvCount.getSharedPreferences("lock", Context.MODE_WORLD_READABLE);
        //步骤1:创建一个SharedPreferences接口对象
        SharedPreferences read = getSharedPreferences("lock", MODE_WORLD_READABLE);
        //步骤2:获取文件中的值
        String value = read.getString("code", "");
        Toast.makeText(getApplicationContext(), "口令为:"+value, Toast.LENGTH_LONG).show();

        break;

}

2. File 文件操作

核心原理: Context提供了两个方法来打开数据文件里的文件IO流 FileInputStream openFileInput(String name); FileOutputStream(String name , int mode),这两个方法第一个参数 用于指定文件名,第二个参数指定打开文件的模式。具体有以下值可选:

MODE_PRIVATE:为默认操作模式,代表该文件是私有数据,只能被应用本身访问,在该模式下,写入的内容会覆盖原文件的内容,如果想把新写入的内容追加到原文件中。可 以使用Context.MODE_APPEND

​ *MODE_APPEND*:模式会检查文件是否存在,存在就往文件追加内容,否则就创建新文件。

MODE_WORLD_READABLE:表示当前文件可以被其他应用读取;

​ *MODE_WORLD_WRITEABLE*:表示当前文件可以被其他应用写入。

除此之外,Context还提供了如下几个重要的方法:

getDir(String name , int mode):在应用程序的数据文件夹下获取或者创建name对应的子目录

File getFilesDir():获取该应用程序的数据文件夹得绝对路径

String[] fileList():返回该应用数据文件夹的全部文件

public String read() {
    try {
        //文件目录: /data/data/cn.tony.app/files/message.txt
        FileInputStream inStream = this.openFileInput("message.txt"); 
        byte[] buffer = new byte[1024];
        int hasRead = 0;
        StringBuilder sb = new StringBuilder();
        while ((hasRead = inStream.read(buffer)) != -1) {
            sb.append(new String(buffer, 0, hasRead));
        }

        inStream.close();
        return sb.toString();
    } catch (Exception e) {
        e.printStackTrace();
    } 
    return null;
}

public void write(String msg){
    // 步骤1:获取输入值
    if(msg == null) return;
    try {
        // 步骤2:创建一个FileOutputStream对象,MODE_APPEND追加模式
        FileOutputStream fos = openFileOutput("message.txt",
                                              MODE_APPEND);
        // 步骤3:将获取过来的值放入文件
        fos.write(msg.getBytes());
        // 步骤4:关闭数据流
        fos.close();
    } catch (Exception e) {
        e.printStackTrace();
    }
}

.2. SD 卡

  • 调用Environment的getExternalStorageState()方法判断手机上是否插了sd卡,且应用程序具有读写SD卡的权限,如下代码将返回true, Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)
  • 调用Environment.getExternalStorageDirectory()方法来获取外部存储器,也就是SD卡的目录,或者使用 “/mnt/sdcard/” 目录
  • 使用IO流操作SD卡上的文件
// 文件写操作函数
private void write(String content) {
    if (Environment.getExternalStorageState().equals(
        Environment.MEDIA_MOUNTED)) { // 如果sdcard存在
        File file = new File(Environment.getExternalStorageDirectory()
                             .toString()
                             + File.separator
                             + DIR
                             + File.separator
                             + FILENAME); // 定义File类对象
        if (!file.getParentFile().exists()) { // 父文件夹不存在
            file.getParentFile().mkdirs(); // 创建文件夹
        }
        PrintStream out = null; // 打印流对象用于输出
        try {
            out = new PrintStream(new FileOutputStream(file, true)); // 追加文件
            out.println(content);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (out != null) {
                out.close(); // 关闭打印流
            }
        }
    } else { // SDCard不存在,使用Toast提示用户
        Toast.makeText(this, "保存失败,SD卡不存在!", Toast.LENGTH_LONG).show();
    }
}

// 文件读操作函数
private String read() {
    if (Environment.getExternalStorageState().equals(
        Environment.MEDIA_MOUNTED)) { // 如果sdcard存在
        File file = new File(Environment.getExternalStorageDirectory()
                             .toString()
                             + File.separator
                             + DIR
                             + File.separator
                             + FILENAME); // 定义File类对象
        if (!file.getParentFile().exists()) { // 父文件夹不存在
            file.getParentFile().mkdirs(); // 创建文件夹
        }
        Scanner scan = null; // 扫描输入
        StringBuilder sb = new StringBuilder();
        try {
            scan = new Scanner(new FileInputStream(file)); // 实例化Scanner
            while (scan.hasNext()) { // 循环读取
                sb.append(scan.next() + "\n"); // 设置文本
            }
            return sb.toString();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (scan != null) {
                scan.close(); // 关闭打印流
            }
        }
    } else { // SDCard不存在,使用Toast提示用户
        Toast.makeText(this, "读取失败,SD卡不存在!", Toast.LENGTH_LONG).show();
    }
    return null;
}

3. SQLiteOpenHelper

.1. 基本概念

  • 定义:SQLiteOpenHelper是一个辅助类
  • 作用:管理数据库(创建、增、修、删) & 版本的控制
  • 使用过程:通过创建子类继承SQLiteOpenHelper类,实现它的一些方法来对数据库进行操作。 在实际开发中,为了能够更好的管理和维护数据库,我们会封装一个继承自SQLiteOpenHelper类的数据库操作类,然后以这个类为基础,再封装我们的业务逻辑方法
  • // 调用getReadableDatabase()或getWritableDatabase()才算真正创建或打开数据库
方法名 作用 备注
onCreate() 创建数据库 创建数据库时自动调用
onUpgrade() 升级数据库 数据库版本发生变化的时候回调(取决于数据库版本),只要这个版本高于之前的版本, 就会触发这个onUpgrade()方法
close() 关闭所有打开的数据库对象
execSQL() 可进行增删改操作, 不能进行查询操作
query()、rawQuery() 查询数据库
insert() 插入数据
delete() 删除数据
getWritableDatabase() 创建或打开可以读/写的数据库 通过返回的SQLiteDatabase对象对数据库进行操作
getReadableDatabase() 创建或打开可读的数据库 同上

.2. 实例代码

public class SQLiteHelper extends SQLiteOpenHelper{
    private SQLiteDatabase myDataBase;
    private final Context myContext;
    private final String Tag="SQLiteHepler";
    //数据库版本号
    private static Integer Version = 1;
    /**
     * Constructor
     * Takes and keeps a reference of the passed context in order to access to the application assets and resources.
     * @param context
     */
    public SQLiteHelper(Context context) {
        super(context, Constant.DB_NAME, null, 1);
        this.myContext = context;
    }

    public void openDataBase()  {
        String dbpath = myContext.getDatabasePath(Constant.DB_NAME).getPath();
        if (myDataBase != null && myDataBase.isOpen())
        {
            return;
        }
        myDataBase = SQLiteDatabase.openDatabase(dbpath,null,SQLiteDatabase.OPEN_READWRITE);
    }

    public Cursor rawQuery(String sql) {
        return myDataBase.rawQuery(sql, null);
    }
    @Override
    public synchronized void close() {

        if(myDataBase != null)
            myDataBase.close();

        super.close();

    }
    /**
     * @function: 创建数据库
     * */
    @Override
    public void onCreate(SQLiteDatabase db) {
        boolean dbExist = checkDataBase();
        if(dbExist){
            Log.i(Tag, "onCreate: do nothing - database already exist");
        }else {
            db.execSQL(Constant.SQL);
            Log.i(Tag, "onCreate: 创建数据库完成");
        }
    }
    /**
     * @function: 升级数据库
     * */
    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
		Log.i(Tag,"更新数据库版本为:"+newVersion);
    }

    /**
     * @function: Creates a empty database on the system and rewrites it with your own database.
     * */
    public void createDataBaseByCopy() throws IOException {
        boolean dbExist = checkDataBase();
        if(dbExist){
            //do nothing - database already exist
        }else{
            //By calling this method and empty database will be created into the default system path
            //of your application so we are gonna be able to overwrite that database with our database.
            this.getReadableDatabase();  //创建或打开可读的数据库
            try {
                Log.e("Check DB", "copied");
                copyDataBase();
            } catch (IOException e) {
                throw new Error("Error copying database");
            }
        }

    }

    /**
     * Check if the database already exist to avoid re-copying the file each time you open the application.
     * @return true if it exists, false if it doesn't
     */
    private boolean checkDataBase(){
        SQLiteDatabase checkDB = null;
        try{
            String myPath = Constant.DB_PATH + Constant.DB_NAME;
            File file = new File(myPath);
            if (file.exists() && !file.isDirectory())
                checkDB = SQLiteDatabase.openDatabase(myPath, null, SQLiteDatabase.OPEN_READONLY);
        }catch(SQLiteException e){
            Log.i(Tag,"checkDataBase: database does't exist yet.");
        }
        if(checkDB != null){
            checkDB.close();
        }
        return checkDB != null ? true : false;
    }

    /**
     * Copies your database from your local assets-folder to the just created empty database in the
     * system folder, from where it can be accessed and handled.
     * This is done by transfering bytestream.
     * */
    private void copyDataBase() throws IOException{
        //Open your local db as the input stream
        InputStream myInput = myContext.getAssets().open(Constant.DB_NAME);
        // Path to the just created empty db
        String outFileName = Constant.DB_PATH + Constant.DB_NAME;
        //Open the empty db as the output stream
        OutputStream myOutput = new FileOutputStream(outFileName);
        //transfer bytes from the inputfile to the outputfile
        byte[] buffer = new byte[1024];
        int length;
        while ((length = myInput.read(buffer))>0){
            myOutput.write(buffer, 0, length);
        }
        //Close the streams
        myOutput.flush();
        myOutput.close();
        myInput.close();
    }
}

2. 增删查改批量操作

  • 使用sql语句
  • 使用Android提供的contentvalue方式
c.getColumnIndex(String columnName);//返回某列名对应的列索引值  
c.getString(int columnIndex);   //返回当前行指定列的值 
public class SQLiteOperation {
    private final String Tag="SQLiteOperation";
    private SQLiteHelper sqLiteHelper;
    private SQLiteOperation(){
    }
    private static class Inner {
        private static final SQLiteOperation instance = new SQLiteOperation();
    }
    public static SQLiteOperation getSingleton(){
        return SQLiteOperation.Inner.instance;
    }
    public void initSQLiteHepler(Context context){
        sqLiteHelper=new SQLiteHelper(context);    //todo? 这里并发是不是会存在问题
    }
    /**
     * @function: 增加单个数据到表中
     * */
    public Boolean add(FlexData flexData){
        SQLiteDatabase database=sqLiteHelper.getWritableDatabase();
        ContentValues contentValues=new ContentValues();
        contentValues.put("flexdata",flexData.getStringFlexData());
        contentValues.put("timestamp",flexData.getTimestamp());
        return database.insert("table_flex",null,contentValues)>0?true:false;
    }
    public void addBatch(ArrayList<FlexData> flexDataArrayList){
        SQLiteDatabase database=sqLiteHelper.getWritableDatabase();
        database.beginTransaction();
        try{
            ContentValues contentValues=new ContentValues();
            for(FlexData flexData: flexDataArrayList){
                contentValues.put("flexdata",flexData.getStringFlexData());
                contentValues.put("timestamp",flexData.getTimestamp());
                database.insert("table_flex",null,contentValues);
                contentValues.clear();
            }
            //调用该方法设置事务成功,否则endTransaction()方法将事务进行回滚操作
            database.setTransactionSuccessful();
        }finally {
            //由事务的标志决定是提交还是回滚事务
            database.endTransaction();
        }
    }
    /**
     * @function: 查询表中所有数据
     * */
    public ArrayList<FlexData> queryAll(){
        SQLiteDatabase sqLiteDatabase=sqLiteHelper.getReadableDatabase();
        ArrayList<FlexData> flexDataArrayList=new ArrayList<FlexData>();
        //Log.i(Tag,"queryAll: 操作");
        Cursor cursor=sqLiteDatabase.query("table_flex",new String[]{"flexdata","timestamp"},null,null,null,null,null);
        //Log.i(Tag,"queryAll: successful,the size="+cursor.getCount());
        while(cursor.moveToNext()){
            //Log.i(Tag,"queryAll: successful,the size="+cursor.getCount());
            @SuppressLint("Range") FlexData flexData=new FlexData(cursor.getString(cursor.getColumnIndex("flexdata")),cursor.getString(cursor.getColumnIndex("timestamp")));
            //Log.i(Tag,flexData.toString());
            flexDataArrayList.add(flexData);
        }
        //Log.i(Tag,"queryAll: 操作成功,获取数据大小="+flexDataArrayList.size());
        return flexDataArrayList;
    }
    /**
     * @function: 删除表操作
     * */
    public Boolean deleteAll(){
        try{
            SQLiteDatabase sqLiteDatabase=sqLiteHelper.getWritableDatabase();
            String sql="delete from table_flex";    //"delete from table_flex"; "DROP TABLE table_flex"
            sqLiteDatabase.execSQL(sql);
            Log.i(Tag,"deleteAll: 删除操作成功");
            return true;
        }catch (SQLException e){
            e.printStackTrace();
        }
        return false;
    }
}
.1. 选择性查询
  • table:表名。
  • columns:要查询出来的列名。
  • selection:查询条件子句。
  • selectionArgs:对应于selection语句中占位符的值。
  • groupBy:分组。相当于select语句group by关键字后面的部分。
  • having:分组后聚合的过滤条件。相当于select语句having关键字后面的部分。
  • orderBy:排序。相当于select语句order by关键字后面的部分 ASC或DESC。
  • limit:指定偏移量和获取的记录数。
query( table, columns, selection, selectionArgs, groupBy, having, orderBy, limit );
public Cursor queryAllCursor(String arg){
    SQLiteDatabase sqLiteDatabase=sqLiteHelper.getReadableDatabase();
    Cursor cursor=sqLiteDatabase.query("table_flex",new String[]{"label","flexdata"},"label like ?",new String[]{arg},null,null,null);
    while(cursor.moveToNext()){
        @SuppressLint("Range") FlexData flexData=new FlexData(cursor.getString(cursor.getColumnIndex("flexdata")),cursor.getString(cursor.getColumnIndex("timestamp")),cursor.getString(cursor.getColumnIndex("label")));
        flexDataArrayList.add(flexData);
    }
    Log.i(Tag,"queryAllCursor,查询个数为:"+cursor.getCount());
    return cursor;
}

3. 测试工具

  • 通过DeviceFile Explorer插件可以在手机上找到对应的sqlite数据库文件,然后使用sqlitespy工具进行查看
  • 通过编写测试的activity来检验数据库操作是不是有问题。
  • 通过在测试文件中编写(这个目前不会) 示例demo,但是找不到对应的包 url2

4. 插件工具

  • SQLiteSpy
  • Android SDK的tools目录下提供了一个sqlite3.exe工具,程序运行生成的*.db文件一般位于"/data/data/项目名(包括所处包名)/databases/*.db",因此要对数据库文件进行操作需要先找到数据库文件:
# adb shell  如果有多个设备
adb devices
adb -s 设备号 shell
#cd data/data
#ls                --列出所有项目
#cd project_name   --进入所需项目名
#cd databases    
#ls                --列出现寸的数据库文件
#sqlite3 test_db   --进入所需数据库
>.databases        --产看当前数据库
>.tables           --查看当前数据库中的表
>.help             --sqlite3帮助
>.schema            --各个表的生成语句
0%