Android基础知识巩固系列 Android之五大存储

因为最近要面试,于是打算整理整理一下Android的基础知识,由于之前本人已经学习过大概的Android基础知识,这里主要讲这四大组件、五大存储、六大布局、网络请求等这些内容,其他一些等有时间再整理,今天我们讲下五大存储,话不多说。

五大存储

  1. 文件存储(通过JavaIO流的读写方法写入到文件中)

  2. SharePreferences(以键值对形式存储在xml中)

  3. SQLite(嵌入式关系型数据库,使用SQL语言)

  4. ContentProvider(作为传输数据的媒介,数据源可多样性)

  5. 网络存储(通过网络获取远程服务器的数据)

参考网站:https://www.jianshu.com/p/ea8bc4aaf057

五大存储示意图: 

一、文件存储

参考网站:https://blog.csdn.net/qq_33094497/article/details/52351186

1. 核心原理:

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

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

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

             MODE_WORLD_READABLE:表示当前文件可以被其他应用读取;(不安全,4.2被弃用)

             MODE_WORLD_WRITEABLE:表示当前文件可以被其他应用写入。(不安全,4.2被弃用)

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

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

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

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

openFileOutput()方法的第一参数用于指定文件名称,不能包含路径分隔符“/” ,如果文件不存在,Android 会自动创建它。创建的文件保存在/data/data/<package name>/files目录,如: /data/data/cn.tony.app/files/message.txt,

2.应用场景:

量大的数据、文件缓存、SD卡存储多媒体文件;

它比较适合用于一些简单的文本数据或二进制数据,如果想使用文件存储的方式来保存一些较为复杂的本文数据,就需要定义一套自己的格式规范,这样可以方便之后将数据从文件中重新解析出来,所以说它不适合存储较为复杂的文本数据。

3.代码示例:

public class MainActivity extends AppCompatActivity {
    private static final String TAG = "MainActivity";
    EditText editText;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        editText = findViewById(R.id.editText);
        String content = load();

        if (!TextUtils.isEmpty(content)) {
            editText.setText(content);
            editText.setSelection(content.length());
            Toast.makeText(getApplicationContext(), "还原成功", Toast.LENGTH_SHORT).show();
        }

    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        Log.d(TAG, "onDestroy: ");
        String str = editText.getText().toString();
        save(str);
    }

    private void save(String str) {
        FileOutputStream fos = null;
        BufferedWriter bw = null;
        try {
            fos = openFileOutput("data", MODE_PRIVATE);
            bw = new BufferedWriter(new OutputStreamWriter(fos));
            bw.write(str);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (bw != null) {
                    bw.close();
                }
                if (fos != null) {
                    fos.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }


    private String load() {
        StringBuilder content = new StringBuilder();
        FileInputStream fis = null;
        BufferedReader br = null;

        try {
            fis = openFileInput("data");
            br = new BufferedReader(new InputStreamReader(fis));
            String line = "";
            while ((line = br.readLine()) != null) {
                content.append(line);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (br != null) {
                try {
                    br.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (fis != null) {
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return content.toString();
    }
    
}

4.使用文件存储读写SD卡需要的注意事项

读写SD卡上的文件,其中读写步骤按如下进行:

1、调用Environment的getExternalStorageState()方法判断手机上是否插了sd卡,且应用程序具有读写SD卡的权限,如下代码将返回true

Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)

2、调用Environment.getExternalStorageDirectory()方法来获取外部存储器,也就是SD卡的目录,或者使用"/mnt/sdcard/"目录

3、使用IO流操作SD卡上的文件 

           必须在AndroidManifest.xml上配置读写SD卡的权限

<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

注意点:手机应该已插入SD卡,对于模拟器而言,可通过mksdcard命令来创建虚拟存储卡 

 

二、SharedPreferences

1. 核心原理:

保存基于XML文件存储的key-value键值对数据,通常用来存储一些简单的配置信息。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数据能被其他应用程序读,但不能写。(4.2被废弃)

                 Context.MODE_WORLD_WRITEABLE:  指定该SharedPreferences数据能被其他应用程序读、写(4.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编辑完成后,使用该方法提交修改

2.应用场景:

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

3.代码示例:

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

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Button button1 = findViewById(R.id.button1);
        Button button2 = findViewById(R.id.button2);
        button1.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                SharedPreferences sp1 = getSharedPreferences("preferences_data", MODE_PRIVATE); //方法1
                SharedPreferences sp2 = getPreferences(MODE_PRIVATE);   //方法2
                SharedPreferences sp3 = PreferenceManager.getDefaultSharedPreferences(getApplicationContext()); //方法3
                SharedPreferences.Editor editor = sp1.edit();
                editor.putBoolean("sex", true);
                editor.putFloat("balance", 5676.8f);
                editor.putInt("height", 180);
                editor.putLong("AA", 214159265356198L);
                editor.putString("name", "小明");
                editor.apply();
            }
        });

        button2.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                SharedPreferences sp1 = getSharedPreferences("preferences_data", MODE_PRIVATE);
                String name = sp1.getString("name", "");
                long aa = sp1.getLong("AA", -1);
                boolean b = sp1.getBoolean("sex", false);
                Log.d(TAG, name + "");
                Log.d(TAG, aa + "");
                Log.d(TAG, b + "");
            }
        });
    }
}

4.SharedPreferences与SQLite的比较

SharedPreferences对象与SQLite数据库相比,免去了创建数据库,创建表,写SQL语句等诸多操作,相对而言更加方便,简洁。但是SharedPreferences也有其自身缺陷,比如其职能存储boolean,int,float,long和String五种简单的数据类型,比如其无法进行条件查询等。所以不论SharedPreferences的数据存储操作是如何简单,它也只能是存储方式的一种补充,而无法完全替代如SQLite数据库这样的其他数据存储方式。

 

三、SQLite 

参考网站:https://www.jianshu.com/p/8e3f294e2828     https://www.cnblogs.com/ITtangtang/p/3920916.html

1.介绍

SQLite是轻量级嵌入式数据库引擎,它支持 SQL 语言,并且只利用很少的内存就有很好的性能。现在的主流移动设备像Android、iPhone等都使用SQLite作为复杂数据的存储引擎,在我们为移动设备开发应用程序时,也许就要使用到SQLite来存储我们大量的数据,所以我们就需要掌握移动设备上的SQLite开发技巧。

2.SQLiteOpenHelper类

2.1 介绍

2.2 常用方法

/** 
  *  创建数据库
  */ 
 // 1. 创建 or 打开 可读/写的数据库(通过 返回的SQLiteDatabase对象 进行操作)
 getWritableDatabase()

 // 2. 创建 or 打开 可读的数据库(通过 返回的SQLiteDatabase对象 进行操作)
 getReadableDatabase()

 // 3. 数据库第1次创建时 则会调用,即 第1次调用 getWritableDatabase() / getReadableDatabase()时调用
 // 在继承SQLiteOpenHelper类的子类中复写
 onCreate(SQLiteDatabase db) 

 // 4. 数据库升级时自动调用
 // 在继承SQLiteOpenHelper类的子类中复写
 onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)

 // 5. 关闭数据库
 close()

 /** 
  *  数据库操作(增、删、减、查)
  */ 
 // 1. 查询数据
 (Cursor) query(String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy, String limit)  
 // 查询指定的数据表返回一个带游标的数据集。
 // 各参数说明: 
 // table:表名称 
 // colums:列名称数组 
 // selection:条件子句,相当于where 
 // selectionArgs:条件语句的参数数组 
 // groupBy:分组 
 // having:分组条件 
 // orderBy:排序类 
 // limit:分页查询的限制 
 // Cursor:返回值,相当于结果集ResultSet 

 (Cursor) rawQuery(String sql, String[] selectionArgs) 
 //运行一个预置的SQL语句,返回带游标的数据集(与上面的语句最大的区别 = 防止SQL注入)

 // 2. 删除数据行  
 (int) delete(String table,String whereClause,String[] whereArgs) 
 
 // 3. 添加数据行 
 (long) insert(String table,String nullColumnHack,ContentValues values) 
 
 // 4. 更新数据行 
(int) update(String table, ContentValues values, String whereClause, String[] whereArgs) 
 
 // 5. 执行一个SQL语句,可以是一个select or 其他sql语句 
 // 即 直接使用String类型传入sql语句 & 执行
 (void) execSQL(String sql) 

3.代码示例

  • 使用步骤 = 自定义数据库子类(继承SQLiteOpenHelper类)、创建数据库 & 操作数据库(增、删、查、改)

3.1 自定义数据库子类(继承 SQLiteOpenHelper 类)

/** 
  * 创建数据库子类,继承自SQLiteOpenHelper类
  * 需 复写 onCreat()、onUpgrade()
  */ 
public class DatabaseHelper extends SQLiteOpenHelper {

    // 数据库版本号
    private static Integer Version = 1;
    // SQL语句
    private static final String CREATE_BOOK = "create table Book(" +
            "id integer primary key autoincrement," +
            "author text," +
            "price real," +
            "pages integer," +
            "name text)";

    /** 
     * 构造函数
     * 在SQLiteOpenHelper的子类中,必须有该构造函数
     */ 
    public DatabaseHelper(Context context, String name, SQLiteDatabase.CursorFactory factory,
                          int version) {
        // 参数说明
        // context:上下文对象
        // name:数据库名称
        // param:一个可选的游标工厂(通常是 Null) 
        // version:当前数据库的版本,值必须是整数并且是递增的状态

        // 必须通过super调用父类的构造函数
        super(context, name, factory, version);
    }
    
    /** 
     * 复写onCreate()
     * 调用时刻:当数据库第1次创建时调用
     * 作用:创建数据库 表 & 初始化数据
     * SQLite数据库创建支持的数据类型: 整型数据、字符串类型、日期类型、二进制
     */ 
    @Override
    public void onCreate(SQLiteDatabase db) {
              // 创建数据库1张表
              // 通过execSQL()执行SQL语句(此处创建了1个名为Book的表)
        db.execSQL(CREATE_BOOK);
              // 注:数据库实际上是没被创建 / 打开的(因该方法还没调用)
              // 直到getWritableDatabase() / getReadableDatabase() 第一次被调用时才会进行创建 / 打开 
    }

    /** 
     * 复写onUpgrade()
     * 调用时刻:当数据库升级时则自动调用(即 数据库版本 发生变化时)
     * 作用:更新数据库表结构
     * 注:创建SQLiteOpenHelper子类对象时,必须传入一个version参数,该参数 = 当前数据库版本, 若该版本高于之前版本, 就调用onUpgrade()
     */ 

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        // 参数说明: 
        // db : 数据库 
        // oldVersion : 旧版本数据库 
        // newVersion : 新版本数据库 

        // 使用 SQL的ALTER语句
        String sql = "alter table Book add pages integer";  
        db.execSQL(sql);  
    }

}

3.2 创建数据库:getWritableDatabase()、getReadableDatabase()

public class MainActivity extends AppCompatActivity {
    MyDataBaseHelper mMyDataBaseHelper;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // 步骤1:创建DatabaseHelper对象
        // 注:此时还未创建数据库
        mMyDataBaseHelper = new MyDataBaseHelper(getApplicationContext(), "BookStore.db", null, 1);

        Button button = findViewById(R.id.create_db);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mMyDataBaseHelper.getWritableDatabase(); // 创建 or 打开 可读/写的数据库
                mMyDataBaseHelper.getReadableDatabase(); // 创建 or 打开 可读的数据库
            }
        });
    }
}

注:当需操作数据库时,都必须先创建数据库对象 & 创建 / 打开数据库。

  • 对于操作 = “增、删、改(更新)”,需获得 可"读 / 写"的权限:getWitableDatabase()

  • 对于操作 = “查询”,需获得 可"读 "的权限:getReadableDatabase()

3.3 升级数据库

在onUpgrade方法中先把表都删了,再重新执行onCreate

public class MyDataBaseHelper extends SQLiteOpenHelper {
    private static final String CREATE_BOOK = "create table Book(" +
            "id integer primary key autoincrement," +
            "author text," +
            "price real," +
            "pages integer," +
            "name text)";
    private static final String CREATE_CATEGORY = "create table category(" +
            "id integer primary key autoincrement," +
            "category_name text," +
            "category_code integer)";

    private Context mContext;

    public MyDataBaseHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) {
        super(context, name, factory, version);
        mContext = context;
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        db.execSQL(CREATE_BOOK);
        db.execSQL(CREATE_CATEGORY);
        Toast.makeText(mContext, "创建数据库成功", Toast.LENGTH_SHORT).show();
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        db.execSQL("drop table if exists Book");
        db.execSQL("drop table if exists Categort");
        onCreate(db);
    }
}

实例方法传入的版本数字要比原来的大,正常+1就行

public class MainActivity extends AppCompatActivity {
    MyDataBaseHelper mMyDataBaseHelper;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mMyDataBaseHelper = new MyDataBaseHelper(getApplicationContext(), "BookStore.db", null, 2); //版本号+1

        Button button = findViewById(R.id.create_db);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
               mMyDataBaseHelper.getWritableDatabase();
            }
        });
    }
}

3.4 操作数据库(增删查改)

1. 创建 & 打开数据库 

      // a. 创建DatabaseHelper对象
      // 注:一定要传入最新的数据库版本号
      SQLiteOpenHelper dbHelper = new DatabaseHelper(SQLiteActivity.this,"test_carson",2);
      // b.创建 or 打开 可读/写的数据库
      SQLiteDatabase sqliteDatabase = dbHelper.getWritableDatabase();

  /** 
    *  操作1:插入数据 = insert()
    */ 
        // a. 创建ContentValues对象
        ContentValues values = new ContentValues();

        // b. 向该对象中插入键值对
        values.put("id", 1);
        values.put("name", "carson");
            //其中,key = 列名,value = 插入的值
            //注:ContentValues内部实现 = HashMap,区别在于:ContenValues Key只能是String类型,Value可存储基本类型数据 & String类型

        // c. 插入数据到数据库当中:insert()
        sqliteDatabase.insert("user", null, values);
                // 参数1:要操作的表名称
                // 参数2:SQl不允许一个空列,若ContentValues是空,那么这一列被明确的指明为NULL值
                // 参数3:ContentValues对象
        // 注:也可采用SQL语句插入
        String sql = "insert into user (id,name) values (1,'carson')";
        db.execSQL(sql) ;
            
  /** 
    *  操作2:修改数据 = update()
    */ 
        // a. 创建一个ContentValues对象
        ContentValues values = new ContentValues();
        values.put("name", "zhangsan");

        // b. 调用update方法修改数据库:将id=1 修改成 name = zhangsan
        sqliteDatabase.update("user", values, "id=?", new String[] { "1" });
            // 参数1:表名(String)
            // 参数2:需修改的ContentValues对象
            // 参数3:WHERE表达式(String),需数据更新的行; 若该参数为 null, 就会修改所有行;?号是占位符
            // 参数4:WHERE选择语句的参数(String[]), 逐个替换 WHERE表达式中 的“?”占位符;

            // 注:调用完upgrate()后,则会回调 数据库子类的onUpgrade()

        // 注:也可采用SQL语句修改
        String sql = "update [user] set name = 'zhangsan' where id="1";
        db.execSQL(sql);

  /** 
    *  操作3:删除数据 = delete()
    */
        // 删除 id = 1的数据
        sqliteDatabase.delete("user", "id=?", new String[]{"1"});
            // 参数1:表名(String)
            // 参数2:WHERE表达式(String),需删除数据的行; 若该参数为 null, 就会删除所有行;?号是占位符
            // 参数3:WHERE选择语句的参数(String[]), 逐个替换 WHERE表达式中 的“?”占位符;

        // 注:也可采用SQL语句修改
        String sql = "delete from user where id="1";
        db.execSQL(sql);

  /** 
    *  操作4:查询数据1 = rawQuery() 
    *  直接调用 SELECT 语句
    */
        Cursor c = db.rawQuery("select * from user where id=?",new Stirng[]{"1"}); 
        // 返回值一个 cursor 对象

        // 通过游标的方法可迭代查询结果
        if(cursor.moveToFirst()) { 
           String password = c.getString(c.getColumnIndex("password")); 
         }
        
        //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);   //返回当前行指定列的值 
        
        // 通过游标遍历1个名为user的表
        Cursor result=db.rawQuery("SELECT _id, username, password FROM user");  
         result.moveToFirst();  
         while (!result.isAfterLast()) {  
            int id=result.getInt(0);  
            String name=result.getString(1);  
            String password =result.getString(2);  
            // do something useful with these  
            result.moveToNext();  
          }  
         result.close();


     // 若查询是动态的,使用该方法会复杂。此时使用 query() 会方便很多
     // 注:无法使用SQL语句,即db.execSQL(sql);

  /** 
    *  操作4:查询数据2 = query() 
    *  直接调用 SELECT 语句
    */
        // 方法说明
        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:要操作的表
        // columns:查询的列所有名称集
        // selection:WHERE之后的条件语句,可以使用占位符
        // groupBy:指定分组的列名
        // having指定分组条件,配合groupBy使用
        // orderBy指定排序的列名
        // limit指定分页参数
        // distinct可以指定“true”或“false”表示要不要过滤重复值

        // 所有方法将返回一个Cursor对象,代表数据集的游标 

        // 具体使用
         Cursor cursor = sqliteDatabase.query("user", new String[] { "id","name" }, "id=?", new String[] { "1" }, null, null, null);
            // 参数1:(String)表名
            // 参数2:(String[])要查询的列名
            // 参数3:(String)查询条件
            // 参数4:(String[])查询条件的参数
            // 参数5:(String)对查询的结果进行分组
            // 参数6:(String)对分组的结果进行限制
            // 参数7:(String)对查询的结果进行排序
            
        // 注:无法使用SQL语句,即db.execSQL(sql);
  /** 
    *  操作5:关闭数据库 = close()
    *  注:完成数据库操作后,记得调用close()关闭数据库,从而释放数据库的连接
    */
        sqliteDatabase.close();  

  /** 
    *  操作6:删除数据库 = deleteDatabase()
    */
        // 删除 名为person的数据库  
        deleteDatabase("test.db");

demo地址:https://github.com/lipete/SQLiteDemo

 

四、ContentProvider

参考网站:https://www.jianshu.com/p/ea8bc4aaf057

1.介绍

ContentProvider即内容提供者,是Android四大组件之一

Android这个系统和其他的操作系统还不太一样,我们需要记住的是,数据在Android当中是私有的,当然这些数据包括文件数据和数据库数据 以及一些其他类型的数据。那这个时候有读者就会提出问题,难道两个程序之间就没有办法对于数据进行交换?Android这么优秀的系统不会让这种情况发生 的。解决这个问题主要靠ContentProvider。一个Content Provider类实现了一组标准的方法接口,从而能够让其他的应用保存或读取此Content Provider的各种数据类型。也就是说,一个程序可以通过实现一个Content Provider的抽象接口将自己的数据暴露出去。外界根本看不到,也不用看到这个应用暴露的数据在应用当中是如何存储的,或者是用数据库存储还是用文件 存储,还是通过网上获得,这些一切都不重要,重要的是外界可以通过这一套标准及统一的接口和程序里的数据打交道,可以读取程序的数据,也可以删除程序的数 据,当然,中间也会涉及一些权限的问题。

一个程序可以通过实现一个ContentProvider的抽象接口将自己的数据完全暴露出去,而且ContentProviders是以类似数据 库中表的方式将数据暴露,也就是说ContentProvider就像一个“数据库”。那么外界获取其提供的数据,也就应该与从数据库中获取数据的操作基 本一样,只不过是采用URI来表示外界需要访问的“数据库”。

Content Provider提供了一种多应用间数据共享的方式,比如:联系人信息可以被多个应用程序访问。

Content Provider是个实现了一组用于提供其他应用程序存取数据的标准方法的类。 应用程序可以在Content Provider中执行如下操作: 查询数据 修改数据 添加数据 删除数据

标准的Content Provider: Android提供了一些已经在系统中实现的标准Content Provider,比如联系人信息,图片库等等,你可以用这些Content Provider来访问设备上存储的联系人信息,图片等等。

2.作用

进程之间进行数据交互和共享,即跨进程通信

3.原理(Binder机制)

感兴趣可以点进去看看

4.使用方式(代码示例)

  • 由于ContentProvider不仅常用与进程之间通信,同时也适用于进程内通信
  • 所以本实例会采用ContentProvider讲解:
  1. 进程内通信
  2. 进程间通信
  • 实例说明:采用的数据源是Android中的SQLite数据库

4.1 进程内通信

步骤说明:

  1. 创建数据库类
  2. 自定义 ContentProvider 类
  3. 注册 创建的 ContentProvider 类
  4. 进程内访问 ContentProvider 的数据

步骤1:创建数据库类

public class DBHelper extends SQLiteOpenHelper {

    private static final String CREATE_STUDENT = "create table if not exists Student(" +
            "id integer primary key autoincrement," +
            "name varchar(11)," +
            "age integer)";
    private static final String CREATE_TEACHER = "create table if not exists Teacher(" +
            "id integer primary key autoincrement," +
            "name varchar(11)," +
            "age integer)";

    public DBHelper(@Nullable Context context) {
        super(context, "person.db", null, 1);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        db.execSQL(CREATE_STUDENT);
        db.execSQL(CREATE_TEACHER);
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {

    }
}

步骤2:自定义 ContentProvider 类

public class MyContentProvider extends ContentProvider {
    Context mContext;
    DBHelper dbHelper;
    SQLiteDatabase sqLiteDatabase;

    public static final String AUTOHORITY = "com.peter.myprovider";
    // 设置ContentProvider的唯一标识

    public static final int Student_Code = 1;
    public static final int Teacher_Code = 2;
    // UriMatcher类使用:在ContentProvider 中注册URI
    private static final UriMatcher mMatcher;

    static {
        mMatcher = new UriMatcher(UriMatcher.NO_MATCH);
        // 初始化
        mMatcher.addURI(AUTOHORITY, "Student", Student_Code);
        mMatcher.addURI(AUTOHORITY, "Teacher", Teacher_Code);
        // 若URI资源路径 = content://cn.scu.myprovider/user ,则返回注册码User_Code
        // 若URI资源路径 = content://cn.scu.myprovider/job ,则返回注册码Job_Code
    }

    @Override
    public boolean onCreate() {
        mContext = getContext();
        dbHelper = new DBHelper(getContext());
        sqLiteDatabase = dbHelper.getWritableDatabase();

        sqLiteDatabase.execSQL("delete from Student");
        sqLiteDatabase.execSQL("insert into Student(name,age) values(\"小明\",17)");
        sqLiteDatabase.execSQL("insert into Student(name,age) values(\"小红\",18)");

        sqLiteDatabase.execSQL("delete from Teacher");
        sqLiteDatabase.execSQL("insert into Teacher(name,age) values(\"张老师\",54)");
        sqLiteDatabase.execSQL("insert into Teacher(name,age) values(\"王老师\",47)");
        return true;
    }

    @Override
    public Uri insert(@NonNull Uri uri, ContentValues values) {
        // 根据URI匹配 URI_CODE,从而匹配ContentProvider中相应的表名
        // 该方法在最下面
        String table = getTableName(uri);
        // 向该表添加数据
        sqLiteDatabase.insert(table, null, values);
        // 当该URI的ContentProvider数据发生变化时,通知外界(即访问该ContentProvider数据的访问者)
        mContext.getContentResolver().notifyChange(uri, null);

//        // 通过ContentUris类从URL中获取ID
//        long personid = ContentUris.parseId(uri);
//        System.out.println(personid);
        return uri;
    }

    @Override
    public Cursor query(@NonNull Uri uri, String[] projection, String selection,
                        String[] selectionArgs, String sortOrder) {
        // 根据URI匹配 URI_CODE,从而匹配ContentProvider中相应的表名
        // 该方法在最下面
        String table = getTableName(uri);

//        // 通过ContentUris类从URL中获取ID
//        long personid = ContentUris.parseId(uri);
//        System.out.println(personid);

        // 查询数据
        return sqLiteDatabase.query(table,projection,selection,selectionArgs,null,null,sortOrder,null);
    }

    @Override
    public int update(@NonNull Uri uri, ContentValues values, String selection,
                      String[] selectionArgs) {
        // TODO: Implement this to handle requests to update one or more rows.
        throw new UnsupportedOperationException("Not yet implemented");
    }


    @Override
    public String getType(@NonNull Uri uri) {
        // TODO: Implement this to handle requests for the MIME type of the data
        // at the given URI.
        throw new UnsupportedOperationException("Not yet implemented");
    }


    @Override
    public int delete(@NonNull Uri uri, String selection, String[] selectionArgs) {
        // Implement this to handle requests to delete one or more rows.
        throw new UnsupportedOperationException("Not yet implemented");
    }

    /**
     * 根据URI匹配 URI_CODE,从而匹配ContentProvider中相应的表名
     */
    private String getTableName(Uri uri) {
        String tableName = null;
        switch (mMatcher.match(uri)) {
            case Student_Code:
                tableName = "Student";
                break;
            case Teacher_Code:
                tableName = "Teacher";
                break;
        }
        return tableName;
    }
}

步骤3:注册 创建的 ContentProvider类

        <provider
            android:name=".MyContentProvider"
            android:authorities="com.peter.myprovider"
            android:enabled="true"
            android:exported="true"/>

步骤4:进程内访问 ContentProvider中的数据

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Uri studentUri = Uri.parse("content://com.peter.myprovider/Student");
        Uri teacherUri = Uri.parse("content://com.peter.myprovider/Teacher");

        ContentValues values1 = new ContentValues();
        values1.put("name", "小天");
        values1.put("age", 18);

        ContentResolver resolver1 = getContentResolver();
        resolver1.insert(studentUri, values1);

        Cursor cursor1 = resolver1.query(studentUri, null, null, null, null);
        if (cursor1 != null) {
            while (cursor1.moveToNext()) {
                Log.d("AAAAAAAAAAAAAA1", cursor1.getInt(cursor1.getColumnIndex("id")) +
                        cursor1.getString(cursor1.getColumnIndex("name")) +
                        cursor1.getInt(cursor1.getColumnIndex("age")));
            }
            cursor1.close();
        }

        ContentValues values2 = new ContentValues();
        values2.put("name", "刘老师");
        values2.put("age", 38);

        ContentResolver resolver2 = getContentResolver();
        resolver2.insert(teacherUri, values2);

        Cursor cursor2 = resolver2.query(teacherUri, null, null, null, null);
        if (cursor2 != null) {
            while (cursor2.moveToNext()) {
                Log.d("AAAAAAAAAAAAAA2", cursor2.getInt(cursor2.getColumnIndex("id")) +
                        cursor2.getString(cursor2.getColumnIndex("name")) +
                        cursor2.getInt(cursor2.getColumnIndex("age")));
            }
            cursor2.close();
        }

    }
}

结果:

05-16 13:59:21.773 7400-7400/com.peterli.storagetypefour1 D/AAAAAAAAAAAAAA1: 7小明17
05-16 13:59:21.773 7400-7400/com.peterli.storagetypefour1 D/AAAAAAAAAAAAAA1: 8小红18
05-16 13:59:21.773 7400-7400/com.peterli.storagetypefour1 D/AAAAAAAAAAAAAA1: 9小天18
05-16 13:59:21.783 7400-7400/com.peterli.storagetypefour1 D/AAAAAAAAAAAAAA2: 7张老师54
05-16 13:59:21.783 7400-7400/com.peterli.storagetypefour1 D/AAAAAAAAAAAAAA2: 8王老师47
05-16 13:59:21.783 7400-7400/com.peterli.storagetypefour1 D/AAAAAAAAAAAAAA2: 9刘老师38

github地址:https://github.com/lipete/ContentProvider1

4.2 进程间通信

  • 实例说明:本文需要创建2个进程,即创建两个工程,作用如下(相当于C/S架构)

进程1(Server)

使用步骤如下:

  1. 创建数据库类
  2. 自定义 ContentProvider类
  3. 注册 创建的 ContentProvider类

前2个步骤同上例相同,此处不作过多描述,此处主要讲解步骤3

步骤3:注册 创建的 ContentProvider类

<provider 
               android:name="MyProvider"
               android:authorities="com.peterli.myprovider"

               // 声明外界进程可访问该Provider的权限(读 & 写)
               android:permission="com.peterli.PROVIDER"             
               
               // 权限可细分为读 & 写的权限
               // 外界需要声明同样的读 & 写的权限才可进行相应操作,否则会报错
               // android:readPermisson = "com.peterli.Read"
               // android:writePermisson = "com.peterli.Write"

               // 设置此provider是否可以被其他进程使用
               android:exported="true"
                
  />

// 声明本应用 可允许通信的权限
    <permission android:name="com.peterli.PROVIDER" android:protectionLevel="normal"/>
    // 细分读 & 写权限如下,但本Demo直接采用全权限
    // <permission android:name="com.peterli.Write" android:protectionLevel="normal"/>
    // <permission android:name="com.peterli.Read" android:protectionLevel="normal"/>

github地址:https://github.com/lipete/ContentProvider2

进程2(Client)

步骤1:声明可访问的权限

    // 声明本应用可允许通信的权限(全权限)
    <uses-permission android:name="com.peterli.PROVIDER"/>

    // 细分读 & 写权限如下,但本Demo直接采用全权限
    // <uses-permission android:name="com.peterli.Read"/>
    //  <uses-permission android:name="com.peterli.Write"/>
    
// 注:声明的权限必须与进程1中设置的权限对应

步骤2:访问 ContentProvider的类

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Uri uri1 = Uri.parse("content://com.peter.provider/Student");
        Uri uri2 = Uri.parse("content://com.peter.provider/Teacher");

        ContentValues values1 = new ContentValues();
        values1.put("name", "小黑");
        values1.put("age", 18);
        ContentResolver resolver1 = getContentResolver();
        resolver1.insert(uri1, values1);

        ContentValues values2 = new ContentValues();
        values2.put("name", "黄老师");
        values2.put("age", 78);
        ContentResolver resolver2 = getContentResolver();
        resolver2.insert(uri2, values2);

        Cursor cursor1 = resolver1.query(uri1, null, null, null, null);
        if (cursor1 != null) {
            while (cursor1.moveToNext()) {
                Log.d("AAAAAAAAAAA1", cursor1.getInt(cursor1.getColumnIndex("id")) +
                        cursor1.getString(cursor1.getColumnIndex("name")) +
                        cursor1.getInt(cursor1.getColumnIndex("age")));
            }
            cursor1.close();
        }

        Cursor cursor2 = resolver2.query(uri2, null, null, null, null);
        if (cursor2 != null) {
            while (cursor2.moveToNext()) {
                Log.d("AAAAAAAAAAA2", cursor2.getInt(cursor2.getColumnIndex("id")) +
                        cursor2.getString(cursor2.getColumnIndex("name")) +
                        cursor2.getInt(cursor2.getColumnIndex("age")));
            }
            cursor2.close();
        }
    }
}

结果

05-16 14:05:30.483 8080-8080/com.peterli.client D/AAAAAAAAAAA1: 1小明17
05-16 14:05:30.483 8080-8080/com.peterli.client D/AAAAAAAAAAA1: 2小红18
05-16 14:05:30.483 8080-8080/com.peterli.client D/AAAAAAAAAAA1: 3小黑18
05-16 14:05:30.483 8080-8080/com.peterli.client D/AAAAAAAAAAA2: 1张老师54
05-16 14:05:30.483 8080-8080/com.peterli.client D/AAAAAAAAAAA2: 2王老师47
05-16 14:05:30.483 8080-8080/com.peterli.client D/AAAAAAAAAAA2: 3黄老师78

github地址:https://github.com/lipete/Client

 

五、网络存储

参考网站:http://www.cnblogs.com/pxsbest/p/5068482.html

前面介绍的几种存储都是将数据存储在本地设备上,除此之外,还有一种存储(获取)数据的方式,通过网络来实现数据的存储和获取。

我们可以调用WebService返回的数据或是解析HTTP协议实现网络数据交互。

具体需要熟悉java.net.*,Android.net.*这两个包的内容,在这就不赘述了,请大家参阅相关文档。

下面是一个通过地区名称查询该地区的天气预报,以POST发送的方式发送请求到webservicex.net站点,访问WebService.webservicex.net站点上提供查询天气预报的服务。

代码如下:

public class MyAndroidWeatherActivity extends Activity { 
    //定义需要获取的内容来源地址 
    private static final String SERVER_URL =  
        "http://www.webservicex.net/WeatherForecast.asmx/GetWeatherByPlaceName";  
 
    /** Called when the activity is first created. */ 
    @Override 
    public void onCreate(Bundle savedInstanceState) { 
        super.onCreate(savedInstanceState); 
        setContentView(R.layout.main); 
 
        HttpPost request = new HttpPost(SERVER_URL); //根据内容来源地址创建一个Http请求 
        // 添加一个变量  
        List<NameValuePair> params = new ArrayList<NameValuePair>();  
        // 设置一个地区名称 
        params.add(new BasicNameValuePair("PlaceName", "NewYork"));  //添加必须的参数 
 
        try {  
            //设置参数的编码 
            request.setEntity(new UrlEncodedFormEntity(params, HTTP.UTF_8));  
            //发送请求并获取反馈 
            HttpResponse httpResponse = new DefaultHttpClient().execute(request); 
 
            // 解析返回的内容 
            if(httpResponse.getStatusLine().getStatusCode() != 404){  
               String result = EntityUtils.toString(httpResponse.getEntity());  
               System.out.println(result); 
            } 
        } catch (Exception e) { 
            e.printStackTrace(); 
       }  
    }

别忘记了在配置文件中设置访问网络权限:

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

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值