《移动互联网技术》 第七章 数据存取: 掌握File、SharePreferences、SQLite和ContentProvider四种数据存取方式

在这里插入图片描述

🌷🍁 博主 libin9iOak带您 Go to New World.✨🍁
🦄 个人主页——libin9iOak的博客🎐
🐳 《面试题大全》 文章图文并茂🦕生动形象🦖简单易学!欢迎大家来踩踩~🌺
🌊 《IDEA开发秘籍》学会IDEA常用操作,工作效率翻倍~💐
🪁🍁 希望本文能够给您带来一定的帮助🌸文章粗浅,敬请批评指正!🍁🐥

《移动互联网技术》课程简介

《移动互联网技术》课程是软件工程、电子信息等专业的专业课,主要介绍移动互联网系统及应用开发技术。课程内容主要包括移动互联网概述、无线网络技术、无线定位技术、Android应用开发和移动应用项目实践等五个部分。移动互联网概述主要介绍移动互联网的概况和发展,以及移动计算的特点。无线网络技术部分主要介绍移动通信网络(包括2G/3G/4G/5G技术)、无线传感器网络、Ad hoc网络、各种移动通信协议,以及移动IP技术。无线定位技术部分主要介绍无线定位的基本原理、定位方法、定位业务、数据采集等相关技术。Android应用开发部分主要介绍移动应用的开发环境、应用开发框架和各种功能组件以及常用的开发工具。移动应用项目实践部分主要介绍移动应用开发过程、移动应用客户端开发、以及应用开发实例。
课程的教学培养目标如下:
1.培养学生综合运用多门课程知识以解决工程领域问题的能力,能够理解各种移动通信方法,完成移动定位算法的设计。
2.培养学生移动应用编程能力,能够编写Andorid应用的主要功能模块,并掌握移动应用的开发流程。
3. 培养工程实践能力和创新能力。
 通过本课程的学习应达到以下目的:
1.掌握移动互联网的基本概念和原理;
2.掌握移动应用系统的设计原则;
3.掌握Android应用软件的基本编程方法;
4.能正确使用常用的移动应用开发工具和测试工具。

第七章 数据存取

本章小结:

1**、本单元学习目的**

通过学习四种数据存取方法,重点掌握文件系统的内部存储和外部存储**;掌握用于存取配置信息等小批量数据的SharePreferences;掌握数据库SQLite的添加,查询,更新和删除操作;**掌握用于应用程序之间交换数据的ContentProvider组件;掌握XML格式数据的Pull和SAX两种解析方法,以及JSON格式数据的JSONObject和GSON两种解析方法。

2**、本单元学习要求**

(1) 了解数据存取的权限管理;

(2) 掌握四种数据存取方式:File、SharePreferences、SQLite和ContentProvider;

(3) 掌握不同格式数据解析程序的编写。

3**、本单元学习方法**

结合教材以及Android Studio开发软件,对File、SharePreferences、SQLite和ContentProvider等模块进行编程练习,运行调试,并在模拟器中观察运行情况。

4**、本单元重点难点分析**

重点

(1) 四种数据存取方式

1) 文件操作

在Android中,可以通过文件流对象来操作文件。要获取文件流对象需要用openFileInput() 或openFileOutput() 函数来打开文件,并且需要将文件的名称传给文件输入流。注意这里指定的文件名不可以包含路径,因为所有的文件都是默认存储到/data/data//files/ 目录下。

FileInputStream in = null;

// 文件名KnowledgeUnit

in = openFileInput(“KnowledgeUnit”);

FileOutputStream out = null;

out = openFileOutput(“KnowledgeUnit”, Context.MODE_PRIVATE);

输出流还要指定文件操作的模式。使用MODE_PRIVATE模式,文件是私有数据,只能被应用本身访问。在该模式下,写入的内容会覆盖原文件的内容。MODE_APPEND模式会检查文件是否存在,存在就往文件里追加内容,否则就创建新文件。

先通过openFileOutput函数获取FileOutputStream 对象。给openFileOutput()函数传入要读取的文件名;然后系统会自动到/data/data//files/目录下去加载这个文件,并返回一个FileInputStream 对象,接下来再通过 Java 流的方式读取数据。接着用FileOutputStream构造BufferedWriter,写入文本到输出流。BufferedWriter通过缓存的方式来写入以提高存储效率。

public void save(String inputKPoint) {
FileOutputStream out = null;
BufferedWriter writer = null;

try {
out = openFileOutput(fileName, Context.MODE_PRIVATE);
writer = new BufferedWriter(new OutputStreamWriter(out));
writer.write(inputKPoint);
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (writer != null)
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}

文件中读取数据,首先获取文件输入流,然后构造读取数据的对象reader。BufferedReader是一行一行读取数据,每读取一行就把它连接在一起,然后存储在content对象中。

private String fileName = "KnowledgeUnit";

FileInputStream in = null;

BufferedReader reader = null;

 

StringBuilder content = new StringBuilder();

try {

​    in = openFileInput(fileName);

​    reader = new BufferedReader(new InputStreamReader(in));

​    String line = "";

​     while ((line = reader.readLine()) != null) {

​      content.append(line);

  }

}

return content.toString();

2) SharedPreferences

在Windows系统中,通常会用ini文件来保存一些参数信息。在Andriod系统中,比如在微信里可以设置加朋友时是否需要验证,这就是应用的参数设置。Android系统提供了一个轻量级的数据存取工具:SharedPreferences类,它适合用来保存应用的各种配置信息。

SharedPreferences非常轻量化,主要用来存储少量的信息。它的核心思想是在xml文件中通过键值对(通常也称为map)来存取数据。SharedPreferences采用文件的读写方式,文件存放在“/data/data//shared_prefs”目录下。

在SharedPreferences中存储数据一共有四个步骤:首先,获取一个SharedPreferences对象,然后再获取一个Editor对象;通过Editor就可以向SharedPreferences中存放数据;最后,需要调用commit函数来完成数据的存储。

要保存SharedPreferences数据,先要获取SharedPreferences.Editor对象,然后通过editor的各种put函数来写入数据,比如写入字符串、整数、布尔变量等等;最后,完成提交。

btnSave.setOnClickListener(new View.OnClickListener() {

  @Override

  public void onClick(View v) {

​    SharedPreferences.Editor editor =getSharedPreferences(id, MODE_PRIVATE).edit();

​    editor.putString("昵称", "libin9iOak");

​    editor.putString("简介", "Android初学者");

​    editor.putInt("年龄", 22);

​    editor.putBoolean("是否已选课", true);

​    editor.commit();

  }

});

接下来从SharedPreferences中取出数据。也是先获取SharedPreferences对象,然后调用针对不同数据类型的get函数来获取刚才存储的字符串、整数、以及布尔变量。各种get 函数都接收两个参数,第一个参数是键值,通过它就可以取得对应的数据;第二个参数是默认值,如果传入的键值找不到对应的值,就以该默认值作为返回值。最后,为了查看是否获取成功,可以通过打印日志的方式来显示获取的结果。

private String id = “me”;

SharedPreferences pref = getSharedPreferences(id, MODE_PRIVATE);

String nickname = pref.getString(“昵称”, “”);

String briefIntro = pref.getString(“简介”, “”);

int age = pref.getInt(“年龄”, 0);

boolean signUp = pref.getBoolean(“是否已选课”, false);

Log.d(TAG, "昵称: " + nickname);

Log.d(TAG, "简介: " + briefIntro);

Log.d(TAG, “年龄:” + age);

Log.d(TAG, "是否已选课: " + signUp);

3) SQLite****数据库

SQLite 是2000年,D. Richard Hipp 开发的一种中小型嵌入式数据库。作为一个轻量级的关系型数据库,SQLite运算速度非常快,占用资源少,通常只需要几百 K 的内存,适合在移动设备上使用。SQLite不仅支持标准的 SQL 语法,还遵循数据库的原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability)原则。它是进程内的数据库引擎,因此不存在数据库的客户端和服务器。数据库中所有的信息(比如表、视图等)都包含在一个文件中。这个文件可以自由复制到其它目录或其它机器上。

首先使用 DBQuizHelper类在SQLite中创建数据库。DBQuizHelper类是自定义的一个操作数据库的类。在SQLiteActivity中,给它的构造函数传入数据库的名称:Exam.db。然后,在“创建数据库”按钮按下时,调用getWritableDatabase() 函数完成数据库的创建。

private DBQuizHelper dbQuizHelper;

dbQuizHelper = new DBQuizHelper(this, “Exam.db”, null, 2);

Button btnCreateDB = (Button) findViewById(R.id.create_database);

btnCreateDB.setOnClickListener(new View.OnClickListener() {

@Override

public void onClick(View v) {

​ dbQuizHelper.getWritableDatabase();

}

});

DBQuizHelper继承自SQLiteOpenHelper类。SQLiteOpenHelper是SQLite Database的一个帮助类,用来管理数据库的创建、基本操作和版本更新。它是一个抽象类,需要创建一个自己的帮助类去继承它。

SQLiteOpenHelper的构造函数有四个参数,第一个参数是 Context对象,有它才能对数据库进行操作。第二个参数是数据库名;第三个参数允许在查询数据的时候返回一个游标(Cursor),一般传入空 null。第四个参数表示当前数据库的版本号,用来对数据库进行升级操作。

接下来构造DBQuizHelper类。

public class DBQuizHelper extends SQLiteOpenHelper {

// 创建的测试题目表Quiz,字符串形式的sql语句。

public static final String CREATE_QUIZ = “create table Quiz (”

​ + "id integer primary key autoincrement, "

​ + "statement text, "

​ + "type text, "

​ + "answer text, "

​ + “difficulty integer)”;

private Context context;

public DBQuizHelper(Context context, String name,

​ SQLiteDatabase.CursorFactory factory, int version) {

super(context, name, factory, version);

this.context = context;

}

在DBQuizHelper中,重写SQLiteOpenHelper类的onCreate()函数,调用execSQL() 函数来执行sql语句。

@Override

public void onCreate(SQLiteDatabase db) {

db.execSQL(CREATE_QUIZ);

db.execSQL(CREATE_COURSE);

Toast.makeText(context, “Create succeeded”, Toast.LENGTH_SHORT).show();

}

在onUpgrade()函数中可以升级数据库。如果数据库中表的定义发生了改变,比如在Quiz表中增加了一列“题目所属章节”,那么就需要在数据库中重新创建Quiz表。首先删除原来的Quiz表,然后再调用onCreate() 函数重新创建它。

@Override

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

// 删除原来的表

db.execSQL(“drop table if exists Quiz”);

db.execSQL(“drop table if exists Course”);

// 创建新的表

onCreate(db);

}

下面来实现数据库的增、删、改、查四大功能。要在数据库中插入一行,首先获取SQLiteDatabase的对象db,同时创建ContentValues对象values。values中存放要写入数据库的信息,然后用SQLiteDatabase的insert函数向表中添加数据。

insert函数的第一个参数是表名,第二个参数用来指定插入一条完全为NULL的记录,一般用不到这个功能,可直接传入 null。第三个参数是 ContentValues 对象,一个values对象代表了quiz表中的一行。注意:quiz表中还有id这一列,并没有给它赋值,因为在前面创建表的时候已经将 id 列设置为自增长,它的值会随着行的插入自动生成,不需要手动赋值。

SQLiteDatabase db = dbQuizHelper.getWritableDatabase();

ContentValues values = new ContentValues();

// 第一个参数是表的列名。

values.put(“statement”, “简述Android中Intent的作用。”);

values.put(“type”, “简答题”);

values.put(“answer”, “Intent用于同一应用或不同应用的组件之间通信。”);

values.put(“difficulty”, “2”);

db.insert(“Quiz”, null, values);

values.clear();

values.put(“statement”, “Service结束运行的方法有哪两种?有何区别?”);

​ … …

db.insert(“Quiz”, null, values);

更新和删除数据都比较简单。现在把某一道题目的难度做一下修订,原来题目的难度是“2”,现在要改为“4”。需要在values对象中重新设置难度列,然后调用update函数进行修改。注意:update函数的第三个参数是where条件。第四个参数是where条件的取值,也就是要修改的题目。

SQLiteDatabase db = dbQuizHelper.getWritableDatabase();

ContentValues values = new ContentValues();

values.put(“difficulty”, “4”);

// where条件:statement=“Service结束运行的方法有哪两种?有何区别?”

// 把这道题的难度从原来的“2”改为“4”

db.update(“Quiz”, values, “statement = ?”,

new String[]{“Service结束运行的方法有哪两种?有何区别?”});

SQLiteDatabase db = dbQuizHelper.getWritableDatabase();

delete函数接收三个参数,第一个参数是表名,第二、第三个参数用来限定要删除哪些数据。下面的代码删除所有难度大于4的题目。如果不指定条件,将默认删除所有的行。

db.delete(“Quiz”, “difficulty > ?”, new String[]{“4”});

查询语句返回一个游标对象cursor,通过它可以获取查询的结果。

SQLiteDatabase db = dbQuizHelper.getWritableDatabase();

Cursor cursor = db.query(“Quiz”, null, null, null, null, null, null);

查询获取了游标对象,通过它能够查看或者处理查询结果集中的数据。游标就像一个指针它可以指向结果集中的任何一行,让用户能够对指定的行进行操作。在一个循环中,通过游标的 getColumnIndex函数取得某一列的位置索引,把这个索引传给游标的getString函数,getString函数再从结果集中读取数据。

if (cursor.moveToFirst()) {

do {

​ String statement = cursor.getString(cursor. getColumnIndex(“statement”));

​ String type = cursor.getString(cursor. getColumnIndex(“type”));

​ int difficult = cursor.getInt(cursor.getColumnIndex(“difficulty”));

​ Log.d(TAG, "试题: " + statement);

​ Log.d(TAG, "题型: " + type);

​ Log.d(TAG, “难度:” + difficult);

} while (cursor.moveToNext());

}

cursor.close();

在循环末尾判断游标是否已经到达结果集的最后一行。最后,在游标使用完以后要调用close函数关闭它。

4) 内容共享组件

内容提供器(ContentProvider)是Android应用的四大组件之一。ContentProvider 在Android中的作用是对外共享数据,也就是说可以通过ContentProvider把应用中的数据共享给其他应用访问。其他应用也可以通过ContentProvider 对共享应用中的数据进行增、删、改、查,比如答题应用就能够直接访问联系人信息。Android系统内置的短信、媒体库等程序都实现了跨程序数据共享功能。

内容提供器对底层数据存储方式进行抽象,为存储和获取数据提供了统一的接口,可以让数据在不同的应用程序之间共享。内容提供器为数据共享提供了一个安全的环境。它允许把自己的应用数据根据需求开放给其他应用。其他应用也可以增加、删除、修改和查询开放的数据,不用担心开放数据权限而带来的安全问题。Android系统还提供了音频、视频、图片和通讯录的共享接口,可以通过它们直接访问这些资源。

一个应用程序要使用上述多个共享数据,如果需要开发者了解每个内容提供器的不同实现,就太繁琐了;所以Android提供了内容解析器ContentResolver来统一管理不同内容提供器的共享功能。要访问某一个内容提供器,首先获取内容解析器,内容解析器提供了对数据进行增、删、改、查的操作函数。

要访问数据需要用到ContentResolver的查询函数。它的查询方式类似于SQL语句,用uri表示要访问的数据地址。通常数据以表的形式呈现。projection是要查询的列名;selection是约束条件;selectionArgs是条件参数对应的值;sortOrder是排序方式。查询的返回结果是游标,通过它可以逐行访问数据。

Cursor cursor = getContentResolver().query(

​ uri,

​ projection,

​ selection,

​ selectionArgs,

​ sortOrder

);

通过前面的查询,返回一个游标对象。在循环中使用游标对象把数据提取出来,直到循环结束。

if (cursor != null) {

while (cursor.moveToNext()) {

​ String statement = cursor.getString(cursor.getColumnIndex(“statement”));

​ int difficult = cursor.getInt(cursor.getColumnIndex(“difficult”));

}

cursor.close();

}

下面利用ContentResolver在答题APP应用中读取联系人信息。首先构造一个ContactsActivity,这个活动要访问联系人APP的内容提供器。注意访问共享数据还要声明使用权限,如果使用Android 6.0以上的版本,记得要申请动态使用权限。

构造一个联系人类ContactsUtil来读取所有联系人信息。首先获取内容解析器,然后查询联系人。在query函数中,没有用Uri.parse() 函数去解析一个内容URI字符串,因为Android中的Phone类(ContactsContract.CommonDataKinds.Phone类)已经对共享资源进行了封装,提供了一个CONTENT_URI常量。接下来,使用游标对象遍历联系人信息,把联系人姓名和手机号逐一提取出来。联系人姓名对应DISPLAY_NAME常量,电话号码对应NUMBER常量,其他参数可以查阅Andriod的相关资料。

public class ContactsUtil {

public static void getContactInfos(Context context) {

​ ContentResolver resolver = context.getContentResolver();

​ Cursor cursor = resolver.query(

​ // 联系人uri。

​ ContactsContract.CommonDataKinds.Phone.CONTENT_URI,

​ null, null, null, null);

​ while (cursor.moveToNext()) {

​ String contactId = cursor.getString(

​ cursor.getColumnIndex(ContactsContract.Contacts._ID));

​ String name = cursor.getString(cursor.getColumnIndex(

​ ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME));

String phone = cursor.getString(cursor.getColumnIndex(

​ ContactsContract.CommonDataKinds.Phone.NUMBER));

(2) 数据的解析方法

现在,对网络数据的组织有两大规范,分别是XML格式和JSON格式。XML是用于标记文件使其具有结构性的标记语言。JSON是一种轻量级的数据交换格式。

可扩展标记语言(Extensible Markup Language,XML)主要用来存储带有结构,带有格式的数据。XML经常用于网络数据传输和作为程序配置文件。

常用的XML解析方法有:DOM解析、SAX 解析和PULL解析。SAX(Simple API for XML)是一种基于事件的解析器,它采用事件处理机制,围绕事件源以及事件处理器来工作。当事件源产生事件后,调用事件处理器完成相应的任务。调用事件处理器还要传递相应事件的状态信息,这样事件处理器才能根据事件信息来决定自己的行为。

SAX解析包括以下四个步骤:

(1)获取XML文件对应的资源,可以是XML输入流、文件、URI和字符串;

(2)获取SAX解析工厂(SAXParserFactory);

(3)由解析工厂生成一个SAX解析器(SAXParser);

(4)将XML输入流和处理器(Handler)传给XMLReader,调用parse函数进行解析。

public class DataParse {

public static void parseXMLWithSAX(String xmlData) {
try {
SAXParserFactory factory = SAXParserFactory.newInstance();
XMLReader xmlReader = factory.newSAXParser().getXMLReader();
ParseHandler handler = new ParseHandler(xmlStr);
xmlReader.setContentHandler(handler);
xmlReader.parse(new InputSource(new StringReader(xmlData)));
} catch (Exception e) {
e.printStackTrace();
}
}

… …

}

SAX在解析XML时,采用逐行扫描的方式来处理数据。当扫描到文档(document)、元素(element)的开始和结束位置时,通知事件处理函数;随后把事件发送给处理器,由处理器完成文档的解析。解析XML的工作在类ParseHandler中完成,它继承自DefaultHandler,解析时需要重写DefaultHandler中的方法。

Android还附带了一个PULL解析器来解析XML文档。PULL解析器的工作方式和SAX类似,都是基于事件模式。不同的是在PULL解析过程中返回的事件类型是数字,并且需要从解析器中获取事件,然后再做出相应的处理。SAX是由处理器触发事件,然后执行代码。PULL解析器的解析速度快,简单易用。Android系统内部在解析各种XML时也是用PULL解析器,Android官方也推荐使用PULL解析技术。

PULL解析器的工作原理:首先获取一个XmlPullParserFactory对象,通过它来得到XML PULL解析器;然后调用解析器的setInput 函数把 XML 数据放入解析器,开始执行解析。解析器用getEventType 函数得到当前的解析事件类型。

public static void parseXMLWithPull(String xmlData) {

  try {

​    Log.d(TAG, "xml 数据:" + xmlData);// 设置解析器

​    XmlPullParserFactory factory = XmlPullParserFactory.newInstance();

​    XmlPullParser xmlPullParser = factory.newPullParser();

​    xmlPullParser.setInput(new StringReader(xmlData));int eventType = xmlPullParser.getEventType();

​    String id = "";

​    String statement = "";

​    String type = "";// 执行XML文档解析

while (eventType != XmlPullParser.END_DOCUMENT) {
       String nodeName = xmlPullParser.getName();
       switch (eventType) {
         // 根据事件类型提取XML中各个标签的信息
         case XmlPullParser.START_TAG: {
           if ("id".equals(nodeName)) {
             id = xmlPullParser.nextText();
           } else if ("statement".equals(nodeName)) {
             statement = xmlPullParser.nextText();
           } else if ("type".equals(nodeName)) {
             type = xmlPullParser.nextText();
           }
           break;
         }
 
         case XmlPullParser.END_TAG: {
           if ("quiz".equals(nodeName)) {
             Log.d(TAG, "题号: " + id);
             Log.d(TAG, "题目:" + statement);
             Log.d(TAG, "题型:" + type);
           }
           break;
         }
         default:
           break;
       }
       eventType = xmlPullParser.next();
     }
   } catch (Exception e) {
     e.printStackTrace();
   }
 }

PULL解析器能够分辨文档标签的开始元素和结束元素。当某个元素开始时,调用解析器的nextText函数从XML文档中提取所有字符数据。当解释到一个文档结束时,自动生成EndDocument事件。在循环中,通过 getName() 函数得到当前节点的名称,如果发现节点名是id、statement 或 type,就调用 nextText 函数来取得节点内信息,当解析完一个节点就将节点的内容打印出来。

JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,具有良好的可读性,并且便于快速编写。它采用完全独立于编程语言的文本格式来存储和表示数据。数据格式采用键值对的方式,可以用来表示对象、数字,还可以设置对象的属性和值。

(1) {} 花括号用来保存对象;

(2) [] 方括号用来保存数组;

(3) “” 双引号内是属性或值;

(4) : 冒号表示后者是前者的值。

相对于XML,简洁和清晰的层次结构使得 JSON 成为理想的数据交换语言;而且JSON格式易于人们阅读和编写,同时也易于机器解析和生成,能有效地提升网络传输效率。

采用JsonArray的方式可以把JSON数据转换成JSON数组的形式;然后再通过数组获取一个个的JSON对象。对于每个JSON对象,利用JSONObject解析出JSON格式中的每项数据。下面把id、题干和题目类型等信息通过JSONObject提取出来。

public static void parseJSONWithJSONObject(String jsonData) {
     try {
       Log.d(TAG, "JSON 数据:" + jsonData);
 
       JSONArray jsonArray = new JSONArray(jsonData);
       for (int i = 0; i < jsonArray.length(); i++) {
         JSONObject jsonObject = jsonArray.getJSONObject(i);
         String id = jsonObject.getString("id");
         String statement = jsonObject.getString("statement");
         String type = jsonObject.getString("type");
         … …
         Log.d(TAG, "题号:" + id);
         Log.d(TAG, "题目:" + statement);
         Log.d(TAG, "题型:" + type);

… …
       }
     } catch (Exception e) {
       e.printStackTrace();
     }
   }

GSON是解析JSON的一个开源框架,它用于转换Java对象和JSON对象。在使用GSON API之前,需要在build.gradle文件中添加对gson的依赖关系。

dependencies {

… …

compile 'com.google.code.gson:gson:2.7’

… …

testCompile ‘junit:junit:4.12’

}

Gson提供了fromJson() 和toJson() 两个直接用于解析和生成的函数。fromJson函数实现反序列化,toJson函数实现了序列化。下面用fromJson函数 把JSON数据中的测试题集合转换为测试题列表集合;然后,把集合中的题目提取出来转换为quiz对象。

Gson gson = new Gson();

List qList = gson.fromJson(data.toString(),

​ new TypeToken<List>(){}.getType());

Quiz quiz = gson.fromJson(data, Quiz.class);

GSON作为Java对象和JSON数据之间进行映射的Java类库,可以将一个JSON字符串转换成一个Java对象,或者将一个Java对象转换成JSON字符串。GSON解析的特点是:快速、高效、代码量少、简洁、数据传递和解析方便。

难点

(1) 数据共享的基本原理

开发者自己编写的移动应用程序也可以把数据共享出来让其他应用程序使用。应用程序要共享数据需要提供公开的URI,这样其他应用程序才能够访问到共享的数据。每个ContentProvider都拥有一个公共的URI,它用于表示ContentProvider所提供的数据。

答题APP把测试题目共享给其他应用程序。首先,创建一个Quiz内容提供器,它从ContentProvider继承;然后,通过UriMatcher 类来匹配Uri。数据是来自SQLite数据库的quiz表。

// 自定义 ContentProvider

public class QuizProvider extends ContentProvider {

private static final int QUEYSUCESS = 0;

private static final int INSERTSUCESS = 1;

private static final int UPDATESUCESS = 2;

private static final int DELSUCESS = 3;

private static final String AUTHORITY = “pers.cnzdy.tutorial.quiz.provider”;

static UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH);

private DBQuizHelper helper;

private String table = “quiz”;

uri用来指定数据源。当一个数据源含有多个内容,比如包含多个表,就需要用不同的uri进行区分。在QuizProvider中,利用UriMatcher来匹配执行不同的操作。

static{

/**

* authority 主机名 通过主机名来访问共享的数据

* path pers.cnzdy.tutorial.quiz.provider/query

* code 匹配码

* 添加查询、插入、更新、删除匹配规则。

*/

matcher.addURI(AUTHORITY, “query”, QUEYSUCESS);

matcher.addURI(AUTHORITY, “insert”, INSERTSUCESS);

matcher.addURI(AUTHORITY, “update”, UPDATESUCESS);

matcher.addURI(AUTHORITY, “delete”, DELSUCESS);

}

UriMatcher的addURI() 函数有三个参数,分别是权限、路径和自定义编码。通过自定义编码,通过UriMatcher的过滤,来确定要访问哪张表,或者执行哪个动作。

在QuizProvider的onCreate函数中,创建DBQuizHelper对象。

@Override

public boolean onCreate() {

helper = new DBQuizHelper(getContext(), “Exam.db”, null, 2);

return true;

}

QuizProvider的getType函数根据传入的 URI 返回对应的 MIME 类型。

@Override

public String getType(Uri uri) {

return null;

}

查询函数query让其他应用可以从内容提供器中查询数据。query函数有五个参数:uri, projection、selection、selectionArgs、sortOrder。uri 参数用来确定查询哪张表,其他参数与内容解析器的query函数的参数一样。

在query函数中,首先匹配要执行的动作,如果是查询,就调用数据库的查询语句,获取数据,返回游标。如果匹配不成功,则产生异常。

@Override

public Cursor query(Uri uri, String[] projection,

String selection, String[] selectionArgs, String sortOrder) {

int match = matcher.match(uri);

if (match == QUEYSUCESS ) {

​ SQLiteDatabase db = helper.getReadableDatabase();

​ Cursor cursor = db.query(table, projection, selection,

​ selectionArgs, null, null, sortOrder);

​ return cursor;

}else{

​ throw new IllegalArgumentException(“路径匹配失败”);

}

}

QuizProvider的插入函数insert向数据库中添加一条数据,将待添加的数据保存在 values 参数中。添加完成后,返回一个用来表示这条新记录的 URI。如果QuizProvider的访问者需要知道内容提供器中的数据是否发生了变化,就调用内容解析器的notifyChange() 函数来通知注册在这个URI上的访问者。

@Override

public Uri insert(Uri uri, ContentValues values) {

int match = matcher.match(uri);

if (match == INSERTSUCESS) {

​ SQLiteDatabase db = helper.getReadableDatabase();

​ long insert = db.insert(table, null, values);

​ if (insert > 0) {

​ // 通知注册在这个uri上的访问者

​ getContext().getContentResolver().notifyChange(uri, null);

}

​ Uri uriResult= Uri.parse(“content://”+ AUTHORITY + “/” + insert);

​ return uriResult;

}else{ … … }

}

QuizProvider的删除函数delete删除数据库中的数据。selection和selectionArgs 参数用于约束删除哪些行,被删除的行数将作为返回值返回。

@Override

public int delete(Uri uri, String selection, String[] selectionArgs) {

int match = matcher.match(uri);

if (match == DELSUCESS) {

​ SQLiteDatabase db = helper.getReadableDatabase();

​ int delete = db.delete(table, selection, selectionArgs);

​ if (delete > 0) {

​ getContext().getContentResolver().notifyChange(uri, null);

}

​ return delete;

}else { … … }

}

更新函数更新内容提供器中已有的数据。新数据保存在 values 参数中,同样selection 和 selectionArgs 参数用于约束更新哪些行,受影响的行数将作为返回值返回。

@Override

public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {

int match = matcher.match(uri);

if (match == UPDATESUCESS) {

​ SQLiteDatabase db = helper.getReadableDatabase();

​ int update = db.update(table, values, selection, selectionArgs);

​ if (update > 0) {

​ getContext().getContentResolver().notifyChange(uri, null);

}

​ return update;

}else { … … }

}

内容提供器还需要在全局配置文件中进行定义。android:name对应自定义的QuizProvider类。android:authorities为QuizProvider类中定义的静态字符串:“pers.cnzdy.tutorial.quiz.provider”。

<application

​ … …

<provider

​ android:name=".Data.QuizProvider"

​ android:authorities=“pers.cnzdy.tutorial.quiz.provider”

​ android:enabled=“true”

​ android:exported=“true”>

… …

完成以上代码后,其他的应用程序就能够访问应用共享的数据了。

(2) Android****的文件存储方式

在逻辑上,Android系统把整个存储空间划分为内部存储(Internal storage)和外部存储(External storage)。内部存储用于存放系统本身和应用程序的数据,空间有限。内部存储有严格的权限管理,用户不能随意访问。如果要访问,需要root权限。在DDMS中,用File Explorer查看Android系统的存储空间,可以看到第一级的data文件夹,它就是内部存储,

打开data文件夹之后(需要root权限),有一个app文件夹,它存放着所有app的安装的文件(调试app时,会上传apk到该文件夹)。另外,还有第二级data文件夹,在这个文件夹下面都是一些包名,打开包名之后列出以下一些文件:

data/data/包名/shared_prefs

data/data/包名/databases

data/data/包名/files

data/data/包名/cache

使用sharedPreferenced存取数据时,将数据保存到该文件夹的xml文件中。如果使用数据库,数据库文件将存储在databases文件夹中,一般的数据则存储在files文件夹中,缓存文件存储在cache文件夹中。

应用程序将文件保存在内部存储中。系统默认只有自己的应用能访问这些文件;并且一个应用所创建的所有文件都放在一个文件夹下面,这个文件夹的名称与应用包名相同,即应用创建的内部存储文件与应用相关联。当应用卸载之后,内部存储中的这些文件也会被删除。在默认情况下,应用程序安装到内部存储。另外,通过在AndroidManifest.xml文件中指定android:installLocation属性,应用程序也可以安装在外部存储器中。

内部存储通常使用Context来操作,下面是访问内部存储的常用函数:

Environment.getDataDirectory() // 获取路径: /data

context.getFilesDir() // 获取路径: /data/data/< package name >/files/…

context.getCacheDir() // 获取路径: /data/data/< package name >/cach/…

context.getDir(String name, String mode) 返回/data/data//目录下指定名称文件夹的File对象,如果该文件夹不存在则用指定名称创建一个新的文件夹。mode用于指示文件的创建模式,指定MODE_PRIVATE将把文件设为应用的私有文件。

对于外部存储中的数据,应用程序可以自由访问,不需要严格的访问权限,比如可以在电脑上直接查看这些文件。外部存储中的文件能够被其他App访问或者通过电脑进行访问。外部存储又分为SD卡和扩展卡两种存储方式。

storage的子文件夹又分为两类,分别是公有目录和私有目录。公有目录是系统创建的文件夹,比如:DCIM、DOWNLOAD等;私有目录是“/Android”文件夹。私有目录属于应用私有,当用户卸载应用时,该目录及其内容将被删除。

通常建议应用程序的数据(不适合其他应用使用的文件,比如:图像、纹理、音效等等)存放在外部存储的私有目录中(即该App的包名下面)。这样当用户卸载应用之后,相关的数据会一起删除;如果直接在/storage/文件夹下面创建应用子文件夹,那么当应用被删除的时候,这个子文件夹就不会被删除。

a) 获取外部存储路径:/storage/sdcard0

Environment.getExternalStorageDirectory()

b) 通过以下函数可以获取私有目录:

// 获取路径: /storage/emulated/0/Android/data/< package name >/files/…

context.getExternalFilesDir()

// 获取路径: /storage/emulated/0/Android/data/< package name >/cach/…

context.getExternalCacheDir()

九个公有目录可以通过以下函数获取:

// 获取路径: /storage/sdcard0/Alarms

Environment.getExternalStoragePublicDirectory(DIRECTORY_ALARMS)

// 获取路径: /storage/sdcard0/DCIM

Environment.getExternalStoragePublicDirectory(DIRECTORY_DCIM)

// 获取路径: /storage/sdcard0/Download

Environment.getExternalStoragePublicDirectory(DIRECTORY_DOWNLOADS)

… …

在使用外部存储执行任何操作之前,要调用 getExternalStorageState函数检查存储介质是否可用。此外,如果是Android 4.4以前版本,读取或写入外部存储(包括公共目录和私有目录)的文件,必须获取 READ_EXTERNAL_STORAGE 或 WRITE_EXTERNAL_STORAGE 系统权限:

本章习题:

1、本单元考核点
File、SharePreferences、SQLite和ContentProvider的操作方法。
ContentProvider的基本原理。
XML格式和JSON格式数据的解析方法。
2、本单元课后习题
1、为了保存永久性的应用数据,Android 主要提供了哪几种数据存储方式?
答案:
(1)Shared Preferences以 key-value 对方式保存私有的基本类型数据。
(2)File Storage在设备存储空间中保存私有数据。
(3)SQLite Databases在私有的数据库中存储结构化数据。
(4)Network Connection将数据保存在网络服务器上。
(5)Android 还提供将私有数据开放给其他应用的途径:Content Provider。
2、简述SAX解析方式和特点。
答案:它逐行扫描文档,一边扫描一边解析,在读取文档时激活一系列事件,这些事件被推给事件处理器,由事件处理器提供对文档内容的访问;特点:不需要将数据存储在内存中,对于大型文档的解析有较大优势。

参考资源:

1、 DebDiv移动开发社区著.移动开发平台解决方案.北京:海洋出版社,2011.

2、 泡在网上的日子 - 做最好的移动开发社区:www.jcodecraeer.com

原创声明

=======

作者: [ libin9iOak ]


本文为原创文章,版权归作者所有。未经许可,禁止转载、复制或引用。

作者保证信息真实可靠,但不对准确性和完整性承担责任。

未经许可,禁止商业用途。

如有疑问或建议,请联系作者。

感谢您的支持与尊重。

点击下方名片,加入IT技术核心学习团队。一起探索科技的未来,共同成长。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

猫头虎

一分也是爱,打赏博主成就未来!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值