Android实习生 —— 数据存储与共享

96
博儿丶
2017.04.01 17:54* 字数 3325

目录

前言
一、使用SharedPreferences存储数据
    1、适用范围
    2、核心原理
    3、app内部实现数据存储(Demo)
    4、共享其他应用的SharedPreferences
    5、关于android:sharedUserId
    6、SharedPreferences总结
二、文件存储数据
    1、功能介绍
    2、存储方式
    3、使用内部存储(Demo)
    4、使用外部存储(Demo)
三、SQLite数据库存储数据
    1、简介及特点
    2、实现原理
    3、通过SQLiteDatabase创建(Demo)
    4、通过继承SQLiteOpenHelper类创建
    5、两种方式的联系
四、使用ContentProvider存储数据
    与SQLite数据库联系
    详见:[Android实习生 —— 四大组件之ContentProvider]   
五、网络存储数据
【附录】
  Demo

前言

Android提供了5种方式来让用户保存持久化应用程序数据。
**
① 使用SharedPreferences存储数据 
② 文件存储数据
③ SQLite数据库存储数据
④ 使用ContentProvider存储数据
⑤ 网络存储数据 **

我们可以根据需求选择对应的方式。文章根据相关Demo讲述各种方式的用法及优缺点说明,在文章末尾附录会有相关Demo的下载 。

通过以上方式还可以实现数据的在不同app间的数据共享。

一、使用SharedPreferences存储数据

1、适用范围

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

2、核心原理
  • 保存基于XML文件存储的key-value键值对数据。

  • 通过DDMS的File Explorer面板,展开文件浏览树,很明显SharedPreferences数据总是存储在/data/data/<package name>/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数据能被其他应用程序读,写。
//以上三种写法均已过时,可以直接用数字代替,
//Context.MODE_PRIVATE = 0
//Context.MODE_WORLD_READABLE = 1
//Context.MODE_WORLD_WRITEABLE = 2
  • 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编辑完成后,使用该方法提交修改
3、app内部实现数据存储(Demo)
  • 通过点击“保存用户名”,对登陆成功的用户进行用户名键值对的保存,让用户下次启动app时自动填充用户名。
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    private EditText userName,userPass;
    private CheckBox checkBox;
    private Button ok,cancel;
    private SharedPreferences pref;
    private SharedPreferences.Editor editor;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //初始化SharedPreferences 及相关组建。
        init();


        //3、取出userInfo中的数据。
        String name00=pref.getString("userName",null);
        if (name00==null) {
            checkBox.setChecked(false);
        }else {
            checkBox.setChecked(true);
            //4、将取到的用户名赋给用户名编辑框。
            userName.setText(name00);
        }

    }

    private void init() {
        userName = (EditText) findViewById(R.id.userName);
        userPass = (EditText) findViewById(R.id.userPass);
        checkBox = (CheckBox) findViewById(R.id.check);
        ok = (Button) findViewById(R.id.join_btn);
        cancel = (Button) findViewById(R.id.cancel_btn);
        ok.setOnClickListener(this);
        cancel.setOnClickListener(this);
        //1、获取SharedPreferences对象,并把文件名设为"userInfo"。
        pref =getSharedPreferences("userInfo", MODE_PRIVATE);
        //2、获取SharedPreferences内部接口Editor用来编辑userInfo。
        editor = pref.edit();
    }


    @Override
    public void onClick(View v) {
        //2.1:获取用户输入的用户名密码信息。
        String name = userName.getText().toString();
        String pass = userPass.getText().toString();
        switch (v.getId()) {
            case R.id.join_btn:
                if ("admin".equals(name)&&"123456".equals(pass)){
                    if(checkBox.isChecked()){
                        //2.2.1:判断成功登入并对"保存用户名"打勾之后,
                        //将用户名的键值对添加到文件名为"userInfo"文件中并提交。
                        editor.putString("userName",name);
                        editor.commit();
                    }else{
                        //2.2.2若没打勾,则清空并提交。
                        editor.remove("userName");
                        editor.commit();
                    }
                    Toast.makeText(this,"登陆成功",Toast.LENGTH_SHORT).show();
                }else{
                    Toast.makeText(this,"登陆失败",Toast.LENGTH_SHORT).show();
                }
                    break;
            case R.id.cancel_btn:
                userName.setText(null);
                userPass.setText(null);
                break;
        }
    }
  • 效果
    登入成功


    登入成功

    第二次打开自动填充用户名。


    第二次打开app

    【在DDMS中依次打开data/data/<包>/shared_prefs,可以看到此文件内容】
    data/data/<包>/shared_prefs

    文件内容
4、共享其他应用的SharedPreferences
  • 在创建SharedPreferences时,指定MODE_WORLD_READABLE模式,表明该SharedPreferences数据可以被其他程序读取。
SharedPreferences  pref =getSharedPreferences("userInfo", MODE_WORLD_READABLE);
  • 创建其他应用程序对应的Context上下文引用:
Context otherAppContent = null;
        try {
            otherAppContent = createPackageContext("com.bb.sharedpr",CONTEXT_IGNORE_SECURITY);
//com.bb.sharedpr为我们要调用数据的包名
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        }
  • 使用其他程序的Context获取对应的SharedPreferences
SharedPreferences read = otherAppContent.getSharedPreferences("userInfo",MODE_WORLD_READABLE);
  • 如果是写入数据,使用Editor接口即可,所有其他操作均和前面一致。
5、关于android:sharedUserId
  • 通常,不同的APK会具有不同的userId,因此运行时属于不同的进程中,而不同进程中的资源是不共享的,才保障了程序运行的稳定。然后在有些时候,我们自己开发了多个APK并且需要他们之间互相共享资源,那么就需要通过设置shareUserId来实现这一目的。

  • 通过SharedUserId,拥有同一个User id的多个APK可以配置成运行在同一个进程中.所以默认就是可以通过获取上下文来互相访问任意数据. 也可以配置成运行成不同的进程, 同时可以访问其他APK的数据目录下的数据库和文件.就像访问本程序的数据一样。

  • 而上面的两个工程中并没有对Android:sharedUserId属性进行设置。这个属性是在查资料时看到的:意思是说,在manifest.xml里面将两个应用程序的android:sharedUserId属性设为相同的就可以对SharedPreferences文件进行写。(此处并没有验证)

6、SharedPreferences总结
  • 优点
    SharedPreferences对象与SQLite数据库相比显得格外轻量级,免去了创建数据库,创建表,写SQL语句等诸多操作,相对而言更加方便,简洁。
  • 缺点
    1、其职能存储boolean,int,float,long和String五种简单的数据类型。
    2、无法进行条件查询等。

【所以不论SharedPreferences的数据存储操作是如何简单,它也只能是存储方式的一种补充,而无法完全替代如SQLite数据库这样的其他数据存储方式。】

二、文件存储数据

1、功能介绍

Android文件系统和其他平台上的类似,使用File APIs可以读写文件。这部分内容需要你已经了解了Linux文件系统的基础,并且也了解了java.io包中的标准文件输入输出APIs。

2、存储方式

所有的Android设备都有两块文件存储区域:内部和外部存储。

  • 内部存储:指设备自带的非易失性存储器。
    • 永远可用,因为不可以拆卸。
    • 文件默认情况下只对你的app可用,是私有的,无论是用户或者是其他app都不能共享访问你的数据。
    • 当用户卸载你的app时,系统会自动移除app在内部存储上的所有文件。
  • 外部存储:指可拆卸的存储介质,如卫星电视SD卡。
    • 不一定一直可以访问,因为用户可以拆卸外部存储设备。
    • 文件是全局可读的,没有访问限制,不受你的控制。可以和其他app共享数据,用户使用电脑也可以访问在外部存储中的文件。
    • 当用户卸载你的app时,只有当你把文件存储在以 getExternalFilesDir().获得的路径下时,系统才会帮你自动移除。
3、使用内部存储(Demo)

Context提供了两个方法来打开数据文件里的文件IO流

FileInputStream openFileInput(String name); 
FileOutputStream openFileInput(String name , int mode);
//name参数: 用于指定文件名称,不能包含路径分隔符“/” ,
//如果文件不存在,Android 会自动创建它。

这两个方法第一个参数 用于指定文件名,第二个参数指定打开文件的模式。

MODE_PRIVATE:为默认操作模式,代表该文件是私有数据,只能被应用本身访问,在该模式下,
              写入的内容会覆盖原文件的内容,如果想把新写入的内容追加到原文件中。可以使用Context.MODE_APPEND
MODE_APPEND:模式会检查文件是否存在,存在就往文件追加内容,否则就创建新文件。
MODE_WORLD_READABLE:表示当前文件可以被其他应用读取。
MODE_WORLD_WRITEABLE:表示当前文件可以被其他应用写入。
模式可以连用 比如可读可写 就写成:
MODE_WORLD_READABLE+MODE_WORLD_WRITEABLE

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

getDir(String name , int mode):在应用程序的数据文件夹下获取或者创建name对应的子目录
getFilesDir():获取该应用程序的数据文件夹得绝对路径
fileList():返回该应用数据文件夹的全部文件 

我们将实现的Demo为将输入的文字写入文件,并读取出来。

  • 第一步:创建和写入一个内部存储的私有文件:
public void WriteFiles(String content){
         try {
          //①调用Context的openFileOutput()函数,填入文件名和操作模式,它会返回一个FileOutputStream对象。
            FileOutputStream fos = openFileOutput("a.txt",
            MODE_PRIVATE);
          //②通过FileOutputStream对象的write()函数写入数据。
             fos.write(content.getBytes());
          //③FileOutputStream对象的close ()函数关闭流。
             fos.close();
        } catch (FileNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
         
    }
  • 第二步:读取一个内部存储的私有文件:
public String readFiles(){
        String content = null;
         try {
            //① 调用openFileInput( ),参数中填入文件名,会返回一个FileInputStream对象。
            FileInputStream fis= openFileInput("a.txt");
            StringBuilder sb = new StringBuilder();
            byte [] buffer =  new byte[1024];
            int len = 0;
            //② 使用流对象的 read()方法读取字节
            while ((len=fis.read(buffer))!=-1) {
                sb.append(new String(buffer, 0, len));
            }
            content =sb.toString();
            //③ 调用流的close()方法关闭流
            fis.close();
        } catch (FileNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return content;
    }

第三步:MainActivity中实现流程:

protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        edt = (EditText) findViewById(R.id.editText1);
        but = (Button) findViewById(R.id.write);
        contentvalue = (TextView) findViewById(R.id.contentvalue);
        but.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                WriteFiles(edt.getText().toString());
                contentvalue.setText(readFiles());
            }
        });
    }

效果

  • app效果
  • 文件路径
  • 文件

【注意】保存内存缓存文件
有时候我们只想缓存一些数据而不是持久化保存,可以使用getCacheDir()去创建或打开一个文件,文件的存储目录( /data/data/包名/cache )是一个应用专门来保存临时缓存文件的内存目录。

File file = this.getCacheDir();
Log.i("info", file.toString();

当设备的内部存储空间比较低的时候,Android可能会删除这些缓存文件来恢复空间,但是你不应该依赖系统来回收,要自己维护这些缓存文件把它们的大小限制在一个合理的范围内,比如1MB.当你卸载应用的时候这些缓存文件也会被移除。

4、使用外部存储(Demo)

因为内部存储容量限制,有时候需要存储数据比较大的时候需要用到外部存储,使用外部存储分为以下几个步骤:

  • 第一步:添加外部存储访问限权
<!-- 在AndroidManifest.xml中加入访问SDCard的权限--> 
<!-- 在SDCard中创建与删除文件权限 --> 
    <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/> 
   <!-- 往SDCard写入数据权限 --> 
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
  • 第二步:检测外部存储的可用性
//获取外存储的状态
String state = Environment.getExternalStorageState();
if (Environment.MEDIA_MOUNTED.equals(state)) {
    // 可读可写
    mExternalStorageAvailable = mExternalStorageWriteable = true;
} else if (Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
    // 可读
} else {
    // 可能有很多其他的状态,但是我们只需要知道,不能读也不能写  
}
  • 第三步:读写数据
public class MainActivity extends Activity {
    private TextView textView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        textView= (TextView) findViewById(R.id.tv); if(Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)){
            File sdCardDir = Environment.getExternalStorageDirectory();//获取SDCard目录  "/sdcard"
            File saveFile = new File(sdCardDir,"a.txt");
            //写数据
            try {
                FileOutputStream fos= new FileOutputStream(saveFile);
                fos.write("bobobo".getBytes());
                fos.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
            //读数据
            try {
                FileInputStream fis= new FileInputStream(saveFile);
                int len =0;
                byte[] buf = new byte[1024];
                StringBuffer sb = new StringBuffer();
                while((len=fis.read(buf))!=-1){
                    sb.append(new String(buf, 0, len));
                }
                textView.setText(sb.toString());
                fis.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}
  • 效果


    效果

    外部路径

三、SQLite数据库存储数据

1、简介及特点
  • SQLite是轻量级嵌入式数据库引擎,且只利用很少的内存就有很好的性能。

  • 在我们为移动设备开发应用程序时,使用SQLite作为复杂数据、大量数据的存储引擎。

  • SQLite它是一个独立的,无需服务进程,无需安装和管理配置,支持事务处理,可以使用SQL语言的数据库。

  • 支持多种开发语言,C,PHP,Perl,Java,ASP.NET,Python。

  • 在Content Provider 技术中就是使用SQLite数据库来操作数据的。

2、实现原理
  • 直接通过SQLiteDatabase对象来创建一个数据库。

  • 或者继承SQLiteOpenHelper类封装创建和更新数据库使用的逻辑。

它们都会在ddns 的file explorer 中的data/data/<包>/databases中创建这个数据库文件。

3、通过SQLiteDatabase创建(Demo)
  • 第一步:创建数据库并插入数据
private SQLiteDatabase db;
public void init() {
        db = openOrCreateDatabase("user.db", MODE_PRIVATE, null);
        db.execSQL("create table if not exists usertb (_id integer primary key autoincrement, name text not null , age integer not null , sex text not null )");
        db.execSQL("insert into usertb(name,sex,age) values('张三','女',18)");
        db.execSQL("insert into usertb(name,sex,age) values('李四','男',19)");
        db.execSQL("insert into usertb(name,sex,age) values('王五','女',22)");
        //查询数据库并展示
        query(findViewById(R.id.query));
    }

query代码【关于查询代码的详情在本小节末尾会有详细说明】

public void query(View view) {
        tv_id.setText("");
        tv_name.setText("");
        tv_sex.setText("");
        tv_age.setText("");
        Cursor cursor = db.rawQuery("select * from usertb", null);
        if (cursor != null) {
            while (cursor.moveToNext()) {
                tv_id.append("\n" + cursor.getString(cursor.getColumnIndex("_id")));
                tv_name.append("\n" + cursor.getString(cursor.getColumnIndex("name")));
                tv_sex.append("\n" + cursor.getString(cursor.getColumnIndex("sex")));
                tv_age.append("\n" + cursor.getString(cursor.getColumnIndex("age")));
            }
            cursor.close();
        }
//        db.close();不要关闭,不然单独调用查询操作会空指针
    }

效果:


效果

文件路径

pc中使用Navicat可以操作数据表
  • 第二步:实现插入数据的操作(两种方式任选其一)
    • 使用insert方法
public void add(View view) {
       ContentValues cv = new ContentValues();//实例化一个ContentValues用来装载待插入的数据
        cv.put("name","新来的");
        cv.put("sex","女");
        cv.put("age","18");
        db.insert("usertb",null,cv);//执行插入操作
//      使用直接执行语句添加 
//      db.execSQL("insert into usertb(name,sex,age) values('新来的','女',18)");
        query(findViewById(R.id.query));
    }
  • 使用execSQL方式来实现
db.execSQL("insert into usertb(name,sex,age) values('新来的','女',18)");
  • 效果


    添加效果
  • 第三步:实现删除数据的操作
    同样有2种方式可以实现

String whereClause = "name=?";//删除的条件
String[] whereArgs = {"新来的"};//删除的条件参数
db.delete("user",whereClause,whereArgs);//执行删除

使用execSQL方式的实现

String sql = "delete from usertb where name='新来的'";//删除操作的SQL语句
db.execSQL(sql);//执行删除操作
  • 第四步:实现修改数据的操作(将张三改成张三三)
    同上,仍是2种方式
ContentValues cv = new ContentValues();//实例化ContentValues
cv.put("name","张三三");//添加要更改的字段及内容
String whereClause = "name=?";//修改条件
String[] whereArgs = {"张三"};//修改条件的参数
db.update("usertb",cv,whereClause,whereArgs);//执行修改

使用execSQL方式的实现

String sql = "update usertb set name = '张三三' where username='张三'";//修改的SQL语句
db.execSQL(sql);//执行修改

【关于查询操作】
查询操作相对于上面的几种操作要复杂些,因为我们经常要面对着各种各样的查询条件,所以系统也考虑到这种复杂性,为我们提供了较为丰富的查询形式:

db.rawQuery(String sql, String[] selectionArgs);  
db.query(String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy);  
db.query(String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy, String limit);  
db.query(String distinct, String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy, String limit);

各参数说明:

table:表名称
colums:表示要查询的列所有名称集
selection:表示WHERE之后的条件语句,可以使用占位符
selectionArgs:条件语句的参数数组
groupBy:指定分组的列名
having:指定分组条件,配合groupBy使用
orderBy:y指定排序的列名
limit:指定分页参数
distinct:指定“true”或“false”表示要不要过滤重复值
Cursor:返回值,相当于结果集ResultSet

最后,他们同时返回一个Cursor对象,代表数据集的游标,有点类似于JavaSE中的ResultSet。下面是Cursor对象的常用方法:

c.move(int offset); //以当前位置为参考,移动到指定行  
c.moveToFirst();    //移动到第一行  
c.moveToLast();     //移动到最后一行  
c.moveToPosition(int position); //移动到指定行  
c.moveToPrevious(); //移动到前一行  
c.moveToNext();     //移动到下一行  
c.isFirst();        //是否指向第一条  
c.isLast();     //是否指向最后一条  
c.isBeforeFirst();  //是否指向第一条之前  
c.isAfterLast();    //是否指向最后一条之后  
c.isNull(int columnIndex);  //指定列是否为空(列基数为0)  
c.isClosed();       //游标是否已关闭  
c.getCount();       //总数据项数  
c.getPosition();    //返回当前游标所指向的行数  
c.getColumnIndex(String columnName);//返回某列名对应的列索引值  
c.getString(int columnIndex);   //返回当前行指定列的值

举例

db.update("stutb", values, "_id>?", new String[]{"3"});//把id>3的性别更新成"女"
db.delete("stutb", "name like ?", new String[]{"%丰%"});//删掉名字中带有"丰"的记录
//使用游标类 进行查询
Cursor c = db.query("stutb", null, "_id>?", new String[]{"0"}, null, null, "_id");
4、通过继承SQLiteOpenHelper类创建

Android 提供了 SQLiteOpenHelper,其是SQLiteDatabase的一个帮助类,用来管理数据库的创建和版本的更新。你只要继承 SQLiteOpenHelper 类根据开发应用程序的需要,封装创建和更新数据库使用的逻辑就行了。

  • 第一步:写一个子类继承SQLiteOpenHelper并复写三个方法
       public class DatabaseHelper extends SQLiteOpenHelper {
       /**
       * @param context  上下文环境(例如,一个 Activity)
       * @param name   数据库名字
       * @param factory  一个可选的游标工厂(通常是 Null)
       * @param version  数据库模型版本的整数
       * 
       * 会调用父类 SQLiteOpenHelper的构造函数
       */ 
      //    public DatabaseHelper(Context context, String name,    CursorFactory factory, int version) {
       //        super(context, name, factory, version);
       //    }

  //这里我们直接定义数据库名字根版本号
 private static final String DATABASE_NAME = "stu.db";
     private static final int VERSION = 1;
     private static final String TABLE_NAME = "stutb";

     // 步骤2:重载构造方法
     public DatabaseHelper(Context context) {
         super(context, DATABASE_NAME, null, VERSION);
     }
    /**
     *  在数据库第一次创建的时候会调用这个方法
     *  
     *根据需要对传入的SQLiteDatabase 对象填充表和初始化数据。
     */
    //在这里进行建表
       @Override
    public void onCreate(SQLiteDatabase db) {

        db.execSQL("create table if not exists stutb(_id integer primary key autoincrement,name text not null,sex text not null,age integer not null)");
        db.execSQL("insert into stutb(name,sex,age)values('张三','男',18)");
        db.execSQL("insert into stutb(name,sex,age)values('张四','女',20)");

    }

    /**
     * 当数据库需要修改的时候(两个数据库版本不同),Android系统会主动的调用这个方法。
     * 一般我们在这个方法里边删除数据库表,并建立新的数据库表.
     */
    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        //三个参数,一个 SQLiteDatabase 对象,一个旧的版本号和一个新的版本号

    }

    @Override
    public void onOpen(SQLiteDatabase db) {
        // 每次成功打开数据库后首先被执行
        super.onOpen(db);
    }
}
  • 第二步:继承SQLiteOpenHelper之后就拥有了以下两个方法
    getReadableDatabase()  创建或者打开一个查询数据库
    getWritableDatabase() 创建或者打开一个可写数据库
DatabaseHelper database = new DatabaseHelper(MainActivity.this);//传入一个上下文参数
SQLiteDatabase db = null;
db = database.getWritableDatabase();
//上面这段代码会返回一个 SQLiteDatabase 类的实例,使用这个对象,你就可以查询或者修改数据库。
getWritableDatabase()和getReadableDatabase()方法都可以获取一个用于操作数据库的SQLiteDatabase实例。
//  其中getReadableDatabase()方法则是先以读写方式打开数据库,如果数据库的磁盘空间满了,就会打开失败,当打开失败后
//  会继续尝试以只读方式打开数据库。如果该问题成功解决,则只读数据库对象就会关闭,然后返回一个可读写的数据库对象。
//  getWritableDatabase() 方法以读写方式打开数据库,一旦数据库的磁盘空间满了,数据库就只能读而不能写,
//  使用的是getWritableDatabase() 方法就会出错。 
  • 第三步:实现查询代码
Cursor c = db.rawQuery("select * from stutb", null);
        if (c!=null) {
            String [] cols = c.getColumnNames();
            while (c.moveToNext()) {
                for (String ColumnName : cols) {
                    Log.i("info ", ColumnName+":"+c.getString(c.getColumnIndex(ColumnName)));
                }
            }
            c.close();
        }
        db.close();
  • 效果(命令行打印)


    命令行打印结果
5、两种方式的联系
  • Context.openOrCreateDatabase 与 SQLiteDatabase.openOrCreateDatabase本质上完成的功能都一样,Context.openOrCreateDatabase最终是需要调用 SQLiteDatabase.openOrCreateDatabase来完成数据库的创建的。 也就是说, SQLiteDatabase类是android上对sqlite的最底层的封装,几乎所有的对数据库的操作最终都通过这个类来实现。

  • 对数据表的操作方式不同,SQLiteOpenHelper是SQLiteDatabase的帮助类。要如你的代码执行,你需要通过类似SQLiteOpenHelper的getWritableDatabase方法获取到SQLiteDatabase实例才可以。创建表直接可以在SQLiteOpenHelper的onCreate方法中做,那里通过参数你可以得到一个SQLiteDatabase的实例。

四、使用ContentProvider存储数据

与SQLite数据库联系

1.SQLiteOpenHelper是将对数据库和表的创建、插入、更新、删除操作进行了简单的封装;

2.而ContentProvider可以说是一个对外的接口,除了可以实现对SQLiteOpenHelper的封装,还可以实现对文件操作、图片操作、对象操作等实现封装;

3.在多线程中使用SQLiteOpenHelper要考虑线程同步问题,而如果使用ContentProvider的话基本不用考虑;

4.使用ContentProvider存储数据可以实现不同app之间的数据共享。

详见:
Android实习生 —— 四大组件之ContentProvider

五、网络存储数据

一、网络保存数据介绍

可以使用网络来保存数据,在需要的时候从网络上获取数据,进而显示在App中。用网络保存数据的方法有很多种,对于不同的网络数据采用不同的上传与获取方法。

本文利用LeanCloud来进行网络数据的存储。

LeanCloud是一种简单高效的数据和文件存储服务。感兴趣的可以查看网址:https://leancloud.cn/。 的例子。

二、使用方法

1、上传数据

AVObject personObject = new AVObject(TABLENAME);
        personObject.put(NAME, person.name);
        personObject.put(AGE, person.age);
        personObject.put(INFO, person.info);
        personObject.saveInBackground(new SaveCallback() {
            @Override
            public void done(AVException e) {
                if (e == null) {
                    Log.v(TAG, "put data success!");
                } else {
                    Log.v(TAG, "put data failed!error:" + e.getMessage());
                }
            }
        });

2、读取数据

AVQuery<AVObject> avQuery = new AVQuery<>(TABLENAME);
        avQuery.findInBackground(new FindCallback<AVObject>() {
            @Override
            public void done(List<AVObject> list, AVException e) {
                if (e == null) {
                    Log.v(TAG, "get data success!");
                    String message = "";
                    //倒着遍历后四条上传的数据
                    for (int i = list.size()-1; i >=list.size()-4; i--) {
                        String name = list.get(i).getString(NAME);
                        int age = list.get(i).getInt(AGE);
                        String info = list.get(i).getString(INFO);

                        message += "name:" + name + ",age:" + age + ",info:" + info + ".\n";
                    }
                    textView.setText(message);
                }
            }
        });
三、小案例

1、导入jar包


导入jar包

2、添加NetworkDBManager类

package com.zhangmiao.datastoragedemo;

import android.util.Log;
import android.widget.TextView;

import com.avos.avoscloud.AVException;
import com.avos.avoscloud.AVObject;
import com.avos.avoscloud.AVQuery;
import com.avos.avoscloud.FindCallback;
import com.avos.avoscloud.SaveCallback;

import java.util.List;

/**
 * Created by zhangmiao on 2016/12/22.
 */
public class NetworkDBManager {

    private static final String TAG = "NetworkDBManager";

    private final static String TABLENAME = "person";
    private final static String NAME = "name";
    private final static String AGE = "age";
    private final static String INFO = "info";

    public void putData(Person person) {
        AVObject personObject = new AVObject(TABLENAME);
        personObject.put(NAME, person.name);
        personObject.put(AGE, person.age);
        personObject.put(INFO, person.info);
        personObject.saveInBackground(new SaveCallback() {
            @Override
            public void done(AVException e) {
                if (e == null) {
                    Log.v(TAG, "put data success!");
                } else {
                    Log.v(TAG, "put data failed!error:" + e.getMessage());
                }
            }
        });
    }

    public void getData(final TextView textView) {
        AVQuery<AVObject> avQuery = new AVQuery<>(TABLENAME);
        avQuery.findInBackground(new FindCallback<AVObject>() {
            @Override
            public void done(List<AVObject> list, AVException e) {
                if (e == null) {
                    Log.v(TAG, "get data success!");
                    String message = "";
                     ////倒着遍历后四条上传的数据
                    for (int i = list.size()-1; i >=list.size()-4; i--) {
                        String name = list.get(i).getString(NAME);
                        int age = list.get(i).getInt(AGE);
                        String info = list.get(i).getString(INFO);

                        message += "name:" + name + ",age:" + age + ",info:" + info + ".\n";
                    }
                    textView.setText(message);
                }
            }
        });
    }
}

3、修改AndroidManifest.xml文件

  <uses-permission android:name="android.permission.INTERNET" />
  <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>

4、MainActivity

public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    private NetworkDBManager mNetworkDBManager;
    private TextView mTableInfo;
    private EditText et1,et2,et3;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        Log.v("MainActivity", "onCreate");
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        AVOSCloud.initialize(this, "yMNUazdBt872mNtC9aSakjYy-gzGzoHsz", "d4vw3VYdMCjLpsXRhHTBRutC");

        mNetworkDBManager = new NetworkDBManager();

        et1= (EditText) findViewById(R.id.name);
        et2= (EditText) findViewById(R.id.age);
        et3= (EditText) findViewById(R.id.sex);

        Button networkGet = (Button) findViewById(R.id.network_get);
        Button networkPut = (Button) findViewById(R.id.network_put);

        mTableInfo = (TextView) findViewById(R.id.table_info);
        
        networkGet.setOnClickListener(this);
        networkPut.setOnClickListener(this);
        
    }

    
    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.network_put:
                Person person3 = new Person(et1.getText().toString(), Integer.parseInt(et2.getText().toString()), et3.getText().toString());
                mNetworkDBManager.putData(person3);
                Toast.makeText(this,"上传成功",Toast.LENGTH_SHORT).show();
                break;
            case R.id.network_get:
                mNetworkDBManager.getData(mTableInfo);
                break;
            default:
                Log.v("MainActivity", "default");
                break;
        }
    }
}

【附录】

Demo


整理作者:汪博
少壮不努力,老大徒悲伤。
本文为Android学习规划打造,如有不好的地方请多多指教。

Android复习
Web note ad 1