之前介绍了Android系统下三种数据存储形式,今天补充介绍另外两种,分别是内容提供者和网络存储。有些人可能认为内存提供者和网络存储更偏向于对数据的操作而不是数据的存储,但这两种方式确实与数据有关,所以这里还是将这两种形式简要的说明一下。
Content Provider:
Content Provider,中文名是内存提供者,Android四大组件之一,内容提供者是应用程序之间共享数据的接口,以数据库形式存入手机内存,可以共享自己的数据给其他应用使用。之所以需要设计一个单独的控件来操作数据,是为了实现应用程序之间的数据传递。通过查看DDMS中的目录结构可以看出,数据库文件对于其他应用来说是不可读、不可写,而日常生活中又需要获取其他应用的数据,尤其是系统自带软件的数据。比如打开QQ或者微信时会提示是否同步联系人,又比如备份短信的时候,这些都需要访问和操作其他应用的数据库。因此谷歌工程师在底层软件中集成了大量的方法利用内存提供者的原理,类似于在数据库中提供一个对外访问的路径,供其他应用访问。
为了更好的理解内存提供者的工作原理,可以自定义一个内容提示者来帮助理解。首先写一个类继承ContentProvider,实现该类中的方法,包括一些增删改查和数据初始化的方法,可以在方法中实现对数据库的增删改查操作。数据库本来是不对外开放的,所以为保护数据,类中的方法原始返回数据均是空类型。为保证数据的安全性,可以创建一个UriMatcher对象,利用addURIf方法添加Uri的路径规则,在每一次进行数据操作时先判断传入的路径是否符合命名规则。使用内存提供者需要在配置文件中添加provider标签,指定主机名。只有当访问者与内容提供者的主机名一致时,才可以建立数据连接。在另一个应用中实现对内存提供者的访问。具体操作是:创建内容提供者解析器,定义要访问的Uri的路径。Uri路径有着固定的格式:”content://主机名/匹配字符”。 利用内容提供者解析器进行增删改查,和要操作的数据库之间建立联系。以上内容通常用来理解内容提供者的工作原理,实际工作中很少用到自定义的内容提示者。实际中用的比较多的是用内容提供者操作系统联系人、系统短信等系统应用的数据库。
内容提供者操作系统应用时相对简单,需要用到的大部分程序已经在底层实现,要做的是调用各种方法和相关的参数。需要关注的参数有Uri路径、数据库的表单结构。可以通过查看底层的代码获取相应的参数。其中有些常用的参数可以记下来,方便调用。比如获取全部短信的Uri路径是: content://sms。与联系人有关的数据库表单有三个raw_contacts、data、mimetypes。操作raw_contacts 表的Uri是: content://com.android.contacts/raw_contacts,操作data 表的Uri是: content://com.android.contacts/data。本文以短信的备份、还原来演示利用内容提供者访问短信数据库。
先看一下短信和手机联系人有关的数据库所在的路径。短信在Android 模拟器下存放在的路径是:/data/data/com.android.providers.telephony/databases/目录,联系人在Android 模拟器下存放在的路径是:/data/data/com.android.providers.contacts/databases/目录。对于短信数据库我们关心的表数据有:address、type、body、date,分别表示发送者号码、短信类型(收还是发)、短信内容、日期。对于联系人数据库的三张表一定要按照一定的顺序依次查找才能得到相关的数据,在这不做解释。尽管开发的时候不需要了解短信和手机联系人的数据库路径,但是要明白短信和手机联系人的数据是存在数据库中的,同时数据库对外是不开放的。
与短信有关的数据库的目录结构:
本文给出的案例是短信的备份和还原,从而实现对系统应用数据库的操作。首先利用内容提供者查询到短信数据库里的详细参数,将该数据以Xml文件的形式存入到指定的文件夹。利用xml解析得到数据,将获取的数据存在一个工具类中,这样就能用ListView将数据显示在界面上。具体实现如下。
package com.example.contentprovider; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.util.ArrayList; import java.util.List; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlSerializer; import android.app.Activity; import android.content.ContentResolver; import android.database.Cursor; import android.net.Uri; import android.os.Bundle; import android.util.Log; import android.util.Xml; import android.view.View; import android.view.ViewGroup; import android.widget.BaseAdapter; import android.widget.ListView; import android.widget.TextView; import android.widget.Toast; /** * 短信的备份和还原 * 添加写短信和读短信的权限 * <uses-permission android:name="android.permission.READ_SMS"/> <uses-permission android:name="android.permission.WRITE_SMS"/> * @author Huang */ public class MainActivity extends Activity { private static final String TAG = "MainActivity"; private ListView lv; private List<Person> mlist; private Myadpter adapter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); lv = (ListView) findViewById(R.id.lv); datafresh(); } //数据刷新,一般用到ListView时最好记得刷新数据否则不显示 public void datafresh(){ mlist = getList(); if(adapter == null){ adapter = new Myadpter(); }else { adapter.notifyDataSetChanged(); } } //用ListView显示短信内容 public class Myadpter extends BaseAdapter{ public int getCount() { // TODO Auto-generated method stub return mlist.size(); } public Object getItem(int position) { // TODO Auto-generated method stub return null; } public long getItemId(int position) { // TODO Auto-generated method stub return 0; } public View getView(int position, View convertView, ViewGroup parent) { TextView tv = new TextView(MainActivity.this); tv.setText(mlist.get(position).toString()); return tv; } } //短信备份 public void bankup(View view){ ContentResolver resolver = getContentResolver(); Uri uri = Uri.parse("content://sms"); Cursor cursor = resolver.query(uri, new String[]{"address","body","type","date"}, null, null, null); while(cursor.moveToNext()){ String address = cursor.getString(0); String body = cursor.getString(1); String type = cursor.getString(2); String date = cursor.getString(3); //序列化,把短信以Xml文件的形式存储 XmlSerializer serializer = Xml.newSerializer(); File file = new File(getFilesDir(),"info.xml"); try { FileOutputStream fos = new FileOutputStream(file); serializer.setOutput(fos, "utf-8"); serializer.startDocument("utf-8", true); serializer.startTag(null, "person"); serializer.startTag(null, "address"); serializer.text(address); serializer.endTag(null, "address"); serializer.startTag(null, "body"); serializer.text(body); serializer.endTag(null, "body"); serializer.startTag(null, "type"); serializer.text(type); serializer.endTag(null, "type"); serializer.startTag(null, "date"); serializer.text(date); serializer.endTag(null, "date"); serializer.endTag(null, "person"); serializer.endDocument(); fos.close(); // Toast.makeText(this, "数据保存成功", 0).show(); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } } public void restore(View view){ datafresh(); lv.setAdapter(adapter); } //利用Xml解析得到短信内容 private List<Person> getList() { ContentResolver resolver = getContentResolver(); List<Person> list = new ArrayList();//创建一个Person类存储标签内容 Person p = new Person(); File file = new File(getFilesDir(),"info.xml"); XmlPullParser pullParser = Xml.newPullParser(); try { FileInputStream fis = new FileInputStream(file); pullParser.setInput(fis, "utf-8"); int mtype = pullParser.getEventType(); while(mtype != XmlPullParser.END_DOCUMENT){ String name = pullParser.getName(); switch (mtype){ case XmlPullParser.START_TAG: if("address".equals(name)){ String address = pullParser.nextText(); p.setAddress(address); }else if("body".equals(name)){ String body = pullParser.nextText(); p.setBody(body); }else if("type".equals(name)){ String type = pullParser.nextText(); p.setType(type); }else if("date".equals(name)){ String date = pullParser.nextText(); p.setDate(date); } break; case XmlPullParser.END_TAG: if("person".equals(name)){ list.add(p); } break; } mtype = pullParser.next(); } Log.i(TAG, list.toString()); return list; } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); return null; } } }
效果演示,我的虚拟机中只存了一条短信:
网络存储:
网络存储是最容易理解的一种存储方式了。其实说简单点就是文件的上传和下载。经常听到的云备份就是这种形式。优势也很明显,即把数据存储到服务器,不存储在本地,使用的时候直接从网络获取,避免了手机端信息丢失以及其他的安全隐患。因此,对于这种形式就不作多的解释,直接给出一个文件的上传和下载的实例来演示网络存储。
代码实现:
package com.example.upload; import java.io.File; import java.io.FileNotFoundException; import java.io.InputStream; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; import org.apache.http.Header; import com.loopj.android.http.AsyncHttpClient; import com.loopj.android.http.AsyncHttpResponseHandler; import com.loopj.android.http.RequestParams; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.app.Activity; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.text.TextUtils; import android.view.Menu; import android.view.View; import android.widget.EditText; import android.widget.ImageView; import android.widget.Toast; public class MainActivity extends Activity { protected static final int SUCCESS = 1; protected static final int ERORR = 2; private EditText et_path; private ImageView iv; //访问网络操作耗时,需要在子线程中加一个代理 private Handler handler = new Handler(){ @Override public void handleMessage(Message msg) { switch (msg.what) { case SUCCESS: Bitmap bm = (Bitmap) msg.obj; iv.setImageBitmap(bm); break; case ERORR: Toast.makeText(MainActivity.this, "图片获取失败", 0).show(); break; } super.handleMessage(msg); }}; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); et_path = (EditText) findViewById(R.id.et_path); iv = (ImageView) findViewById(R.id.iv); } //上传程序 public void upload(View view){ // String path = et_path.getText().toString().trim(); // 这里为方便把路径写死,这种方式不太正规,一般可以读et_path来访问 String path = "/mnt/sdcard/info.txt"; if(TextUtils.isEmpty(path)){ Toast.makeText(this, "路径不能为空", 0).show(); return; } File file = new File(path); AsyncHttpClient client = new AsyncHttpClient(); RequestParams param = new RequestParams(); try { param.put("file", file); } catch (FileNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } client.post("http://192.168.1.114:8080/",param, new AsyncHttpResponseHandler() { @Override public void onSuccess(int statusCode, Header[] headers, byte[] responseBody) { // TODO Auto-generated method stub Toast.makeText(MainActivity.this, "提交成功", 0).show(); } @Override public void onFailure(int statusCode, Header[] headers, byte[] responseBody, Throwable error) { // TODO Auto-generated method stub Toast.makeText(MainActivity.this, "提交失败", 0).show(); } }); } //下载程序 public void download(View view){ new Thread(){ public void run() { try { //这里为方便把路径写死,可以读et_path来访问,两者结果一样 URL url = new URL("http://192.168.1.114:8080/demo1.png"); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setConnectTimeout(5000); conn.setRequestMethod("GET"); int code = conn.getResponseCode(); if(code == 200){ InputStream is = conn.getInputStream(); Bitmap bitmap = BitmapFactory.decodeStream(is); Message msg = Message.obtain(); msg.what = SUCCESS; msg.obj = bitmap; handler.sendMessage(msg); is.close(); } } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); Message msg = Message.obtain(); msg.what = ERORR; handler.sendMessage(msg); } }; }.start(); } }
至此五种数据存储全部实现。当然,实际开发中可能比这更复杂,会嵌入到别的知识点中,但数据操作无疑是最为基本的一步,是整体项目开发的基础。