Android 中提供了较多的数据存储和交换方式,如SharedPreference+Preference Activity,File,SQLite+Content Provider,以及XML JSON 格式数据解析等。它们各有各的优点,而它们的优点也决定了它们的使用范围。
SharedPreferences SharedPreferences 类提供给开发人员保存和获取基本数据类型的键值对的方式。该类主要用于类似基本类型,等简单变量,在应用程序结束后,数据仍旧会保存。这些数据会以XML文件的形式存储在apk的包里面。
SharedPreferences的获取: SharedPreferences的获取方式有两种:
getSharedPreferences():如果需要多个使用名称来区分的共享文件,则可以使用该方法,其第一参数就是共享文件的名称,对于使用同一个名称获得的多个SharedPreferences引用,其指向同一个对象。
getPreferences():如果Activity仅需要一个共享文件,则可以使用该方法。因为只有一个文件,它并不需要提供名称。
向SharedPreferences类中增加值: 完成向SharedPreferences类中增加值的步骤如下:
调用SharedPreferences类的edit方法获得SharedPreferences.Editor对象
调用诸如putBoolean(),putString()等方法增加数据
使用commit方法或者apply方法提交新值。
写入sharedPreferences SharedPreferences sharedpreference = getSharedPreferences("messageBylinxiaohai" ,MODE_PRIVATE) ; Editor editor = sharedpreference.edit() ; editor.putString("name" ,names ) ; editor.putString("password" ,passwords ) ; editor.commit() ;
读出sharedPreferences SharedPreferences sharedPrefereces = getSharedPreferences("messageBylinxiaohai" ,MODE_PRIVATE); String name = sharedPrefereces.get String ("name" ,"" ); String password = sharedPrefereces.get String ("password" ,"" );
对于SharedPreferences而言,它使用XML文件来保存数据,文件名与指定的名称相同,存储在data/data文件夹下
数据存储模式 数据存储有三种常见模式: MODE_PRIVATE:私有方式 MODE_WORLD_READALE:全局可读 MODE_WORD_WRITEABLE:全局可写
Preference Activity Android 提供了一个用于为应用程序创建系统样式的Preference Screen。它能够确保应用程序中的Preference Activity与本地和其他第三方应用程序中所使用的一致。
Preference Activity 组成 Preference Activity 由4个部分组成:
Preferences Screen布局: 一个XML文件,定义了在Preference Screen中显示的层次结构,它指定了要显示的文本以及相关控件等信息。
Preference Activity 和Preference Fragment 分别是 PreferenceActivity 和Preference Fragment 的扩展,用于包含Preference Screen,在Android 3.0之后,Preference Screen包含在Preference Fragment中,而Preference Fragment包含在Preference Activity中。
Preference Header定义:一个XML文件,定义了应用程序的Preference Fragment 以及用于显示Preference Fragment的层次结构。
SharedPreference 变化监听器:onSharedPreferenceChangeListener类的实现,用于监听SharedPreference 的变化。
实现一个Preference Activity 步骤1:定义Preference Screen
Preference screen定义文件存储在res/xml文件夹中.
Preference Screen 定义格式如下:
<?xml version=“1.0 ” encoding=“utf-8 ”?> <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" > </PreferenceScreen>
PreferenceScreen 内部还可以嵌套另一个PreferenceScreen ,每个PreferenceScreen 将表示为一个可选的元素,单击它会出现一个新的屏幕。
在每个PreferenceScreen 内部可以包含PreferenceCategory 和 xxxPreference控件 PreferenceCategory定义:
<PreferenceCategory android:title ="SMS助手" >
Preference常用控件:
CheckBoxPreference:复选控件 EditTextPreference:允许用户输入一个字符串值作为首选项,在运行时首选项文本将会显示一个文本输入对话框 ListPreference 该首选项会显示一个对话框,其中包含了可供选择的值的列表,可以指定不同的数组以包含显示文本和选项值。 MultiSelectListPreference:类似于复选框列表 RingtonePreference:一个专用的列表首选项,显示可供用户选择的可用铃声列表 每个Preference控件应当至少包含下列4种属性: android:key:SharePreference 键,所选择的值将会根据相应的键进行记录。 android:title:用于表示首选项的显示文本 android:summary:在标题文本下方以更小字体显示的更长的文本描述 android:defaultValue:当没有为该首选项分配选项值的时候将会显示默认值
还可以使用Intent在Preference Screen中导入系统首选项:
<PreferenceScreen android:summary ="System Preference imported using an intent" android:title ="Intent Preference" > <intent android:action ="android.settings.DISPLAY_SETTINGS" /> </PreferenceScreen >
下方为定义一个Preference Screen的一个例子。
<?xml version="1.0" encoding="utf-8" ?> <PreferenceScreen xmlns:android ="http://schemas.android.com/apk/res/android" > <CheckBoxPreference android:defaultValue ="true" android:key ="autoBack" android:summaryOff ="关闭" android:summaryOn ="开启" android:title ="自动回复" /> <PreferenceCategory android:title ="SMS助手" > <CheckBoxPreference android:defaultValue ="false" android:key ="smsSilence" android:summaryOff ="关闭" android:summaryOn ="开启" android:title ="静音" /> <PreferenceScreen android:title ="更多选项" > <CheckBoxPreference android:defaultValue ="true" android:key ="cb21" android:summaryOff ="关闭" android:summaryOn ="开启" android:title ="功能1" /> <CheckBoxPreference android:defaultValue ="true" android:key ="cb22" android:summaryOff ="停用" android:summaryOn ="使用" android:title ="功能2" /> <ListPreference android:dialogTitle ="请选择论坛" android:entries ="@array/listentry" android:entryValues ="@array/listvalue" android:key ="list1" android:summary ="开发者论坛" android:title ="android form" /> <EditTextPreference android:defaultValue ="Hello EditTextPreference" android:dialogTitle ="设置输入" android:key ="et1" android:summary ="点击输入" android:title ="EditTextPreference Simple" /> </PreferenceScreen > </PreferenceCategory > <PreferenceCategory > <RingtonePreference android:key ="rt1" android:summary ="选择铃声" android:title ="RingtonePreference Sample" /> </PreferenceCategory > <PreferenceScreen android:summary ="System Preference imported using an intent" android:title ="Intent Preference" > <intent android:action ="android.settings.DISPLAY_SETTINGS" /> </PreferenceScreen > </PreferenceScreen >
步骤2 实现Preference Fragment
继承PreferenceFragment
在onCreate方法中调用addPreferencesFromResource。如下所示:
public class MainActivity extends PreferenceFragment { protected void onCreate(Bundle savedInstanceState) { super .onCreate(savedInstanceState); addPreferencesFromResource(R .xml.setting); }
步骤3 创建Preferece Header
Preference Header 描述了Preference Fragment在Preference Activity中如何分组和显示,每个头都表示并允许显示一个特定的Preference Fragment。 Preference Header是XML资源,存储在res/xml文件中,每个头的资源ID就是它的文件名。每个Preference Header都必须与一个特定的Preference Fragment关联,当选中该头的时候,Fragment就会显示出来,必须指定Preference Header的标题,还可以包含一个摘要和图片资源。还可以使用一个Intent调用.
<?xml version="1.0" encoding="utf-8" ?> <preference-headers xmlns:android ="http://schemas.android.com/apk/res/android" > <header android:fragment ="com.example.testpreferencefragment.UserSettings" android:summary ="系统设置" android:title ="Setting" > <intent android:action =“android:settings.DISPLAY_SETTINGS” > </header > </preference-headers >
步骤4 实现Preference Activity
Preference Activity类用于包含由Preference Header资源定义的PreferenceFragment 层次结构。
继承PreferenceActivity public class MainActivity extends PreferenceActivity
去掉onCreate的实现
重写onBuildHeaders方法,在其中调用loadHeadersFromResource并指定一个Preference Header资源文件。
public void onBuildHeaders(List<Header> target ) { loadHeadersFromResource(R.xml .preferenceheader , target ) ; }
在Manifest清单文件中注册PreferenceActivity
<activity android:name ="com.example.testpreferencefragment.UserSettingActivity" > </activity >
为了显示在该Activity中包含的应用程序设置,需要调用startActivity或者startActivityForResult打开它。
public class UserSettingActivity extends PreferenceActivity { public void onBuildHeaders(List <Header > target) { loadHeadersFromResource(R .xml.preferenceheader, target); } @Override protected boolean isValidFragment(String fragmentName) { return true ; } }
使用Preference Screen设置的Shared Preference:
包括Activity ,Service 和BroadCast Receiver都能访问存储在应用程序沙袋中的Shered Preference值。可以使用如下方法:
SharedPreferences prefs = PreferenceManager . getDefaultSharedPreferences(getApplicationContext () );
设置监听器
mSharedPreference = PreferenceManager . getDefaultSharedPreferences(this ) ; mSharedPreference.registerOnSharedPreferenceChangeListener(new OnSharedPreferenceChangeListener() { public void onSharedPreferenceChanged(SharedPreferences shareferences , String key ) { if (key.equals("autoBack" )){ boolean autoBackStatus = shareferences.getBoolean("autoBack" , false ) ; if (autoBackStatus){Log . i("PreferenceActivity" ,"true" ); }else {Log . i("PreferenceActivity" ,"false" ); } } } });
FILE 文件 文件一般存储在外部存储中:这些外部存储可以被所有应用程序访问,通过USB连接到设备后,可以被挂载到计算机文件系统,外部存储一般位于SD卡上,但是有些设备是在内部存储中将其作为一个独立的分区来实现。当在外部存储介质上存储文件时,对于存储这些文件是没有强制的安全保障的,任何程序都可以访问,重写或者删除这些存储在外部存储中的文件。同时外部存储可能不总是可用的,如果SD卡弹出或者这个设备通过被计算机挂载来访问,应用程序就不能在外部存储上读取文件了。
使用raw文件作为资源 可以将外部文件资源放置在项目层次结构的res/raw文件夹中作为程序所需的外部文件资源,这些资源将在分发包中包含它们。 下面是读取raw文件夹下的一个歌词文件的例子,结果如下图,代码如下:
inputStream = getResources() .open RawResource(R.raw .rawfile ) ; Reader reader = new InputStreamReader(inputStream ) ; BufferedReader br = new BufferedReader(reader ) ; String lines = null; try { while ((lines = br.readLine() )!=null){ Log . i("MainActivity" , lines); } if (inputStream!=null) { inputStream.close() ; inputStream = null; } if (br!=null) { br.close() ; br = null; } } catch (IOException e) { e.printStackTrace() ; }
在Android中使用File对象存储数据主要有两种方式:
1.使用Java提供的IO流体系 使用FileOutputStream类提供的openFileOutput()方法来打开相应的输出流。使用FileInputStream类提供的openFileInput()方法打开相应的输入流,默认情况下使用IO流保存的文件仅对当前应用程序可见,也就是仅仅能够从应用程序沙箱中读写文件,指定路径分隔符将会导致抛出一个异常,如果用户卸载了该应用程序,则保护数据文件也会一起被删除。 在创建FileOutputstream的时候,如果指定的文件名不存在,Android将会创建一个新的文件,如果文件已经存在那么默认的行为就是覆盖它,如果想要在已经存在的文件末尾添加内容,可以指定其模式为MODE_APPEND. 如果要将这些文件分享给其他应用可用可以使用Content Provider或者在创建的时候使用:MODE_WORLD_WRITEABLE和MODE_READABLE.
如果应用程序需要缓存临时文件,Android提供了可以管理的内部缓存和一个不能管理的外部缓存,分别调用getCacheDir和getExtenalCacheDir方法可以从当前的上下文中访问它们。 存储在任何一个该缓存位置中的文件在应用程序被卸载后都会被删除掉,当系统运行在低可用存储空间的时候,存储在内部缓存的文件可能被系统所删除,存储在外部缓存中的文件则不会被删除掉,因为系统不会跟踪外部媒介的可用存储空间。
使用Environment类提供的getExternalStorageDirectory方法对Android的SD卡进行数据读写。每个Android设备都支持共享的外部存储用来保存文件,这可以是SD卡等可以移除的存储介质,也可以是手机内存等不可移除的存储介质,保存的外部存储文件都是全局可读的,并且在用户使用USB连接电脑后可以修改这些文件。
使用Environment.getExtralStoragePublicDirectory可以用来找到存储应用程序文件的路径,返回的位置为用户通常存放和管理各类文件的位置。
DIRECTORY_ALARMS: 用户可选的警示音的可用的声音文件DIRECTORY_RINGTONES: 用户可选的铃声的可用的声音文件DIRECTORY_NOTIFICATIONS: 用户可选的通知音的可用的声音文件DIRECTORY_DCIM: 拍摄到的图片和视频DIRECTORY_PICTURES: 图片DIRECTORY_DOWNLOADS: 用户下载的文件DIRECTORY_MOVIES: 电影
如果返回的目录不存在,必须在向该目录写入文件前先创建它。
SQLite + Content Provider SQLite数据库 Android 通过结合使用SQLite数据库和Content Provider,提供了结构化数据的持久化功能。 Android应用程序的数据库存储在/data/data/包名/database,所有的数据库都是私有的,只能被创建它们的应用程序访问。 SQLite 数据库已经被实现为简洁的C语言库,并且是Android软件栈的一部分。通过作为一个库实现,而不是作为一个独立的进程不断执行,每个SQLite数据库成为创建它的应用程序的完整部分,这样做能够减少应用程序的外部依赖性,最小延迟,并简化事务锁定和同步。 SQLite在列定义中使用了一种松散类型的方式,即并不要求一列中的所有值都是同一种类型。
创建数据库 在使用数据库的时候,最好将底层数据库封装起来,只公开与数据库进行交互时必须使用的公有方法和常量,这一般会用到辅助类,这个类用于公开数据库常量,特别是列名等公共元素。
SQLiteOpenHelper是一个抽象类,可以用来实现创建,打开,升级数据库等操作。
public class DBOpenHelper extends SQLiteOpenHelper { private Context context = null ; public DBOpenHelper (Context context, String name, int version) { super (context, name, null , version); this .context = context; } public void onCreate (SQLiteDatabase db) { db.execSQL(AppConstant.CREATE_TABLE); } public void onUpgrade (SQLiteDatabase db, int oldVersion, int newVersion) { db.execSQL("drop table if exists" + AppConstant.TABLE_NAME); onCreate(db); } }
创建数据库所需的辅助常量 public class AppConstant { public static final String DATABASE_NAME = "userdatabase.db" ; public static final int DATABASE_VERSION = 1 ; public static final String TABLE_NAME = "usertable" ; public static final String USER_NAME = "username" ; public static final String USER_PWD = "userpwd" ; public static final String ID = "_id" ; public static final String CREATE_TABLE = "create table " +TABLE_NAME+"(" +ID+" integer primary key autoincrement, " +USER_NAME+" text not null, " +USER_PWD+" text not null );" ; }
创建数据库操作类 一般使用一个专门的类来执行对数据库条目的增删查改操作: 下面为数据库操作类的实现源码:
public class DBOperator { private SQLiteDatabase database = null ; private DBOpenHelper helper = null ; private Context context = null ; public DBOperator(Context context) { helper = new DBOpenHelper(context, AppConstant.DATABASE_NAME, AppConstant.DATABASE_VERSION); database = helper.getWritableDatabase(); this.context = context; } public String DataBasequry(String name ) { Cursor c = database .query(AppConstant.TABLE_NAME ,null ,AppConstant.USER_NAME+"= ?",new String[]{name }, null , null , null ); int count = c.getCount(); if (count>0 ){ c.moveToFirst(); String pwd = c.getString(c.getColumnIndex(AppConstant.USER_PWD)); c.close (); return pwd; }else { c.close (); return null ; } } public String ShowDataBase(){ Cursor c = database .query(AppConstant.TABLE_NAME ,null , null , null , null , null , null ); StringBuilder sb = new StringBuilder(); if (c.getCount()>0 ){ while (c.moveToNext()) { sb.append(c.getString(c.getColumnIndex(AppConstant.USER_NAME))+"\n"+c.getString(c.getColumnIndex(AppConstant.USER_PWD))+"\n"); sb.append("\n"); } c.close (); return sb.toString(); } return null ; } public void DataBasedelete(String name ){ database .delete (AppConstant.TABLE_NAME ,AppConstant.USER_NAME+"=?",new String[]{name }); } public void DataBaseinsert(String name ,String pwd){ ContentValues values = new ContentValues(); values .put(AppConstant.USER_NAME, name ); values .put(AppConstant.USER_PWD, pwd); database .insert (AppConstant.TABLE_NAME , null , values ); } public void DataBaseupdate(String name ,String pwd){ ContentValues values = new ContentValues(); values .put(AppConstant.USER_NAME, name ); values .put(AppConstant.USER_PWD, pwd); database .update (AppConstant.TABLE_NAME , values , AppConstant.USER_NAME+"=?",new String[]{name }); } }
Cursor 数据库查询结果作为Cursor对象返回,Cursor是底层数据库中的结果集指针。Cursor类包含了多个导航函数:
moveToFirst 把游标移动到查询结果中的第一行moveToNext 把游标向下移动到下一行moveToPrevious 把游标向上移动到上一行moveToPosition 把游标移动到指定行
getPostion 返回当前游标位置getColumnName 返回指定列索引的名称getColumnNames 返回当前Cursor中所有列名的字符串数组。getCount 返回结果集中的行数getColumnIndexOrThrow 返回具有指定名称的列的索引(如果不存在拥有该名称的列,就会抛出异常),索引从0 开始计数。getColumnIndex 与上述的方法功能相类似。
当列有可能在一些情况中不存在时,使用getColumnIndex并检查结果是否为-1比较合适,当可以保证所有情况中都存在的时候使用getColumnIndexOrThrow 较为合适。
Content Provider 内容提供者 Content Provider 提供了一个接口用来发布数据,通过Content Resolver来使用该数据,从而将使用数据的应用程序组件和底层的数据源分开。并提供了一种通用机制来允许一个应用程序共享它们的数据或者使用其他应用程序提供的数据。 Content Provider 内部如何保存数据由其设计者决定,但是所有的Content Provider 都实现一组通用的方法用来提供数据的增,删,查,改功能。 客户端通常不会直接调用这些方法,大多数是通过Content Resolver 对象实现对Content Provider 的操作,开发人员可以通过调用Activity或者其他应用程序组件的实现类中的getContentResolver来获取ContentResolver对象。
自定义Content Provider
创建一个Content Provider抽象类的子类:
public class MyContentProvider extends ContentProvider
这时候会有如下方法需要重写:
public boolean onCreate ( ) {} public Cursor query (Uri uri, String [] projection, String selection,String [] selectionArgs, String sortOrder ) {} public String getType (Uri uri ) {} public Uri insert (Uri uri, ContentValues values ) {} public int delete (Uri uri, String selection, String [] selectionArgs ) {} public int update (Uri uri, ContentValues values, String selection,String [] selectionArgs ) {}
注册Content Provider: 为了让Content Resolver能够找到Content Provider必须在Android Manifest文件中对其进行注册,Content Provider注册是通过标签来实现的,必须指定name属性和authorities这两个属性。 authorities属性用来标记Content Provider的基本URI。Content Resolver使用它来找到想要交互的数据库。每个authorities必须唯一,因此可以使用应用的包名。
<provider android:name ="com.example.contentprovider.CustomProvider" android:authorities ="com.example.provider.TestContentProvider" > </provider >
每个Content Provider 都需要使用一个公有的静态CONTENT_URI属性来公开它的授权。 CONTENT_URI可以按照如下形式定义:
public static final Uri CONTENT_URI = Uri .parse("content://com.example.provider.TestContentProvider/data" );
直接使用这种形式的查询表示请求所有行,而在结尾附加/rownumber的查询如:
content:// com.example.provider.TestContentProvider/data/ 1
所示表示请求一条记录。
通过解析可以确定这个URI是请求所有数据还是单行数据。
private static final int SINGLE_ROW = 1 ;private static final int MULTI_ROWS = 2 ;private static UriMatcher matcher = null ;static { matcher = new UriMatcher (UriMatcher.NO_MATCH); matcher.addURI("com.example.provider.TestContentProvider" ,"data" , MULTI_ROWS); matcher.addURI("com.example.provider.TestContentProvider" , "data/#" , SINGLE_ROW); }
实现SQLiteOpenHelper 子类 该部分实现见SQL总结部分。代码如下:public class DBConstant { public static final String DATABASE_NAME = "mydatabase.db" ; public static final int DATABASE_VERSION = 1 ; public static final String TABLE_NAME = "testtable" ; public static final String ID="_id" ; public static final String USERNAME="name" ; public static final String PASSWORD="pwd" ; public static final String CREATE_TABLE= "create table " +TABLE_NAME+"(" +ID +" integer primary key autoincrement," +USERNAME+" text not null," +PASSWORD+" text not null" +");" ; } public class DBOpenHelper extends SQLiteOpenHelper { public DBOpenHelper (Context context, String name, int version) { super (context, name, null, version); } public void onCreate (SQLiteDatabase db) { db.execSQL (DBConstant.CREATE_TABLE); } public void onUpgrade (SQLiteDatabase db, int oldVersion, int newVersion) { db.execSQL ("drop table if exists " +DBConstant.TABLE_NAME); onCreate (db); } }
实现Content Provider抽象类中的方法:
构造底层数据库
private DBOpenHelper mOpenHelper = null ;private SQLiteDatabase database = null ;public boolean onCreate () { mOpenHelper = new DBOpenHelper (getContext(),DBConstant.DATABASE_NAME,DBConstant.DATABASE_VERSION); return true ; }
在这里并不打开数据库,直到需到执行一个查询或者事务的时候再打开。
public Cursor query (Uri uri, String [] projection, String selection, String [] selectionArgs, String sortOrder) { database = mOpenHelper.getWritableDatabase (); SQLiteQueryBuilder querybuilder = new SQLiteQueryBuilder (); querybuilder.setTables (DBConstant.DATABASE_NAME); switch (matcher.match (uri)) { case SINGLE_ROW: String rowID = uri.getPathSegments ().get (1 ); querybuilder.appendWhere (DBConstant.ID+" = " +rowID); break ; default :break ; } Cursor cursor = querybuilder.query (database, projection, selection, selectionArgs,null,null, sortOrder); return cursor; }
实现getType方法 如果要处理的数据类型是一种比较新的数据类型,实现查询后,还必须指定一个MIME类型来标识返回的数据,通过重写getType方法来返回唯一地描述该数据类型的字符串,返回的数据类型包括两种形式 单一项:
vnd.android.cursor .item/vnd.<companyname> .<contenttype>
所有项:
vnd.android.cursor .dir/vnd.<companyname> .<contenttype>
public String getType (Uri uri) { switch (matcher.match (uri)) { case SINGLE_ROW: return "vnd.android.cursor.item/vnd.example.data" ; case MULTI_ROWS: return "vnd.android.cursor.dir/vnd.example.data" ; default : throw new IllegalArgumentException ("不支持当前Uri:" +uri); } }
public Uri insert (Uri uri, ContentValues values ) { database = mOpenHelper.getWritableDatabase(); long id = database .insert (DBConstant.TABLE_NAME , null , values ); if (id > -1 ){ Uri insertUri = ContentUris.withAppendedId(uri, id); getContext().getContentResolver().notifyChange(insertUri, null ); return insertUri; }else { return null ; } }
public int update(Uri uri, ContentValues values, String selection, String [] selectionArgs) { database = mOpenHelper.getWritableDatabase(); switch (matcher.match(uri)) { case SINGLE_ROW: String rowId = uri.getPathSegments().get (1 ); String whereID = DBConstant.ID +" = " +rowId; selection = ((selection == null ) ? selection:(whereID + " AND " +"(" +selection+")" )); break ; default :break ; } int updateCount = database.update(DBConstant.TABLE_NAME,values, selection,selectionArgs); getContext().getContentResolver().notifyChange(uri, null ); return updateCount; }
public int delete(Uri uri, String selection, String [] selectionArgs) { database = mOpenHelper.getWritableDatabase(); switch (matcher.match(uri)) { case SINGLE_ROW: String rowId = uri.getPathSegments().get (1 ); String whereId = DBConstant.ID+" = " +rowId; selection = (TextUtils.isEmpty(selection) ? selection:"( " +selection+" )" + " AND " +whereId); break ; default :break ; } if (selection==null ) {selection="1" ;} int deleteCount = database.delete(DBConstant.TABLE_NAME, selection,selectionArgs); getContext().getContentResolver().notifyChange(uri, null ); return deleteCount; }
要想返回删除项的数量,必须指定一条where子句,要删除所有行并返回一个值,则传入”1”
将当前Content Provider公开,并设置读写权限 : 首选定义readPermission:
<permission android:name ="com.example.provider.TestContentProvider.permission.READPROVIDER“ android:protectionLevel=" normal " ></permission >
定义writePermission:
<permission android:name ="com.example.provider.TestContentProvider.permission.WRITEPROVIDER" android:protectionLevel ="normal" ></permission >
将权限添加到Content provider
<provider………. name和 authorities定义 android:readPermission="com.example.provider.TestContentProvider.permission.READPROVIDER" android:writePermission="com.example.provider.TestContentProvider.permission.WRITEPROVIDER" android:exported="true" > </provider>
Content Resolver 当使用Content Provider公开数据到时候,Content Resoler是用来在这些Content Provider上进行查询和执行事务对应的类。Content Resolver 可以找到指定的ContentProvider并获取到Content Provider的数据。Content Resolver在开始的时候,Android系统将确定查询所需的ContentProvider ,并确认它是否启动并运行它。android系统负责初始化所有的内容提供者,不需要用户自己去创建。实际上Content Provider用户都不能直接访问到Content Provider实例,只能通过ContentResolver在中间代理。 每个应用程序都有一个ContentResolver实例,可使用getContentResolver方法来获取。
ContentResolver resolver = getContentResolver()
使用Content Resolver访问自定义的Provider
由于上述自定义的Content Provider设置了权限,因此要访问它的Resolver必须添加对应的权限才能访问,权限添加如下:
<uses-permission android:name ="com.example.provider.TestContentProvider.permission.READPROVIDER" /> <uses-permission android:name ="com.example.provider.TestContentProvider.permission.WRITEPROVIDER" />
为了验证能够通过一个应用访问另一个应用开放的内容提供者,编写了一个TestContentResolver程序来访问之前实现的TestContentProvider,两者关系如下图, TestContentResolver使用CONTENT_URI从找到对应的内容提供者,当TestContentResolver执行query方法的时候TestContentProvider 便会对应得调用自己的Content Provider.query方法。
public class MainActivity extends Activity implements OnClickListener { private static final String ID = "_id" ; private static final String USERNAME = "name" ; private static final String PASSWORD = "pwd" ; private static final Uri CONTENT_URI = Uri .parse("content://com.example.provider.TestContentProvider/data" ); private Button mInsertBtn = null ; private Button mDeleteBtn = null ; private Button mQueryBtn = null ; private Button mUpdateBtn = null ; private Button mDeleteAllBtn = null ; private TextView mShowResultTv = null ; private EditText mNameET = null ; private EditText mPasswordET = null ; private ContentResolver resolver = null ; protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_main); mInsertBtn = (Button) findViewById(R.id.insertBtn); mDeleteBtn = (Button) findViewById(R.id.deleteBtn); mDeleteAllBtn = (Button) findViewById(R.id.deleteAllBtn); mQueryBtn = (Button) findViewById(R.id.queryBtn); mUpdateBtn = (Button) findViewById(R.id.updateBtn); mShowResultTv = (TextView) findViewById(R.id.showResultTv); mNameET = (EditText) findViewById(R.id.nameEd); mPasswordET = (EditText) findViewById(R.id.pwdEd); mInsertBtn.setOnClickListener(this ); mDeleteBtn.setOnClickListener(this ); mDeleteAllBtn.setOnClickListener(this ); mQueryBtn.setOnClickListener(this ); mUpdateBtn.setOnClickListener(this ); resolver = getContentResolver(); mShowResultTv.setText(showResult()); } public void onClick (View v) { String name = mNameET.getText().toString().trim(); String password = mPasswordET.getText().toString().trim(); switch (v.getId()) { case R.id.queryBtn: if (!TextUtils.isEmpty(name)) { Cursor cursor = queryProviderbyName(name); while (cursor.moveToNext()) { Toast.makeText( MainActivity.this , name + "的密码为:" + cursor.getString(cursor .getColumnIndex(PASSWORD)), Toast.LENGTH_LONG).show(); } cursor.close(); } else { Toast.makeText(MainActivity.this , "请输入要查询用户的用户名" , 0 ).show(); } break ; case R.id.deleteBtn: if (!TextUtils.isEmpty(name)) { deleteProvider(name); mShowResultTv.setText(showResult()); } break ; case R.id.deleteAllBtn: deleteAllProvider(); String result = showResult(); if (result != null ) { mShowResultTv.setText(showResult()); } else { mShowResultTv.setText("" ); } break ; case R.id.insertBtn: if (!TextUtils.isEmpty(name) && !TextUtils.isEmpty(password)) { insertProvider(name, password); mShowResultTv.setText(showResult()); } else { Toast.makeText(MainActivity.this , "用户名和密码不能为空" , 0 ).show(); } break ; case R.id.updateBtn: if (!TextUtils.isEmpty(name) && !TextUtils.isEmpty(password)) { ContentValues values = new ContentValues (); values.put(USERNAME, "new_" + name); values.put(PASSWORD, "new_" + password); updateProvider(name, values); mShowResultTv.setText(showResult()); } break ; default : break ; } } private Cursor queryProviderbyName (String aname) { Cursor cursor = resolver.query(CONTENT_URI, null , USERNAME + "=?" , new String [] { aname }, null ); return cursor; } private Cursor queryProviderbyId (int aid) { Uri rowaddr = ContentUris.withAppendedId(CONTENT_URI, aid); Cursor cursor = resolver.query(rowaddr, null , null , null , null ); return cursor; } private Cursor queryProviderAll () { Cursor cursor = resolver.query(CONTENT_URI, null , null , null , null ); return cursor; } private void insertProvider (String aname, String apassword) { ContentValues values = new ContentValues (); values.put(USERNAME, aname); values.put(PASSWORD, apassword); resolver.insert(CONTENT_URI, values); } private void deleteProvider (String aname) { resolver.delete(CONTENT_URI, USERNAME + " = ?" , new String [] { aname }); } private void deleteAllProvider () { resolver.delete(CONTENT_URI, null , null ); } private void updateProvider (String aname, ContentValues avalue) { resolver.update(CONTENT_URI, avalue, USERNAME + " = ?" , new String [] { aname }); } private String showResult () { StringBuilder sb = new StringBuilder (); String name = null ; String pwd = null ; Cursor c = resolver.query(CONTENT_URI, null , null , null , null ); while (c.moveToNext()) { name = c.getString(c.getColumnIndex(USERNAME)); pwd = c.getString(c.getColumnIndex(PASSWORD)); sb.append(name + " " + pwd + "\n" ); } c.close(); return sb.toString(); } }
使用CursorLoader实现异步查询 数据库查询是耗时的操作,对于数据库Content Provider查询来说,最好不要在应用程序的主线程中执行,Android 3.0引入了Loader类来异步加载数据和监控底层数据源的变化。 Loader类可以实现从任何数据源加载任何数据类型的数据,其中最常用的为CursorLoader类,它允许针对Content Provider执行异步查询并返回一个结果Cursor CursorLoader能够在Activity或者Fragment中使用Cursor所需的所有管理任务,包括管理Cursor的生命周期以确保在Activity终止的时候关闭Cursor。 CursorLoader同样会监控底层查询的改变,所以不需要实现Content Observer。
实现Cursor Loader Callback 由于LoaderCallback是使用泛型实现的,所以在实现的时候应显示指定加载的类型,如下所示:
public class MainActivity extends Activity implements OnClickListener ,LoaderCallbacks<Cursor> { }
要使用Cursor Loader必须实现如下三个方法:
public Loader<Cursor> onCreateLoader(int id , Bundle args ) { }
public void onLoadFinished(Loader<Cursor > loader, Cursor c) { //当Loader Manager 已经完成异步查询的时候该方法将会被调用,并把结果Cursor 作为参数传入,可以这个函数中使用Cursor 更新UI元素和数据适配器 }
public void onLoaderReset(Loader<Cursor > arg0) { //当Loader Manager 重置Cursor Loader的时候,会调用该方法会被调用,在该方法中可以释放查询返回数据的引用,并重置相应的UI,但是不需要关闭Cursor ,Loader Manger会自动完成这个工作 }
使用CursorLoader来替换之前TestContentResolver代码中的showResult() 方法
public Loader<Cursor > onCreateLoader(int id, Bundle args) { CursorLoader loader = new CursorLoader(this, CONTENT_URI, null , null , null , null ); return loader; } public void onLoadFinished(Loader<Cursor > loader, Cursor c) { …………. while (c.moveToNext()) { name = c.getString(c.getColumnIndex(USERNAME)); pwd = c.getString(c.getColumnIndex(PASSWORD )); sb.append(name + " " + pwd + "\n"); } mShowResultTv.setText(sb.toString()); } public void onLoaderReset(Loader<Cursor > arg0) {}
private String showResult () { StringBuilder sb = new StringBuilder (); String name = null ; String pwd = null ; Cursor c = resolver.query(CONTENT_URI, null , null , null , null ); while (c.moveToNext()) { name = c.getString(c.getColumnIndex(USERNAME)); pwd = c.getString(c.getColumnIndex(PASSWORD)); sb.append(name + " " + pwd + "\n" ); } c.close(); return sb.toString(); }
数据交换形式 XML JSON XML 使用XmlSerializer创建一个XML文件 XmlSerializer xml = Xml .new Serializer() ; File file = new File(Environment.getExternalStorageDirectory () ,"xmlfile.xml" ); FileOutputStream fos= new FileOutputStream(file ) ; xml.setOutput(fos ,"utf-8" ) ; xml.startDocument("utf-8" ,true ) ; xml.startTag(null , "register" ) ; for (UserBean user:list ){ xml.startTag(null , "user" ) ; xml.attribute(null,"id" ,user.getId() +"" ); xml.startTag(null , "name" ) ; xml.text(user.getName() ); xml.end Tag(null , "name" ) ; xml.startTag(null , "password" ) ; xml.text(user.getPassword() ); xml.end Tag(null , "password" ) ; xml.end Tag(null , "user" ) ; }xml.end Tag(null ,"register" ) ; xml.end Document() ; fos.close() ; Toast . makeText(getApplicationContext () ,"创建成功" , 0 ).show() ;
使用XmlPushParser来解析一个SD卡中的Xml文件 XmlPullParser xml = Xml .new PullParser() ; File file = new File(Environment.getExternalStorageDirectory () ,"xmlfile.xml" ); FileInputStream fis; fis = new FileInputStream(file ) ; xml.setInput(fis , "utf-8" ) ; int type = xml.getEventType() ;while (type != XmlPullParser.END_DOCUMENT) { switch (type ) { case XmlPullParser.START_TAG: { if ("register" .equals(xml.getName() )) { list = new ArrayList<UserBean>() ; } else if ("user" .equals(xml.getName() )) { user = new UserBean() ; user.setId(Integer.parseInt (xml .getAttributeValue (0) )); } else if ("name" .equals(xml.getName() )) { user.setName(xml .nextText () ); } else if ("password" .equals(xml.getName() )) { user.setPassword(xml .nextText () ); } }break; case XmlPullParser.END_TAG: { if ("user" .equals(xml.getName() )){list .add(user);user = null;} } default:break; } type = xml.next() ; } fis.close() ;
使用XmlResourceParser解析xml文件夹下的Xml文件 XmlResourceParser xml = getResources().getXml(R.xml.xmlfile); int type ;type = xml .getEventType();while (type !=XmlResourceParser.END_DOCUMENT){ switch (type ) { case XmlResourceParser.START_TAG:{ if ("register".equals(xml .getName())){ list = new ArrayList<UserBean>(); }else if ("user".equals(xml .getName())){ user = new UserBean(); user .setId(Integer .parseInt(xml .getAttributeValue(0 ))); }else if ("name".equals(xml .getName())){ user .setName(xml .nextText()); }else if ("password".equals(xml .getName())){ user .setPassword(xml .nextText()); } }break; case XmlResourceParser.END_TAG:{ if ("user".equals(xml .getName())){ list.add (user ); user = null ; } } default :break; } type = xml .next(); }
JSON JSON: (JavaScript Object Notation ) JavaScript对象表示法,它是存储和交换文本信息的语法,类似XML,但是JSON比XML 更小、更快,更易解析,它具有如下特点: 1.它是轻量级的文本数据交换格式 2.它独立于语言和平台
JSON的具体语法以及使用可以查看JSON中国网站:http://www.json.org.cn/
下面是来自JSON中国网站上的JSON格式文本的一个例子: 该部分学习通过创建和解析这个JSON格式文本来学习在Android中JSON的使用。
{ "name" : "JSON中国" , "url" : "http://www.json.org.cn" , "page" : 88 , "isNonProfit" : true , "address" : { "street" : "浙大路38号." , "city" : "浙江杭州" , "country" : "中国" } , "links" : [ { "name" : "Google" , "url" : "http://www.google.com" } , { "name" : "Baidu" , "url" : "http://www.baidu.com" } , { "name" : "SoSo" , "url" : "http://www.SoSo.com" } ] }
Android JSON解析常用的类 Android的json解析部分都在包org.json下,主要有以下几个类:
JSON Object :JSON 对象,其包含一对(Key /Value )数值。最外被大括号包裹,其中的Key 和Value 被冒号":" 分隔。在Key 和Value 之间是以逗号"," 分隔。例如:{"JSON" : "Hello, World" }, Value 的类型包括:Boolean 、JSON Array 、JSON Object 、Number 、String 或者默认值JSON Object .NULL 。JSON Stringer:JSON 文本构建类 ,这个类可以帮助快速和便捷的创建JSON text。其最大的优点在于可以减少由于格式的错误导致程序异常,引用这个类可以自动严格按照JSON 语法规则创建JSON text。每个JSON Stringer实体只能对应创建一个JSON text。.JSON Array :它代表一组有序的数值。数值以逗号”,”分隔(例如 [value1,value2,value3]。它的内部具有查询行为,get ()和opt ()两种方法都可以通过index索引返回指定的数值,put ()方法用来添加或者替换数值。同样这个类的value类型可以包括:Boolean 、JSON Array 、JSON Object 、Number 、String 或者默认值JSON Object .NULL object 。JSON Tokener:Json 解析类 JSON Exception:Json 用到的异常类
创建JSON文件 下面为在SD卡根目录下使用JSONObject, JSONArray来创建上面提到的JSON文件的主要源码:
File jsonFile = new File(Environment.getExternalStorageDirectory () ,"json.txt" ); if (!jsonFile.exists() ) { jsonFile.createNewFile() ; JSONObject jsonRoot = new JSONObject() ; jsonRoot.put("name" , "JSON中国" ); jsonRoot.put("url" ,"http://www.json.org.cn" ); jsonRoot.put("page" , 88 ); jsonRoot.put("isNonProfit" , true ); JSONObject address = new JSONObject() ; address.put("street" , "浙大路38号" ); address.put("city" , "浙江杭州" ); address.put("country" , "中国" ); jsonRoot.put("address" , address); JSONArray array = new JSONArray() ; JSONObject gooleWebsite = new JSONObject() ; gooleWebsite.put("name" , "Google" ); gooleWebsite.put("url" , "http://www.google.com" ); array .put(gooleWebsite); JSONObject baiduWebsite = new JSONObject() ; baiduWebsite.put("name" , "Baidu" ); baiduWebsite.put("url" , "http://www.baidu.com" ); array .put(baiduWebsite); JSONObject sosoWebsite = new JSONObject() ; sosoWebsite.put("name" , "SoSo" ); sosoWebsite.put("url" , "http://www.SoSo.com" ); array .put(sosoWebsite); jsonRoot.put("links" , array ); FileOutputStream out = new FileOutputStream(jsonFile ) ; OutputStreamWriter writer = new OutputStreamWriter(out ) ; BufferedWriter bw = new BufferedWriter(writer ) ; bw.write(jsonRoot.to String() );
解析JSON格式的文件 InputStreamReader reader = new InputStreamReader(new FileInputStream(jsonFiles ) ); BufferedReader br = new BufferedReader(reader ) ; StringBuilder sb = new StringBuilder() ; String line = null; while ((line = br.readLine() )!=null){ sb.append(line+'\n' ); } JSONObject jsonsroot = new JSONObject(sb .toString () ); String name = jsonsroot.getString("name" ) ; String url = jsonsroot.getString("url" ) ; int page = jsonsroot.getInt("page" ) ;Boolean isNonProfit = jsonsroot.getBoolean("isNonProfit" ) ; JSONObject address = jsonsroot.getJSONObject("address" ) ; String street = address.getString("street" ) ; String city = address.getString("city" ) ; String country = address.getString("country" ) ; JSONArray links = jsonsroot.getJSONArray("links" ) ; Links linkArray[] = new Links[links .length ()] ; for (int looper=0 ;looper<links.length() ;looper++){ JSONObject Obj = links.getJSONObject(looper ) ; linkArray[looper ] = new Links() ; linkArray[looper ] .setName(Obj.getString ("name" ) ); linkArray[looper ] .setUrl(Obj.getString ("url" ) ); } String result = "name:" +name+'\n' +"url:" +url+'\n' +"page:" +page+'\n' +"isNonProfit:" +isNonProfit+'\n' +"street:" +street+'\n' +"city:" +city+'\n' +"country:" +country+'\n' +"linkArray[0].name:" +linkArray[0 ] .getName() +'\n' +"linkArray[0].url:" +linkArray[0 ] .getUrl() +'\n' +"linkArray[1].name:" +linkArray[1 ] .getName() +'\n' +"linkArray[1].url:" +linkArray[1 ] .getUrl() +'\n' +"linkArray[2].name:" +linkArray[2 ] .getName() +'\n' +"linkArray[2].url:" +linkArray[2 ] .getUrl() +'\n' ; mShowJsonTv.setText(result ) ;