android:layout_width="fill_parent"
android:layout_height="fill_parent"
android
rientation="vertical" >
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello, I am a TextView" />
资源被编进最终的APK文件中。Android创建了一个封装类,叫做R,在代码中你可以使用它来引用这些资源。R包含了根据资源文件的路径和名称命名的子类。
全局资源说明Global Resource Notes
一些资源允许你定义颜色值。Android接受的颜色值可以使用多种web样式的形式--以下几种包含十六进制常数的形式:#RGB、#ARGB、#RRGGBB、#AARRGGBB。
所有颜色值支持设置透明度(alpha channel value),前两位的十六进制数指定了透明了。0在透明度值是全透明。默认值是不透明。
使用资源Using Resources
这一部分描述如何使用你创建的资源。它包含以下主题:
?8?5 代码中使用资源 - 如何在你的代码中调用资源进行实例化。
?8?5 从其他资源中引用资源../../Docs/android_dev_guide/android_dev_guide/developer.android.com/guide/topics/resources/resources-i18n.html - ReferencesToResources - 你可以从其他资源中引用资源。这就使得你可以重用资源中公共资源值。
?8?5 支持针对交替配置的交替资源 - 你可以根据主机硬件的语言或显示配置指定加载不同的资源。
在编译时,Android产生一个名为R的类,它包含了你的程序中所有资源的资源标识符。这个类包含了一些子类,每一个子类针对一种Android支持的资源类型,或者你提供的一个资源文件。每一个类都包含了已编译资源的一个或多个资源标识符,你可以在代码中使用它们来加载资源。下面是一个小的资源文件,包含了字符串、布局(屏幕或屏幕的一部分)和图像资源。
注意: R类是一个自动产生的文件,并没有设计为可以手动编辑。当资源更新时,它会根据需要重新产生。
package com.google.android.samples;
public final class R {
public static final class string {
public static final int greeting = 0x0204000e;
public static final int start_button_text = 0x02040001;
public static final int submit_button_text = 0x02040008;
public static final main_screen_title = 0x0204000a;
};
public static final class layout {
public static final int start_screen = 0x02070000;
public static final int new_user_pane = 0x02070001;
public static final int select_user_list = 0x02070002;
};
public static final class drawable {
public static final int company_logo = 0x02020005;
public static final int smiling_cat = 0x02020006;
public static final int yellow_fade_background = 0x02020007;
public static final int stretch_button_1 = 0x02020008;
};
};
在代码中使用资源Using Resources in Code
在代码中使用资源,只是要知道所有资源ID和你的被编译的资源是什么类型。下面是一个引用资源的语法:
R.resource_type.resource_name
或者
android.R.resource_type.resource_name
其中resource_type是R的子类,保存资源的一个特定类型。resource_name时在XML文件定义的资源的name属性,或者有其他文件类型为资源定义的文件名(不包含扩展名)。每一种资源类型都会根据其类型加为一个特定的R子类;要了解R的哪一个子类是关于你的资源类型的,请参考资源参考(resource reference)文档。被你的应用程序编译的资源可以不加包名引用(就像R.resource_type.resource_name这样简单)。Android包含了很多标准资源,如屏幕样式和按钮背景。要在代码中引用这些资源,你必须使用android进行限定,如android.R.drawable.button_background。
这里有一些在代码中使用已编译资源的正确和错误用法的例子。
// Load a background for the current screen from a drawable resource.
this.getWindow().setBackgroundDrawableResource(R.drawable.my_background_image);
// WRONG Sending a string resource reference into a
// method that expects a string.
this.getWindow().setTitle(R.string.main_title);
// RIGHT Need to get the title from the Resources wrapper.
this.getWindow().setTitle(Resources.getText(R.string.main_title));
// Load a custom layout for the current screen.
setContentView(R.layout.main_screen);
// Set a slide in animation for a ViewFlipper object.
mFlipper.setInAnimation(AnimationUtils.loadAnimation(this, R.anim.hyperspace_in));
// Set the text on a TextView object.
TextView msgTextView = (TextView)findViewByID(R.id.msg);
msgTextView.setText(R.string.hello_message);
引用资源References to Resources
在属性(或资源)中提供的值也可以作为资源的引用。这种情况经常使用在布局文件中,以提供字符串(因此它们可以被本地化<将UI上的字符串放在一个单独的文件中,在做国际化时只需要将它们翻译成相应的语言版本,然后应用程序根据locale信息加载相应的字符串文件——译者注>)和图像(它们存在于另外的文件中),虽然引用可以是任何资源类型,包括颜色和整数。
例如,如果我们有颜色资源(color resources),我们可以编写一个布局文件,将文本的颜色设为那些资源中包含的值:
使用系统资源Using System Resources
在系统中的包含了很多应用程序可以使用的资源。所有的这些资源都在“android.R”类下定义。例如,使用下面的代码你可以在屏幕上显示标准应用程序的图标:
public class MyActivity extends Activity {
public void onStart() {
requestScreenFeatures(FEATURE_BADGE_IMAGE);
super.onStart();
setBadgeResource(android.R.drawable.sym_def_app_icon);
}
}
以相似的方式,下面的代码将对你的屏幕应用系统定义的标准“绿色背景”视觉处理。
public class MyActivity extends Activity
public void onStart() {
super.onStart();
setTheme(android.R.style.Theme_Black);
}
}
如果这个设备使用一个无线连接(GSM电话),则MCC来自SIM卡,而MNC来自该设备将要附着的网络。你有时会仅使用MCC,例如包含特定国家合法资源在您的应用程序中。如果您的应用程序指定了MCC/MNC组合的资源,这些资源仅在MCC和MNC都匹配的时候才能使用。
语言和区域Language and region 两个字母的ISO 639-1语言码和ISO 3166-1-alpha-2区域码 (以"r"为前缀)。比如en-rUS, fr-rFR, es-rES.
这个代码是大小写敏感的:语言码是小写字母,国家码是大写字母。你不能单独指定一个区域,但是你可以单独指定一个语言,比如en, fr, es, zh.
屏幕方向Screen orientation 纵向,横向,正方形(port, land, square)
屏幕像素密度Screen pixel density 92dpi, 108dpi等. 当Android选择使用哪个资源时,它对屏幕像素密度的处理和其它限定符不同。在文章后面描述的步骤1Android如何查找最匹配的目录中,屏幕密度总被认为是匹配的。在步骤4中,如果被考虑的限定符是屏幕密度,Android将选择在那个位置的最佳匹配,而无需继续步骤5。
触摸屏类型Touchscreen type 非触摸式,触摸笔,手指(notouch, stylus, finger)
键盘可用方式Whether the keyboard is available to the user 外在键盘,隐藏键盘,软键盘(keysexposed, keyshidden, keyssoft)
如果你的应用程序有一个特定的资源只能通过软件盘使用,则使用keyssoft 值,如果没有keyssoft 资源可用(只有keysexposed 和 keyshidden)并且该设备显示了一个软键盘,那么系统将使用keysexposed 资源。
首选文本输入方法Primary text input method 不支持按键,标准键盘,12键(nokeys, qwerty, 12key)
首选非触摸式导航方法Primary non-touchscreen
navigation method 不支持导航,滑板,跟踪球,滚轮(nonav, dpad, trackball, wheel)
屏幕分辨率Screen dimensions 320x240, 640x480, 等. 更大的分辨率必须先被指定。
SDK版本SDK version 设备支持的SDK版本,比如v3。Android1.0 SDK是v1,1.1SDK是v2,1.5SDK是v3。
小版本(Minor version) 你目前还不能指定小版本,它总是被设置为0。
这个列表不包含设备特有的参数比如载波,品牌,设备/硬件,或者制造商。所有应用程序需要知道的设备信息均通过上表中的资源限定符编码。
所有资源目录,许可的和未经许可的,都存放在res/目录下。下面是一些关于许可的资源目录名称的指导原则:
?8?5 你可以指定多个限定符,用破折号分开。比如,drawable-en-rUS-land会被应用在美国英语的横向手机设备中。
?8?5 限定符必须符合表2中列举的顺序。比如:
正确的:values-mcc460-nokeys/
错误的:values-nokeys-mcc460/
?8?5 限定符的值大小写敏感。比如一个纵向特定的drawable目录必须命名为drawable-port,不可以是drawable-PORT或drawable-Port。
?8?5 每个限定符类型仅支持一个值。比如,如果你想使用为法国和西班牙使用相同的drawable文件,你得需要两个资源目录,如drawable-rES/和drawable-rFR/,包含相同的文件。你不能使用一个名为drawable-rES-rFR的目录。
?8?5 限定符不能嵌套使用。比如,你不能使用res/drawable/drawable-en。
资源怎么在代码中使用How resources are referenced in code
所有的资源均通过它们简单未经修饰的名字在代码或资源引用语法中引用。所以如果一个资源命名如下:
MyApp/res/drawable-port-92dpi/myimage.png
它会被这样引用:
R.drawable.myimage (code)
@drawable/myimage (XML)
如果有多个drawable目录可用, Android将会选择其一(如下所述)并从中加载myimage .png。
Android如何查找最匹配的目录How Android finds the best matching directory
Android将从各种潜在的资源中挑选出哪个应该在运行时使用,这取决于设备的当前配置。这里的例子假定使用了如下的设备配置:
区域Locale = en-GB
屏幕方向Screen orientation = port
屏幕像素密度Screen pixel density = 108dpi
触摸屏类型Touchscreen type = notouch
首选文本输入方式Primary text input method = 12key
下面说明了Android如何作出选择:
1. 排除和设备配置冲突的资源文件。比如,假定如下的drawables资源目录可用。那么drawable-fr-rCA/会被排除,因为它和设备的区域配置冲突。
MyApp/res/drawable/
MyApp/res/drawable-en/
MyApp/res/drawable-fr-rCA/
MyApp/res/drawable-en-port/
MyApp/res/drawable-en-notouch-12key/
MyApp/res/drawable-port-92dpi/
例外:屏幕像素密度是唯一不用来排除文件的限定符。即使设备屏幕密度是108dpi,drawable-port-92dpi/也不会被从列表中排除,因为在这里所有的屏幕密度都被视为匹配。
2. 从表2中选取最高优先级的限定符(从MCC开始,然后自该列表依次往下)。
3. 有没有哪个可用的资源目录包含了这个限定符?
?8?5 如果没有,回到步骤2然后查看表2中所列的下一个限定符。在我们的例子中,答案是“没有”直到我们到达语言这一级。If No, return to step 2 and look at the next qualifier listed in Table 2. In our example, the answer is "no" until we reach Language;
?8?5 如果有,则跳转到步骤4。
4. 排除不包含这个限定符的资源目录,在我们的例子中,我们排除所有不包含语言的目录。
MyApp/res/drawable/
MyApp/res/drawable-en/
MyApp/res/drawable-en-port/
MyApp/res/drawable-en-notouch-12key/
MyApp/res/drawable-port-92dpi/
MyApp/res/drawable-port-notouch-12key
例外:如果询问中的限定符是屏幕像素密度,Android会选择最接近匹配于设备的选项,而且选择过程将会完成。一般而言,Android会倾向于缩小一个大图片而不是放大一个小图片。
5. 回头重复步骤2,3,4直到只剩下一个选择。在本例中,屏幕方向是下一个要比较的限定符,我们排除没有指定屏幕方向的资源。现在只剩下一个选择,那就是它了。当drawables被这个应用程序调用时,Android系统会从下面这个目录中加载资源:MyApp/res/drawable-en-port/
提示Tip: 限定符的优先权比匹配的数目要重要得多。比如,在上面的步骤4中,列表中最后的选项包含三个限定符和设备匹配(方向,触摸屏类型,和输入法),而drawable-en 只有一个参数匹配(语言)。但是,语言拥有更高的优先权,所以drawable-port-notouch-12key 被排除出局。
下面的流程图总结了Android如何选择资源目录来加载的过程:
每个数据元素可以指定一个URI和一个数据类型(MIME媒体类型)。有一些单独的属性-模式,主机,端口和路径-URI的每个部分:
scheme://host:port/path
比如,在下面的URI里面,
content://com.example.project:200/folder/subfolder/etc
模式是"内容",主机是"com.example.project",端口是"200",路经是"folder/subfolder/etc"。主机和端口一起组成URI鉴权(authority);如果未指定主机,端口会被忽略。
这些属性都是可选的,但彼此有依赖关系:一个授权要有意义,必须指定一个模式。一个路经要有意义,必须同时指定模式和鉴权。
当一个意图对象中的URI被用来和一个过滤器中的URI规格比较时,它实际上比较的是上面提到的URI的各个部分。比如,如果过滤器仅指定了一个模式,所有那个模式的URIs和这个过滤器相匹配;如果过滤器指定了一个模式、鉴权但没有路经,所有相同模式和鉴权的URIs可以匹配上,而不管它们的路经;如果过滤器指定了一个模式、鉴权和路经,只有相同模式、鉴权和路经的URIs可以匹配上。当然,一个过滤器中的路径规格可以包含通配符,这样只需要部分匹配即可。
数据元素的类型属性指定了数据的MIME类型。这在过滤器里比在URI里更为常见。意图对象和过滤器都可以使用一个"*"通配符指定子类型字段-比如,"text/*"或者"audio/*"-指示任何匹配的子类型。
数据测试同时比较意图对象和过滤器中指定的URI和数据类型。规则如下:
a. 一个既不包含URI也不包含数据类型的意图对象仅在过滤器也同样没有指定任何URIs和数据类型的情况下才能通过测试。
b. 一个包含URI但没有数据类型的意图对象仅在它的URI和一个同样没有指定数据类型的过滤器里的URI匹配时才能通过测试。这通常发生在类似于mailto:和tel:这样的URIs上:它们并不引用实际数据。
c. 一个包含数据类型但不包含URI的意图对象仅在这个过滤器列举了同样的数据类型而且也没有指定一个URI的情况下才能通过测试。
d. 一个同时包含URI和数据类型(或者可从URI推断出数据类型)的意图对象可以通过测试,如果它的类型和过滤器中列举的类型相匹配的话。如果它的URI和这个过滤器中的一个URI相匹配或者它有一个内容content:或者文件file: URI而且这个过滤器没有指定一个URI,那么它也能通过测试。换句话说,一个组件被假定为支持content:和file: 数据如果它的过滤器仅列举了一个数据类型。
如果一个意图可以通过不止一个活动或服务的过滤器,用户可能会被询问要激活那个组件。如果没有发现目标对象将会出现异常。
// 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);
内容提供器Content Providers
内容提供器用来存放和获取数据并使这些数据可以被所有的应用程序访问。它们是应用程序之间共享数据的唯一方法;不存在所有Android软件包都能访问的公共储存区域。
Android为常见数据类型(音频,视频,图像,个人联系人信息,等等)装载了很多内容提供器。你可以看到在android.provider包里列举了一些。你还能查询这些提供器包含了什么数据(尽管,对某些提供器,你必须获取合适的权限来读取数据)。
如果你想公开你自己的数据,你有两个选择:你可以创建你自己的内容提供器(一个ContentProvider子类)或者你可以给已有的提供器添加数据-如果存在一个控制同样类型数据的内容提供器且你拥有写的权限。
这篇文档是一篇关于如何使用内容提供器的简介。先是一个简短的基础知识讨论,然后探究如何查询一个内容提供器,如何修改内容提供器控制的数据,以及如何创建你自己的内容提供器。
内容提供器的基础知识Content Provider Basics
内容提供器究竟如何在表层下保存它的数据依赖于它的设计者。但是所有的内容提供器实现了一个公共的接口来查询这个提供器和返回结果-增加,替换,和删除数据也是一样。
这是一个客户端直接使用的接口,一般是通过ContentResolver对象。你可以通过getContentResolver()从一个活动或其它应用程序组件的实现里获取一个ContentResolver:
ContentResolver cr = getContentResolver();
然后你可以使用这个ContentResolver的方法来和你感兴趣的任何内容提供器交互。
当初始化一个查询时,Android系统识别查询目标的内容提供器并确保它正在运行。系统实例化所有的ContentProvider对象;你从来不需要自己做。事实上,你从来不会直接处理ContentProvider对象。通常,对于每个类型的ContentProvider只有一个简单的实例。但它能够和不同应用程序和进程中的多个ContentProvider对象通讯。进程间的交互通过ContentResolver和ContentProvider类处理。
数据模型The data model
内容提供器以数据库模型上的一个简单表格形式暴露它们的数据,这里每一个行是一个记录,每一列是特别类型和含义的数据。比如,关于个人信息以及他们的电话号码可能会以下面的方式展示:
_ID NUMBER NUMBER_KEY LABEL NAME TYPE
13 (425) 555 6677 425 555 6677 Kirkland office Bully Pulpit TYPE_WORK
44 (212) 555-1234 212 555 1234 NY apartment Alan Vain TYPE_HOME
45 (212) 555-6657 212 555 6657 Downtown office Alan Vain TYPE_MOBILE
53 201.555.4433 201 555 4433 Love Nest Rex Cars TYPE_HOME
每个记录包含一个数字的_ID字段用来唯一标识这个表格里的记录。IDs可以用来匹配相关表格中的记录-比如,用来在一张表格中查找个人电话号码并在另外一张表格中查找这个人的照片。
一个查询返回一个Cursor 对象可在表格和列中移动来读取每个字段的内容。它有特定的方法来读取每个数据类型。所以,为了读取一个字段,你必须了解这个字段包含了什么数据类型。(后面会更多的讨论查询结果和游标Cursor对象)。
唯一资源标识符URIs
每个内容提供器暴露一个公开的URI(以一个Uri 对象包装)来唯一的标识它的数据集。一个控制多个数据集(多个表)的内容提供器为每一个数据集暴露一个单独的URI。所有提供器的URIs以字符串"content://"开始。这个content:形式表明了这个数据正被一个内容提供器控制着。
如果你正准备定义一个内容提供器,为了简化客户端代码和使将来的升级更清楚,最好也为它的URI定义一个常量。Android为这个平台所有的提供器定义了CONTENT_URI 常量。比如,匹配个人电话号码的表的URI和包含个人照片的表的URI是:(均由联系人Contacts内容提供器控制)
android.provider.Contacts.Phones.CONTENT_URI
android.provider.Contacts.Photos.CONTENT_URI
类似的,最近电话呼叫的表和日程表条目的URI如下:Similarly, the URIs for the table of recent phone calls and the table of calendar entries are:
android.provider.CallLog.Calls.CONTENT_URI
android.provider.Calendar.CONTENT_URI
这个URI常量被使用在和这个内容提供器所有的交互中。每个ContentResolver 方法采用这个URI作为它的第一个参数。正是它标识了ContentResolver应该和哪个内容提供器对话以及这个内容提供器的哪张表格是其目标。
查询一个内容提供器Querying a Content Provider
你需要三方面的信息来查询一个内容提供器:
?6?1 用来标识内容提供器的URI
?6?1 你想获取的数据字段的名字
?6?1 这些字段的数据类型
如果你想查询某一条记录,你同样需要那条记录的ID。
生成查询Making the query
你可以使用ContentResolver.query()方法或者Activity.managedQuery()方法来查询一个内容提供器。两种方法使用相同的参数序列,而且都返回一个Cursor对象。不过,managedQuery()使得活动需要管理这个游标的生命周期。一个被管理的游标处理所有的细节,比如当活动暂停时卸载自身,而活动重新启动时重新查询它自己。你可以让一个活动开始管理一个尚未被管理的游标对象,通过如下调用: Activity.startManagingCursor()。
无论query()还是managedQuery(),它们的第一个参数都是内容提供器的URI-CONTENT_URI常量用来标识某个特定的ContentProvider和数据集(参见前面的URIs)。
为了限制只对一个记录进行查询,你可以在URI后面扩展这个记录的_ID值-也就是,在URI路径部分的最后加上匹配这个ID的字符串。比如,如果ID是23,那么URI会是:
content://. . . ./23
有一些辅助方法,特别是ContentUris.withAppendedId() 和Uri.withAppendedPath(),使得为URI扩展一个ID变得简单。所以,比如,如果你想在联系人数据库中查找记录23,你可能需要构造如下的查询语句:
import android.provider.Contacts.People;
import android.content.ContentUris;
import android.net.Uri;
import android.database.Cursor;
// Use the ContentUris method to produce the base URI for the contact with _ID == 23.
Uri myPerson = ContentUris.withAppendedId(People.CONTENT_URI, 23);
// Alternatively, use the Uri method to produce the base URI.
// It takes a string rather than an integer.
Uri myPerson = Uri.withAppendedPath(People.CONTENT_URI, "23");
// Then query for this specific record:
Cursor cur = managedQuery(myPerson, null, null, null, null);
query() 和managedQuery()方法的其它参数限定了更多的查询细节。如下:
?6?1 应该返回的数据列的名字。null值返回所有列。否则只有列出名字的列被返回。所有这个平台的内容提供器为它们的列定义了常量。比如,android.provider.Contacts.Phones类对前面说明过的通讯录中各个列的名字定义了常量ID, NUMBER, NUMBER_KEY, NAME, 等等。
?6?1 指明返回行的过滤器,以一个SQL WHERE语句格式化。 null值返回所有行。(除非这个URI限定只查询一个单独的记录)。
?6?1 选择参数
?6?1 返回行的排列顺序,以一个SQL ORDER BY语句格式化(不包含ORDER BY本身)。null值表示以该表格的默认顺序返回,有可能是无序的。
让我们看一个查询的例子吧,这个查询获取一个联系人名字和首选电话号码列表:
import android.provider.Contacts.People;
import android.database.Cursor;
// Form an array specifying which columns to return.
String[] projection = new String[] {
People._ID,
People._COUNT,
People.NAME,
People.NUMBER
};
// Get the base URI for the People table in the Contacts content provider.
Uri contacts = People.CONTENT_URI;
// Make the query.
Cursor managedCursor = managedQuery(contacts,
projection, // Which columns to return
null, // Which rows to return (all rows)
null, // Selection arguments (none)
// Put the results in ascending order by name
People.NAME + " ASC");
这个查询从联系人内容提供器中获取了数据。它得到名字,首选电话号码,以及每个联系人的唯一记录ID。同时它在每个记录的_COUNT字段告知返回的记录数目。
列名的常量被定义在不同的接口中-_ID和_COUNT 定义在BaseColumns里, NAME在PeopleColumns里,NUMBER在PhoneColumns里。Contacts.People类已经实现了这些接口,这就是为什么上面的代码实例只需要使用类名就可以引用它们的原因。
查询的返回结果What a query returns
一个查询返回零个或更多数据库记录的集合。列名,默认顺序,以及它们的数据类型是特定于每个内容提供器的。但所有提供器都有一个_ID列,包含了每个记录的唯一ID。另外所有的提供器都可以通过返回_COUNT 列告知记录数目。它的数值对于所有的行而言都是一样的。
下面是前述查询的返回结果的一个例子:
_ID _COUNT NAME NUMBER
44 3 Alan Vain 212 555 1234
13 3 Bully Pulpit 425 555 6677
53 3 Rex Cars 201 555 4433
获取到的数据通过一个游标Cursor对象暴露出来,通过游标你可以在结果集中前后浏览。你只能用这个对象来读取数据。如果想增加,修改和删除数据,你必须使用一个ContentResolver对象。
读取查询所获数据Reading retrieved data
查询返回的游标对象可以用来访问结果记录集。如果你通过指定的一个ID来查询,这个集合将只有一个值。否则,它可以包含多个数值。(如果没有匹配结果,那还可能是空的。)你可以从表格中的特定字段读取数据,但你必须知道这个字段的数据类型,因为这个游标对象对于每种数据类型都有一个单独的读取方法-比如getString(), getInt(), 和getFloat()。(不过,对于大多数类型,如果你调用读取字符串的方法,游标对象将返回给你这个数据的字符串表示。)游标可以让你按列索引请求列名,或者按列名请求列索引。
下面的代码片断演示了如何从前述查询结果中读取名字和电话号码:
import android.provider.Contacts.People;
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);
// Add Abraham Lincoln to contacts and make him a favorite.
values.put(People.NAME, "Abraham Lincoln");
// 1 = the new contact is added to favorites
// 0 = the new contact is not added to favorites
values.put(People.STARRED, 1);
Uri uri = getContentResolver().insert(People.CONTENT_URI, values);
增加新值Adding new values
一旦记录已经存在,你就可以添加新的信息或修改已有信息。比如,上例中的下一步就是添加联系人信息-如一个电话号码或一个即时通讯IM或电子邮箱地址-到新的条目中。
在联系人数据库中增加一条记录的最佳途径是在该记录URI后扩展表名,然后使用这个修正的URI来添加新的数据值。为此,每个联系人表暴露一个CONTENT_DIRECTORY常量的表名。下面的代码继续之前的例子,为上面刚刚创建的记录添加一个电话号码和电子邮件地址:
Uri phoneUri = null;
Uri emailUri = null;
// Add a phone number for Abraham Lincoln. Begin with the URI for
// the new record just returned by insert(); it ends with the _ID
// of the new record, so we don't have to add the ID ourselves.
// Then append the designation for the phone table to this URI,
// and use the resulting URI to insert the phone number.
phoneUri = Uri.withAppendedPath(uri, People.Phones.CONTENT_DIRECTORY);
// Now add an email address in the same way.
emailUri = Uri.withAppendedPath(uri, People.ContactMethods.CONTENT_DIRECTORY);
values.clear();
// ContactMethods.KIND is used to distinguish different kinds of
// contact methods, such as email, IM, etc.
values.put(People.ContactMethods.KIND, Contacts.KIND_EMAIL);
values.put(People.ContactMethods.DATA, "[email protected]");
values.put(People.ContactMethods.TYPE, People.ContactMethods.TYPE_HOME);
getContentResolver().insert(emailUri, values);
你可以通过调用接收字节流的ContentValues.put()版本来把少量的二进制数据放到一张表格里去。这对于像小图标或短小的音频片断这样的数据是可行的。但是,如果你有大量二进制数据需要添加,比如一张相片或一首完整的歌曲,则需要把该数据的content: URI放到表里然后以该文件的URI调用ContentResolver.openOutputStream() 方法。(这导致内容提供器把数据保存在一个文件里并且记录文件路径在这个记录的一个隐藏字段中。)
考虑到这一点,MediaStore 内容提供器,这个用来分发图像,音频和视频数据的主内容提供器,利用了一个特殊的约定:用来获取关于这个二进制数据的元信息的query()或managedQuery()方法使用的URI,同样可以被openInputStream()方法用来数据本身。类似的,用来把元信息放进一个MediaStore记录里的insert()方法使用的URI,同样可以被openOutputStream()方法用来在那里存放二进制数据。下面的代码片断说明了这个约定:
import android.provider.MediaStore.Images.Media;
import android.content.ContentValues;
import java.io.OutputStream;
// Save the name and description of an image in a ContentValues map.
ContentValues values = new ContentValues(3);
values.put(Media.DISPLAY_NAME, "road_trip_1");
values.put(Media.DESCRIPTION, "Day 1, trip to Los Angeles");
values.put(Media.MIME_TYPE, "image/jpeg");
// Add a new record without the bitmap, but with the values just set.
// insert() returns the URI of the new record.
Uri uri = getContentResolver().insert(Media.EXTERNAL_CONTENT_URI, values);
// Now get a handle to the file for that record, and save the data into it.
// Here, sourceBitmap is a Bitmap object representing the file to save to the database.
try {
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);
}
批量更新记录Batch updating records
要批量更新一组记录(例如,把所有字段中的"NY"改为"New York"),可以传以需要改变的列和值参数来调用ContentResolver.update()方法。
删除一个记录Deleting a record
要删除单个记录,可以传以一个特定行的URI参数来调用ContentResolver.delete()方法。
要删除多行记录,可以传以需要被删除的记录类型的URI参数来调用ContentResolver.delete()方法(例如,android.provider.Contacts.People.CONTENT_URI)以及一个SQL WHERE 语句来定义哪些行要被删除。(小心:如果你想删除一个通用类型,你得确保包含一个合法的WHERE语句,否则你可能删除比设想的多得多的记录!)
创建一个内容提供器Creating a Content Provider
要创建一个内容提供器,你必须:
?6?1 建立一个保存数据的系统。大多数内容提供器使用Android的文件储存方法或SQLite数据库来存放它们的数据,但是你可以用任何你想要的方式来存放数据。Android提供SQLiteOpenHelper类来帮助你创建一个数据库以及SQLiteDatabase类来管理它。
?6?1 扩展ContentProvider类来提供数据访问接口。
?6?1 在清单manifest文件中为你的应用程序声明这个内容提供器(AndroidManifest.xml)。
下面的章节对后来两项任务有一些标注。
扩展ContentProvider类Extending the ContentProvider class
你可以定义一个ContentProvider子类来暴露你的数据给其它使用符合ContentResolver和游标Cursor对象约定的应用程序。理论上,这意味需要实现6个ContentProvider类的抽象方法:
query()
insert()
update()
delete()
getType()
onCreate()
query()方法必须返回一个游标Cursor对象可以用来遍历请求数据,游标本身是一个接口,但Android提供了一些现成的Cursor对象给你使用。例如,SQLiteCursor可以用来遍历SQLite数据库。你可以通过调用任意的SQLiteDatabase类的query()方法得到它。还有一些其它的游标实现-比如MatrixCursor-用来访问没有存放在数据库中的数据。
因为这些内容提供器的方法可以从不同的进程和线程的各个ContentResolver对象中调用,所以它们必须以线程安全的方式来实现。
周到起见,当数据被修改时,你可能还需要调用ContentResolver.notifyChange()方法来通知侦听者。
除了定义子类以外,你应该还需要采取其它一些步骤来简化客户端的工作和让这个类更容易被访问:
?6?1 定义一个public static final Uri 命名为CONTENT_URI。这是你的内容提供器处理的整个content: URI的字符串。你必须为它定义一个唯一的字符串。最佳方案是使用这个内容提供器的全称(fully qualified)类名(小写)。因此,例如,一个TransportationProvider类可以定义如下:
public static final Uri CONTENT_URI = Uri.parse("content://com.example.codelab.transporationprovider");
如果这个内容提供器有子表,那么为每个子表也都定义CONTENT_URI常量。这些URIs应该全部拥有相同的权限(既然这用来识别内容提供器),只能通过它们的路径加以区分。例如:
content://com.example.codelab.transporationprovider/train
content://com.example.codelab.transporationprovider/air/domestic
content://com.example.codelab.transporationprovider/air/international
请查阅本文最后部分的Content URI Summary以对content: URIs有一个总体的了解。
?6?1 定义内容提供器返回给客户端的列名。如果你正在使用一个底层数据库,这些列名通常和SQL数据库列名一致。同样还需要定义公共的静态字符串常量用来指定查询语句以及其它指令中的列。
确保包含一个名为"_id"(常量_ID)的整数列来返回记录的IDs。你应该有这个字段而不管有没有其它字段(比如URL),这个字段在所有的记录中是唯一的。如果你在使用SQLite数据库,这个_ID 字段应该是下面的类型:
INTEGER PRIMARY KEY AUTOINCREMENT
其中AUTOINCREMENT描述符是可选的。但是没有它,SQLite的ID数值字段会在列中已存在的最大数值的基础上增加到下一个数字。如果你删除了最后的行,那么下一个新加的行会和这个删除的行有相同的ID。AUTOINCREMENT可以避免这种情况,它让SQLite总是增加到下一个最大的值而不管有没有删除。
?6?1 在文档中谨慎的描述每个列的数据类型。客户端需要这些信息来读取数据。
?6?1 如果你正在处理一个新的数据类型,你必须定义一个新的MIME类型在你的ContentProvider.getType()实现里返回。这个类型部分依赖于提交给getType()的content: URI参数是否对这个请求限制了特定的记录。有一个MIME类型是给单个记录用的,另外一个给多记录用。 使用Uri 方法来帮助判断哪个是正在被请求的。下面是每个类型的一般格式:
?8?5 对于单个记录: vnd.android.cursor.item/vnd.yourcompanyname.contenttype
比如,一个火车记录122的请求,URI如下
content://com.example.transportationprovider/trains/122
可能会返回这个MIME类型:
vnd.android.cursor.item/vnd.example.rail
?8?5 对于多个记录: vnd.android.cursor.dir/vnd.yourcompanyname.contenttype
比如, 一个所有火车记录的请求,URI如下
content://com.example.transportationprovider/trains
可能会返回这个MIME类型:
vnd.android.cursor.dir/vnd.example.rail
?6?1 如果你想暴露过于庞大而无法放在表格里的字节数据-比如一个大的位图文件-这个给客户端暴露数据的字段事实上应该包含一个content: URI字符串。这个字段给了客户端数据访问接口。这个记录应该有另外的一个字段,名为"_data",列出了这个文件在设备上的准确路径。这个字段不能被客户端读取,而要通过ContentResolver。客户端将在这个包含URI的用户侧字段上调用ContentResolver.openInputStream() 方法。ContentResolver会请求那个记录的"_data"字段,而且因为它有比客户端更高的许可权,它应该能够直接访问那个文件并返回给客户端一个包装的文件读取接口。
自定义内容提供器的实现的一个例子,参见SDK附带的Notepad例程中的NodePadProvider 类。
声明内容提供器Declaring the content provider
为了让Android系统知道你开发的内容提供器,可以用在应用程序的AndroidManifest.xml文件中以元素声明它。未经声明的内容提供器对Android系统不可见。
名字属性是ContentProvider子类的全称名(fully qualified name)。权限属性是标识提供器的content: URI的权限认证部分。例如如果ContentProvider子类是AutoInfoProvider,那么元素可能如下:
authorities="com.example.autos.autoinfoprovider"
. . . />
请注意到这个权限属性忽略了content: URI的路径部分。例如,如果AutoInfoProvider为各种不同的汽车或制造商控制着各个子表,Note that the authorities attribute omits the path part of a content: URI. For example, if AutoInfoProvider controlled subtables for different types of autos or different manufacturers,
content://com.example.autos.autoinfoprovider/honda
content://com.example.autos.autoinfoprovider/gm/compact
content://com.example.autos.autoinfoprovider/gm/suv
这些路径将不会在manifest里声明。权限是用来识别提供器的,而不是路径;你的提供器能以任何你选择的方式来解释URI中的路径部分。
其它属性可以设置数据读写许可,提供可以显示给用户的图标和文本,启用或禁用这个提供器,等等。如果数据不需要在多个内容提供器的运行版本中同步则可以把multiprocess属性设置成"true"。这使得在每个客户进程中都有一个提供器实例被创建,而无需执行IPC调用。
Content URI 总结
这里回顾一下content URI的重要内容:
A. 标准前缀表明这个数据被一个内容提供器所控制。它不会被修改。
B. URI的权限部分;它标识这个内容提供器。对于第三方应用程序,这应该是一个全称类名(小写)以确保唯一性。权限在元素的权限属性中进行声明:
authorities="com.example.transportationprovider"
. . . >
C. 用来判断请求数据类型的路径。这可以是0或多个段长。如果内容提供器只暴露了一种数据类型(比如,只有火车),这个分段可以没有。如果提供器暴露若干类型,包括子类型,那它可以是多个分段长-例如,提供"land/bus", "land/train", "sea/ship", 和"sea/submarine"这4个可能的值。
D. 被请求的特定记录的ID,如果有的话。这是被请求记录的_ID数值。如果这个请求不局限于单个记录, 这个分段和尾部的斜线会被忽略:
content://com.example.transportationprovider/trains
访问权限是java中一个比较中要的知识点,它规定者什么方法可以访问,什么不可以访问
一:包访问权限;
自定义包:
package com.wj.control;
//包
public class Demo {
//定义一个无参的方法
public void DemoPackage(){
System.out.println("调用
用户自定义聚合函数,用户提供的多个入参通过聚合计算(求和、求最大值、求最小值)得到一个聚合计算结果的函数。
问题:UDF也可以提供输入多个参数然后输出一个结果的运算,比如加法运算add(3,5),add这个UDF需要实现UDF的evaluate方法,那么UDF和UDAF的实质分别究竟是什么?
Double evaluate(Double a, Double b)
在利用tomcat-redis-session-manager做session同步时,遇到了在session保存一个自定义对象时,修改该对象中的某个属性,session未进行序列化,属性没有被存储到redis中。 在 tomcat-redis-session-manager的github上有如下说明: Session Change Tracking
As noted in the &qu
关于Table Driven Approach的一篇非常好的文章:
http://www.codeproject.com/Articles/42732/Table-driven-Approach
package com.ljn.base;
import java.util.Random;
public class TableDriven {
public