2009年4月1日星期三

Android开发_第三章




第三章 开发应用





第一节 实现用户接口





第一小节 屏幕条目的分层结构


Android应用的基本功能单元是activity,一个android.app.Activity类对象。一个activity能实现很多事情,但是他本身没有屏幕呈现功能。实现屏幕呈现和设计UI,可以使用views和viewgroups,Android平台的基本用户接口单元。



Views

一个view是一个基类android.view.View的对象,它是一个数据结构,它保存了一块特定方形屏幕区域的布局和内容。一个View对象处理测量、放置、画布、焦点改变、滚动条和键盘/姿态响应。



view类是widgets(完全实现屏幕交互条目的类集)的一个基类。Widgets处理自己色测量、画布,因此你能更快的建立自己的UI。Widgets包括Text, EditText, InputMethod, MovementMethod, Button, RadioButton, CheckBox和ScrollView。



Viewgroups

一个viewgroup是android.view.Viewgroup的对象。见词释义,一个viewgroup是一些特定类型view对象的集合,viewgroup能给你的UI增加架构,并且为单个实体建立复杂的屏幕条目。



viewgroup类是layout(完全实现屏幕布局的类集)的一个基类。这些布局能够用来实现view集的架构。



树形结构UI

在Android平台上,用view或viewgroup定义一个activity的UI,象下图所示。这个树可以是很简单或很复杂的,你可以用widget集和布局或客户化的view类型来建立它。






要把这个树放到屏幕上渲染,activity调用setContentView()方法,传递一个参考给根节点。一旦android具有根节点对象的参考,就可以直接和节点失效、测量和画这个树。根节点接着要求子节点画他们自己,当然,树上的每个veiwgroup直接画它的子节点。



就象之前所说的,每个viewgroup测量它的有效的空间,布置它的子节点,让每个子节点调用Draw()方法渲染自己。每个子节点需要在父节点的大小和位置,但是父对象有最后的决定权决定每个子节点放在哪个位置、占多大空间。



LayoutParams

每个viewgroup用嵌套的扩展了ViewGroup.LayoutParams的类。这些子类定义了儿子的大小和位置。




注意每个LayoutParams子类有它自己的语法。每个子条目必须定义LayoutParams相应它的父母,虽然它也许为它的儿子定义不同的LayoutParams。



所有的viewgroup都包括高度和宽度,许多包括边界空白和界宽。你可以指定宽度和高度,虽然你也许不想这样做。更多时候也许高速你的view去适配它的内容边界大小,或者变成它所包括的对象一样大。






第二小节 常用的布局对象


FrameLayout

FrameLayout是一个简单的布局对象,在你的屏幕上保留一部分空白的空间,以后你可以用简单对象在填充这个空间。所有的子条目都定位于屏幕的左上角,你不能指定FrameLayout儿子的位置。后来的子对象将简单的部分或全部的覆盖更早的对象,或者模糊化它(假定新的对象是透明的)。



LinearLayout

一个LinearLayout分配所有的子对象在单个方向-水平或垂直,取决于你在LinearLayout上设置的属性。所有子对象都是一个跟着一个堆叠,因此一个垂直列表每行只有一个子对象,不管他们占多宽,一个水平列表只有一行的高度(它的高度就是最高的子对象的高度加上填充)。LinearLayout保持子对象之间的边界,和gravity(子对象的对齐方式:左对齐、右对齐、中心对齐等)。



LinearLayout也支持分配一个权重给子对象。这个值允许子对象扩展它自己来填充任何屏幕上的空白。这样可以防止一群小对象被绑定在大屏幕的边角上,允许他们扩展空间。子对象指定一个权重值,任何余下的空间将按比例分配给这些子对象。缺省权重是0,例如,假定有三个文本框,其中两个权重为1,则这两个将等比例的扩展来填充余下的空间,第三个将不会有任何变化。



下面两个表单呈现了LinearLayout布局:一个Button,一些Label,一些TextBox,加上一些填充值。TextBox设置它的宽度为FILL_PARENT,其它的设置为WRAP_CONTENT。对齐点,缺省在左边,左边的表单没有设置权重(就是缺省值0),右边表单的comments文本框权重设置为1。加入Name文本框也被设置成1,那么Name文本框和comments文本框具有相同的高度。




在水平的LinearLayout,这些子对象会以文本基线对齐。这样可以方便使用者撇一眼文本的时候,不至于对不齐而看走眼。当然这个也可以通过设置布局xml文件的android:baselineAligned="false"来关闭。



TableLayout

TableLayout以行列方式定位它的子对象,一个tablelayout有一个或多个TableRow对象组成,每个TableRow对象定义一行(其实,你可以有其它子对象)。TableLayout容器并不显示行、列、单元格的边界,每行有0个或多个单元格,每个单元格可放一个一个View对象。一个表有许多列,一个表可以有空单元格。单元格不能产生列,跟在HTML里面一样。下面图片显示了TableLayout,带有不可见的点线边界。




列可以被隐藏,能够延展来填充整个屏幕空间,也可以收缩来适应适应屏幕。



AbsoluteLayout

AbsoluteLayout必须指定屏幕上的x/y坐标,(0,0)为屏幕的左上角,坐标值随着向右向下而增加。边界空白不支持,重叠允许(但不建议)。通常不建议使用AbsoluteLayout,除非必须,因为它是刚性的,可能在不同设备上工作不一致。



RelativeLayout

RelativeLayout子对象指定相对于其它对象的相对位置。因此可以分配两个对象在右边界,或者让一个在另一个下面,或者处于屏幕中心。这些条目按照给定顺序渲染,假如第一个条目在屏幕中心,其它相对于它的条目将会相对于屏幕中心。假如用xml文件指定布局,必须首先指定一个参考条目。



这是个例子,包括可见和不可见条目。




本图显示了屏幕对象的类名,以及他们的属性。这些属性有些被这些条目直接支持,一些通过LayoutParams成员支持(屏幕上所有对象都是RelativeLayout的子条目)。RelativeLayout参数是宽度、高度、在下面、在顶上、在左边、填充和左边界。注意这些参数中的一些是相对于其它子对象,包括在左边、在顶上、在下面等等。



一些重要的视图组





































































描述


AbsoluteLayout


绝对位置布局


FrameLayout


框架布局


Gallery


水平滚动布局


GridView


网格布局


LinearLayout


线性布局


ListView


列表布局


RelativeLayout


相对布局


ScrollView


垂直滚动布局


Spinner


在一行文本框里一次显示一个条目


SurfaceView


直接访问画布


TabHost


标签列表布局


TableLayout


表格布局


ViewFlipper


幻灯式布局


ViewSwitcher


同上ViewFlipper








第三小节 和AdapterView工作(绑定数据)


一些视图组有UI,这些对象具有典型子类AdapterView。例子包括Gallery和ListView,这些对象通常要做两个工作:



  • 用数据填充布局




  • 处理用户选择




用数据填充布局

绑定类到Adapter,Adapter从代码得到数据,或者从设备数据库查询到结果。

// Get a Spinner and bind it to an ArrayAdapter that 
// references a String array.
Spinner s1 = (Spinner) findViewById(R.id.spinner1);
ArrayAdapter adapter = ArrayAdapter.createFromResource(
this, R.array.colors, android.R.layout.simple_spinner_item);
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
s1.setAdapter(adapter);

// Load a Spinner and bind it to a data query.
private static String[] PROJECTION = new String[] {
People._ID, People.NAME
};

Spinner s2 = (Spinner) findViewById(R.id.spinner2);
Cursor cur = managedQuery(People.CONTENT_URI, PROJECTION, null, null);

SimpleCursorAdapter adapter2 = new SimpleCursorAdapter(this,
android.R.layout.simple_spinner_item, // Use a template
// that displays a
// text view
cur, // Give the cursor to the list adatper
new String[] {People.NAME}, // Map the NAME column in the
// people database to...
new int[] {android.R.id.text1}); // The "text1" view defined in
// the XML template

adapter2.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
s2.setAdapter(adapter2);

注意:在CursorAdapter的PROJECTION里面的People._ID是必须的,否则会得到一个异常。



处理用户选择

通过设置类的AdapterView.OnItemClickListener成员到监听器,能够捕获选择变化。

// Create a message handling object as an anonymous class.
private OnItemClickListener mMessageClickedHandler = new OnItemClickListener() {
public void onItemClick(AdapterView parent, View v, int position, long id)
{
// Display a messagebox.
Toast.makeText(mContext,"You've got an event",Toast.LENGTH_SHORT).show();
}
};

// Now hook into our object and set its onItemClickListener member
// to our class handler object.
mHistoryView = (ListView)findViewById(R.id.history);
mHistoryView.setOnItemClickListener(mMessageClickedHandler);






第四小节 用XML设计屏幕布局


因为在代码里设计屏幕布局是繁笨的,Android支持通过XML来设计屏幕。Android定义了大量的客户化条目,每个条目代表一个特定的Android View子类。可以象建立HTML文件一样设计屏幕布局,用一系列的标记,保存在应用的res/layout/目录的XML文件里。要学习XML文件格式和哪些条目被使用,请参考布局资源章节。每个文件描述一个单个的android.view.View,也可以是一些简单的可视化的条目,或者包括子对象的布局条目。当Android编译应用时,它编译每个文件到android.view.View资源,可以用setContentView(R.layout.layout_file_name)代码在Activity.onCreate()实现里面来装载。



每个XML文件有Android GUI类的一些标记组成,这些标记有类方法对应的属性(例如,EditText有text属性对应于EditText.setText)。



注意:类和方法名字之间、条目和属性名字之间不是刚好1:1的关系,虽然大部分情况下是1:1关系。



Android趋向于按照呈现在XML文件里面的顺序绘制各个条目。因此,假如条目有重叠,在XML文件的最后一个绘制在顶层。



每个XML文件被编译成一颗树,树根是单个的View或ViewGroup对象,因此必须有单个的根标记。



命名为layout_something的属性是LayoutParams成员。



下面这些值可以用来作度量衡:



  • px (pixels) 点


  • dip (device independent pixels) 独立于设备的点


  • sp (scaled pixels — best for text size) 扩展点


  • pt (points) 点


  • in (inches) 英寸


  • mm (millimeters) 毫米


例如:android:layout_width="25px"



下面的XML文件建立了屏幕布局,注意屏幕顶部的文本由Activity.setTitle设置。注意,参考相对条目的属性用ID相关资源来参考(@id/id_number)。

<?xml version="1.0" encoding="utf-8"?>
<!-- Demonstrates using a relative layout to create a form -->
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:background="@drawable/blue"
android:padding="10px">

<TextView id="@+id/label"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Type here:"/>

<EditText id="@+id/entry"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:background="@android:drawable/editbox_background"
android:layout_below="@id/label"/>

<Button id="@+id/ok"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/entry"
android:layout_alignParentRight="true"
android:layout_marginLeft="10px"
android:text="OK" />

<Button android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toLeftOf="@id/ok"
android:layout_alignTop="@id/ok"
android:text="Cancel" />
</RelativeLayout>

装载XML资源

装载这些布局资源非常容易,只要简单调用onCreate()方法:

protected void onCreate(Bundle savedValues)
{
// Be sure to call the super class.
super.onCreate(savedValues);

// Load the compiled layout resource into the window's
// default ViewGroup.
// The source file is res/layout/hello_activity.xml
setContentView(R.layout.hello_activity);

// Retrieve any important stored values.
restoreValues(savedValues);
}


第五小节 屏幕条目钩子


可以通过Activity.findViewById得到屏幕条目句柄,可以用这个句柄设置或恢复任何值:

TextView msgTextView = (TextView)findViewById(R.id.msg);
msgTextView.setText(R.string.push_me);




第六小节 UI通知的监听


一些UI通知自动地被Android触发。实例,Activity的onKeyDown和onKeyUp,和Widget的onFocusChanged(boolean,int,Rect)。但是呢,一些重要的回调函数,例如按钮点击,不能自动通知,必须手工注册。

public class SendResult extends Activity
{
/**
* Initialization of the Screen after it is first created. Must at least
* call setContentView() to
* describe what is to be displayed in the screen.
*/
protected void onCreate(Bundle savedValues)
{
...

// Listen for button clicks.
Button button = (Button)findViewById(R.id.corky);
button.setOnClickListener(mCorkyListener);
}

// Create an anonymous class to act as a button click listener.
private OnClickListener mCorkyListener = new OnClickListener()
{
public void onClick(View v)
{
// To send a result, simply call setResult() before your
// activity is finished, building an Intent with the data
// you wish to send.
Intent data = new Intent();
data.setAction("Corky!");
setResult(RESULT_OK, data);
finish();
}
};


第七小节 在应用上使用主题


假如你不明确指定应用主题,Android将使用缺省的由android.R.style.Theme定义的主题。多数情况下你想使用不同的主题(如Theme.Light)或建立自己的主题。



为了在XML里面设置主题,在AndroidManifest.xml文件里用theme属性来指定,可以使用<application>标记来指定缺省主题,用<activity>控制部分主题。

<!-- AndroidManifest.xml--> 
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.home">
<application android:theme="@android:style/Theme.Dark" >
<activity class=".Home"
...
</activity>
</application>
</manifest>

也可以用编程方式来实现主题,确保在建立任何view之前设置主题,以便正确的主题被用于你的所有UI条目。

 protected void onCreate(Bundle icicle) {
super.onCreate(icicle);
...
setTheme(android.R.style.Theme_Light);
setContentView(R.layout.linear_layout_3);
}




第八小节 UI条目和概念术语


Activity:

Android应用的标准屏幕。



View:

屏幕上的一个矩形区域。



ViewGroup:

包含多个View对象。



Widget:

格式化条目。



Drawable:

能够装进其它UI的可视化条目。



Panel:

面板



Dialog:

对话框



Window:

窗口



Surface:

表面,能装载画布对象。



SurfaceView:

表面视图



Canvas:

画布



OpenGL ES:

实现3D图形的库。




第二节 Android建筑模块


如果认为Android应用是一个各种类型的组件集,这些组件大部分都是松耦合,你可以精确的描述这些组件的联合而不是简单的描述一个应用。



通常,这些组件都运行在一个相同的系统进程里面,当然可以在这个进程里面建立多个线程,也可以建立完全独立的子进程,这种情况并不通用,因为Android进程对用户透明。



这些是Android API最重要的部分:




AndroidManifest.xml:


它是应用的控制文件,告诉系统你的顶级组件(activities, services, intent receivers, content providers)要做些什么,例如,"glue"指定了你的应用所接收的Intent。




Activities:


它是具有生命周期的一个基本对象,实现一些主要的代码工作,如果需要,显示一个UI给用户。典型的,你会设计一个activity作为你应用的入口点。




Views:


一个view是一个对象,知道自己应该怎样显示在屏幕上,Android用户接口由view树组成,假如你想执行一些客户化的图形技术(比如建立游戏,或者一些不常用的UI widget),你就要建立一个view。




Intents:


表示一个简单的消息对象,能够处理一些感兴趣的事情。例如,如果你的应用想要显示一些web页,它通过intent传递URI,系统定位一些能够处理这个intent事情的代码,并且运行它。Intent也能用于广播一些系统感兴趣的事件。




Services:


它是运行在后台的一块代码,能运行在自己的进程里,也可以运行在其它应用的进程里。其它组件绑定到service,通过远程过程调用来执行它。例如,媒体播放器,当用户退出媒体内容选择UI,用户可能还要继续播放,这时候service就可以实现这样的功能。




Notifications:


它是显示在状态条的小图标,用户能够和这个图标交互来获得信息,最好的例子是SMS消息、呼叫历史和语音邮件。当然应用可以建立自己的notification。Notification是一个有力的机制,用来通知用户有些事情需要处理或注意。



ContentProviders:

它是数据仓库,提供数据给设备来访问,经典的例子是用它来访问联系人列表。你的应用能够通过contentProvider访问其它应用给出的数据,也可以定义自己的contentProvider给自己提供数据。




第一小节 AndroidManifest.xml


任何应用都需要AndroidManifest.xml文件,他放在应用的根目录下,描述一些全局值,包括哪些应用组件、每个组件的实现类、哪些类型数据能处理,他们能从哪里装载。



这个文件的一个重要方面是intent过滤,这些过滤器描述了activity能从哪里、什么时候开始。当activity想去执行一个行为,例如打开一个web页,或者打开一个联系人选择条目,它建立一个intent对象。这个对象能够保持几个描述器描述你所想做的,你想处理什么数据,数据的类型和其它信息位。Android为每个应用在intent对象和intent过滤器里比较,找到最适合处理这个数据的应用,或者实现调用者的行为。



除了宣布应用的activity, content provider, service和intent receiver,还可以在androidmanifest.xml里面指定一些权限和指令。



例子,简单的androidmanifest.xml:

<?xml version="1.0" encoding="utf-8"?>

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.my_domain.app.helloactivity">

<application android:label="@string/app_name">

<activity android:name=".HelloActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>

</application>

</manifest>

一些常用条目:



  • 每个androidManifest.xml文件包括名称空间宣布xmlns:android="http://schemas.android.com/apk/res/android,它处于第一个的位置。这个使得大量的标准android属性有效,它能提供大量的数据和条目。


  • 大多数的manifest包括单个<application>条目,定义了包里的所有应用级别的组件和属性。




  • 呈现给用户的任何包作为一个顶级应用是有效的,需要包括至少一个activity组件支持MAIN行为和LAUNCHER范围。




这是一些androidmanifest.xml的细节轮廓,有效的标记:


<manifest>

文件的根节点,在它下面可以放置:


    <uses-permission>


    请求安全性允许,为了使你的包能正确工作。一个manifest能包含0个或多个本条目。


    <permission>


    安全性允许,用来限制包里面的哪些应用能够访问哪些组件和特性。一个manifest能包含0个或多个本条目。


    <instrumentation>


    用来测试本包或其它包的功能性。一个manifest能包含0个或多个本条目。




    <application>


应用级别的根条目,包括应用的全局的或缺省的属性。例如label, icon, theme, 需要的允许等等。一个manifest包括0个或1个本条目,在它下面可方0个或多个下面的条目:


        <activity>


activity是用户和应用交互的基本工具,用户装载应用后首先看到的就是activity,更多其它的屏幕将被独立的activity来实现。



一个activity必须有一个<activity>标记。假如一个activity在manifest里面没有匹配的标记,你就不能装载它。



        另外,可以包含一个或多个<intent-filter>标记。


            <intent-filter>


            用IntentFilter的格式定义组件所支持的intent集。


                <action>


                组件支持的Intent行为。


                <category>


                组件支持的Intent范围。


                <data>


组件支持的Intent data MIME type, Intent data URI scheme, Intent data URI authority, Intent data URI path。



            你可以让activity跟一些meta data通信:




            <meta-data>


            给activity增加新的meta data,客户端能够通过ComponentInfo.metaData恢复。


        <receiver>


一个BroadcastReceiver允许应用得到数据或行为改变的通知,甚至当前它没有运行。和<activity>标记一起,你可以包含一个或多个receiver支持的<intent-filter>条目或<meta-data>值。


        <service>


一个service是一个能运行在后台一定时间的组件,和<activity>标记一起,你可以包含一个或多个receiver支持的<intent-filter>条目或<meta-data>值。


        <provider>


一个ContentProvider是一个管理稳定数据,并且发布数据给其它应用使用的组件。可以使用一个或多个<meta-data>条目。







第三节 保存、恢复和披露数据




一个典型的桌面操作系统提供一个通用的文件系统,任何应用能够存取其它应用的文件,可能需要一些存取控制。Android提供不同的系统:所有的应用数据都是私有的,当然也提供一个标准的方法披露它的数据给其它应用。这一章描述很多不同的方法供应用保存和恢复数据,提供数据给其它应用,怎么请求其它应用的数据。



Preferences:

一个轻量级的方法提供元数据类型的键、值对。典型的用来保存应用的参数。



Files:

可以保存文件在设备或可移动存取介质上,缺省情况下其它应用不能访问这些文件。



Databases:

支持SQLite,应用可以建立自己的私有SQLite数据库。



Content Providers:

内容提供者是可选的应用组件,它对于应用的私有数据披露其读写访问,当然可以加一些访问限制。内容提供者提供标准数据请求语法,和一个标准的访问机制。Androis实现了一些标准数据类型的内容提供者,比如个人的联系人信息。



Network:

也可以使用网络来保存和恢复数据。




第一小节 使用应用Preferences


保存诸如缺省问候语、文本字体之类的应用参数。调用Context.getSharedPreferences()来读写数据。假如想跟同一个包里的其它组件共享这些数据,需要给参数集一个名字,或者用Activity.getPreferences()不带名字保持对调用activity的私有。不能跨包共享参数集,下面是个例子:

public class Calc extends Activity {
public static final String PREFS_NAME = "MyPrefsFile";
...

@Override
protected void onCreate(Bundle state){
super.onCreate(state);

...

// Restore preferences
SharedPreferences settings = getSharedPreferences(PREFS_NAME, 0);
boolean silent = settings.getBoolean("silentMode", false);
setSilent(silent);
}

@Override
protected void onStop(){
super.onStop();

// Save user preferences. We need an Editor object to
// make changes. All objects are from android.context.Context
SharedPreferences settings = getSharedPreferences(PREFS_NAME, 0);
SharedPreferences.Editor editor = settings.edit();
editor.putBoolean("silentMode", mSilentMode);

// Don't forget to commit your edits!!!
editor.commit();
}
}


第二小节 使用Files


Android提供本地文件流的读写访问给应用。调用Context.openFileOutput()和Context.openFileInput()带本地名字和路径来读写文件。如果从其它应用用相同的名字、路径来调用则不会成功;只能访问本地文件。



假如你的应用在编译的时候有静态文件,可以保存你的文件在你的项目res/raw/<mydatafile>,接着用Resources.openRawResource(R.raw.mydatafile)来读取。




第三小节 使用SQLite Databases


建立数据库用SQLiteOpenHelper。Android装备了sqlite3数据库工具,能够浏览表内容,运行SQL命令并且执行其它有用的功能。



所有的数据库,SQLite,保存在/data/data/<package_name>/database。



Android并没有在标准SQLITE上做限制,不过建议每个表增加一个自动增加的关键值作为索引,对于私有数据并不是必须的。但是假如你实现一个content provider,则它是必须的。




第四小节 访问Content Providers


假如想发布数据,可以建立content provider,这个方法能够让所有应用存取某个应用的数据。这个是跨包之间共享数据的唯一方法。Android提供公用数据类型的一些content provider(声音、视频、图像、个人联系信息等等)。在provider包里面有一些Android的基本content provider。



所有的content provider必须实现通用的查询数据方法和返回结果方法。当然,content provider能实现客户化的helper功能,使数据的保存、恢复更简单。



用content provider保存和恢复数据



Android提供各种数据类型供content provider使用,从音乐图片到电话号码,从android.provider包content provider提供的数据。



Android的content provider是很松的连接到他们的客户端,每个content provider披露一个统一的URI标记它处理的数据类型,客户必须使用这个URI来保存和恢复这个类型的数据。



查询数据



每个content provider披露一个统一公共的URI,用来客户端查询、增加、更新、删除数据。URI有两种格式,一种指示这个类型的所有值,一种指示这个类型的一条特定的记录。

content://contacts/people: 返回所有联系人名字。

content://contacts/people/23: 返回单个结果,ID为23的联系人。



一个应用发送一个查询给设备,指定一个通用条目类型(如所有电话号码),或一个特定条目(小马的电话号码)。Android返回结果集的一个游标,下面一个假想的查询字符集和结果集:

query = content://contacts/people/
结果:


















































_ID

_COUNT

NUMBER

NUMBER_KEY

LABEL

NAME

TYPE

13

4

(425) 555 6677

425 555 6677

California office

Bully Pulpit

Work

44

4

(212) 555-1234

212 555 1234

NY apartment

Alan Vain

Home

45

4

(212) 555-6657

212 555 6657

Downtown office

Alan Vain

Work

53

4

201.555.4433

201 555 4433

Love Nest

Rex Cars

Home



注意查询字符串不是标准的SQL查询串,而是一个URI描述了返回结果的类型,这个URI由三个部分组成:开始串content://,接着哪种数据类型要恢复,最后加一个可选的特定条目的ID。例子:



  • content://media/internal/images


  • content://media/external/images


  • content://contacts/people


  • content://contacts/people/23


虽然这是通用格式,这些查询有些任意和迷惑,因此,android在android.provider包里面提供了helper类列表,它定义了这些查询串,因此应该不需要知道不同数据类型的实际URI值。这些helper类定义了叫CONTENT_URI的串。



典型的,你用定义的CONTENT_URI对象来做查询,而不是自己写URI。因此上面每个查询能够用下面URI来参考:



  • MediaStore.Images.Media.INTERNAL_CONTENT_URI


  • MediaStore.Images.Media.EXTERNAL_CONTENT_URI


  • Contacts.People.CONTENT_URI


去查询特定的记录,你用相同的CONTENT_URI,但是要增加特定的ID值,例子:

// Get the base URI for contact with _ID=23.
// This is same as Uri.parse("content://contacts/people/23");
Uri myPerson = ContentUris.withAppendedId(People.CONTENT_URI, 23);
// Query for this record.
Cursor cur = managedQuery(myPerson, null, null, null);

TIP:可以用withAppendedPath(Uri, String)增加串到URI。



查询返回数据记录的游标,本次返回什么记录,下次会有什么记录。



应当用Activity.managedQuery()方法恢复游标,游标可以准确处理自己的位置。可以调用Activity.startManagingCursor()方法管理一个未管理的游标。



下面是一个例子,用来恢复联系人列表和他们基本的电话号码:

// An array specifying which columns to return. 
string[] projection = new string[] {
People._ID,
People.NAME,
People.NUMBER,
};

// Get the base URI for People table in Contacts content provider.
// ie. content://contacts/people/
Uri mContacts = People.CONTENT_URI;

// Best way to retrieve a query; returns a managed query.
Cursor managedCursor = managedQuery( mContacts,
projection, //Which columns to return.
null, // WHERE clause--we won't specify.
People.NAME + " ASC"); // Order-by clause.

查询返回什么



查询返回0个或多个数据库记录,列名字、顺序和类型给指定给content provider,每个查询都有一个名字为_id的列,假如查询返回二进制数据,例如位图或声音文件,将返回一个随机名字的列,它给出URI格式(content://URI)让你得到这些数据。看下图:

























_id

name

number

44

Alan Vain

212 555 1234

13

Bully Pulpit

425 555 6677

53

Rex Cars

201 555 4433

这个结果显示一个子集被返回,可选的子集列表放在查询的projection参数里面,内容管理者应该列出支持哪些列,需要描述每一列(参考Contacts.People.Phone,他扩展了BaseColumns,PhonesColumns和PeopleColumns),或者列出常量列名字。为了访问数据,需要知道列数据类型。



通过游标恢复的数据能够在结果集里面前后查找,可以用游标读、修改、删除行,增加新行需要不同的对象。



注意,每个记录集包括一个列名为_id的列,和一个_count域,表示当前结果集的记录数,这些域名字由BaseColumns定义。



查询文件



返回结果的文件域典型的是文件路径,但是,调用者从来不应该直接打开或读取这些文件,相反,应该调用ContentProvider.openInputStream()/ContentResolver.openOutputStream(),或helper功能。



读恢复的数据





游标对象提供了访问结果记录集的方法,假如你查询一条特定记录,你将得到仅一个值,否则,可以包含多个值。可以从这些记录读取特定的数据,当然你必须知道这个数据的类型,游标让你从索引请求列名字,或从列名取得索引号。







假如你正在读2进制数据,例如图像文件,应该调用ContentResolver.openOutputStream()从列名content://URI。







下面片段显示了读取名字和电话号码:


private void getColumnData(Cursor cur){ 
if (cur.moveToFirst()) {

String name;
String phoneNumber;
int nameColumn = cur.getColumnIndex(People.NAME);
int phoneColumn = cur.getColumnIndex(People.NUMBER);
String imagePath;

do {
// Get the field values
name = cur.getString(nameColumn);
phoneNumber = cur.getString(phoneColumn);

// Do something with the values.
...

} while (cur.moveToNext());

}
}

修改数据








批量更新一组数据,用列和值调用ContentResolver.update()方法。







增加新记录



要增加新记录,调用ContentResolver.insert()带URI来增加,将返回新记录的URI,包括记录号,可以用来查询和得到新记录的游标:
ContentValues values = new ContentValues();
Uri phoneUri = null;
Uri emailUri = null;

values.put(Contacts.People.NAME, "New Contact");
//1 = the new contact is added to favorites
//0 = the new contact is not added to favorites
values.put(Contacts.People.STARRED,1);

//Add Phone Numbers
Uri uri = getContentResolver().insert(Contacts.People.CONTENT_URI, values);

//The best way to add Contacts data like Phone, email, IM is to
//get the CONTENT_URI of the contact just inserted from People's table,
//and use withAppendedPath to construct the new Uri to insert into.
phoneUri = Uri.withAppendedPath(uri, Contacts.People.Phones.CONTENT_DIRECTORY);

values.clear();
values.put(Contacts.Phones.TYPE, Phones.TYPE_MOBILE);
values.put(Contacts.Phones.NUMBER, "1233214567");
getContentResolver().insert(phoneUri, values);

//Add Email
emailUri = Uri.withAppendedPath(uri, ContactMethods.CONTENT_DIRECTORY);

values.clear();
//ContactMethods.KIND is used to distinguish different kinds of
//contact data like email, im, etc.
values.put(ContactMethods.KIND, Contacts.KIND_EMAIL);
values.put(ContactMethods.DATA, "test@example.com");
values.put(ContactMethods.TYPE, ContactMethods.TYPE_HOME);
getContentResolver().insert(emailUri, values);
可以调用带URI的ContentResolver().openOutputStream()来保存文件:
// Save the name and description in a map. Key is the content provider's
// column name, value is the value to save in that record field.
ContentValues values = new ContentValues(3);
values.put(MediaStore.Images.Media.DISPLAY_NAME, "road_trip_1");
values.put(MediaStore.Images.Media.DESCRIPTION, "Day 1, trip to Los Angeles");
values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg");

// Add a new record without the bitmap, but with the values.
// It returns the URI of the new record.
Uri uri = getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);

try {
// Now get a handle to the file for that record, and save the data into it.
// sourceBitmap is a Bitmap object representing the file to save to the database.
OutputStream outStream = getContentResolver().openOutputStream(uri);
sourceBitmap.compress(Bitmap.CompressFormat.JPEG, 50, outStream);
outStream.close();
} catch (Exception e) {
Log.e(TAG, "exception while writing image", e);
}
删除记录

用ContentResolver.delete()带URI来删除记录。
要删除多行,调用ContentResolver.delete()带记录类型的URI(例如,android.provider.Contacts.People.CONTENT_URI)和SQL WHERE字句。

建立Content Provider

  1. 扩展ContentProvider。
  2. 定义名叫CONTENT_URI的public static final URI,这是带有content://格式字符串。必须定义一个统一的字符串,最好的方法是用完全合格的小写的类名字格式,例如:public static final Uri CONTENT_URI=Uri.parse("content://com.google.codelab.rssprovider");
  3. 建立系统保存数据,最通用的方法使用文件或SQLite数据库,当然可以用任何其它方式保存数据。
  4. 定义需要返回给客户端的列名,假如用基本的数据库,这些列名就是SQL数据库的列名。任何情况下,你需要定义一个整数列名_id来定义一列。如果使用SQLite数据库,这个类型应该是INTEGER PRIMARY KEY AUTOINCREMENT。AUTOINCREMENT是可选的,缺省情况下,SQLite会自动增加。假如删除最后一行,再增加一行,则增加的这行跟删除的一行具有相同的_id。为了避免这种情况,你需要定义成INTEGER PRIMARY KEY AUTOINCREMENT。Android提供SQLiteOpenHelper类帮助建立和管理你的数据库版本。
  5. 假如披露字节型数据,例如位图,他实际是个类似content://字符串,这是客户端用来恢复数据用的。content provider为这种类型应该实现列名为_data的记录。_data域列出设备上的文件路径,这个域不能给客户端直接读,但可以让ContentResolver读。客户调用ContentResolver.openOutputStream()读取这个内容。ContentResolver请求记录的_data域,因为它比客户端有更高的权限,应该可以直接访问这个文件,并且返回给客户端。
  6. 声明public static Strings供客户返回,或者从游标返回值,
  7. 在应答查询时返回记录集的游标对象,意味着实现了query(),update(), insert()和delete()方法。一般情况下,也许调用ContentResolver.notifyChange()来通知监听器来更新信息。
  8. 在AndroidManifest.xml里面增加一个<provider>标记,用它的authorities属性来定义内容类型的鉴权部分。例如,假如类型类型是content://com.example.autos/auto请求所有自动化的列表,那么authorities应当是com.example.autos。假如数据不需要在多个版本之间同步,需要设置multiprocess属性为true。
  9. 假如你正在处理新的数据类型,必须定义新的MIME类型android.ContentProvider.getType(url)。MIME有两种格式:一是为特定的记录,一是为多记录。下面是通用格式:

  • vnd.android.cursor.item/vnd.yourcompanyname.contenttype
    为单记录,例如,为列车122记录:
    content://com.example.transportationprovider/trains/122

    也许返回MIME类型
    vnd.android.cursor.item/vnd.example.rail
  • vnd.android.cursor.dir/vnd.yourcompanyname.contenttype
    为多记录,例如,为所有列车记录:
    content://com.example.transportationprovider/trains

    也许返回MIME类型
    vnd.android.cursor.dir/vnd.example.rail

一个私有的content provider实现例子,看notepad例子的NodePadProvider类。

下面是各个URI部分的解释:
A. 标准的前缀,不能修改。
B. 鉴权部分,为第三方应用,包括一个类,相应的值在<provider>属性里面:
<provider class="TransportationProvider" authorities="com.example.transportationProvider" />
C. 决定哪种类型的数据被请求,可以0个或多个分段:假如content provider仅仅披露一种类型数据,这项可以不需要写;假如提供几种类型,包括子类型,可以是几个条目:"land/bus,land/train,sea/ship,sea/submarine。
D. 假如一个特定记录被请求,就是一个特定_id记录值被请求,假如特定类型的所有记录被请求,则可以省略。例如:content://com.example.transportationProvider/trans。




第五小节 网络访问


除了上面这些在设备上的存取方式,也可以从网络保存和恢复数据,如果要实现网络操作,必须用下面的包:



  • java.net.*


  • android.net.*




第四节 安全模型






没有评论: