Alarm

Alarm 介绍
  1. Alarm是一种在预先确定时间或时间间隔内激活Intent的方式,和Timmer不同,Alarm是在应用程序之外操作的,所以即使应用程序关闭,也能用来激活应用程序事件或者操作,这个和静态注册广播一样。
  2. Alarm可以结合BroadCast Receiver一起使用,允许设置能够激活广播Intent,启动Service,甚至启动Activity的Alarm,而不需要打开或者运行应用程序。
  3. Android中的Alarm在设备处于休眠状态到时候依旧保持活动状态,可以有选择地设置Alarm来唤醒设备,但是无论何时,重启设备,所有的Alarm都会被取消。
  4. 可以使用Alarm实现基于网络查找的定时更新,或者把费时的或者成本受限的操作安排在非高峰期运行,或者对失败的操作调度重试。
Alarm 使用
  • 获得Alarm Manager:
AlarmManager  manager = getSystemService(Context.ALARM_SERVICE);
  • 设置一次性的Alarm:
manager.set(type, triggerAtMillis, operation);

type类型,可以有如下几种方式:

RTC_WAKEUP:在指定时间唤醒设备,并激活Pending Intent
RTC :在指定时间点激活Pending Intent但是不会唤醒设备。
ELAPSED_REALTIME:根据设备启动之后经过的时间激活Pending Intent ,但是不会唤醒设备。
ELAPSED_REALTIME_WAKEUP根据设备启动之后经过的时间激活Pending Intent ,并唤醒设备。

triggerAtMillis触发时间:

如果把触发时间设置为过去的时间,那么将会被立即触发。

要激活的Pending Intent

PendingIntent alarmIntnet =PeddingIntent.getBroadCast(this,0,intent,0);
  • 设置重复的Alarm

当需要对重复Alarm的精确时间间隔进行细粒度控制的时候,可以使用setRepeating方法,传入这个方法的时间间隔可以用于指定Alarm的确切事件间隔,最多可以精确到毫秒,如下所示:

manager.setRepeating(type, triggerAtMillis, intervalMillis, operation);

当需要减少因为定时唤醒设备来执行更新所消耗的电量的时候可以使用setInexactRepeating方法,在运行时,Android会同步多个没有精确指定时间间隔的Alarm到重复Alarm,并同时触发。方法如下:

manager.setInexactRepeating(type, triggerAtMillis, intervalMillis, operation);

在使用setInexactRepeating的时候不必为其设置确切的时间,它接收如下的几种Alarm Manager常量。

INTERVAL_FIFTEEN_MINUTES
INTERVAL_HALF_HOUR
INTERVAL_HOUR
INTERVAL_HALF_DAY
INTERVAL_DAY
  • 取消Alarm
manager.cancel(alarmIntnet)

Timer与TimerTask

与Alarm不同的是Timer是在应用程序之内操作的,对于只在应用程序生命周期内发生的定时操作,将Handler类和Timmer以及Thread结合起来使用是一种比Alarm更好的方法,因为这样可以允许Android更好地控制系统资源。

  • Timer的使用:
Timer timer = new Timer();
TimerTask task = new TimerTask() {
public void run() {
//周期要执行的任务
}
};
timer.schedule(task, delay, period);
  • 取消Timer :
timer.cancel();

Adapter

Adapter的翻译成中文是适配器和设计模式中介绍的适配器其实是一个意思,它的作用就是将数据库等形式存在的数据适配到像ListView等组件上。
Adapter是连接后端数据和前端显示的适配器接口,是数据和UI(View)之间一个重要的纽带。

常见的Adapter类型

ArrayAdapter :只能展示一行文字。
BaseAdapter:是一个抽象类,继承它需要实现较多的方法,所以也就具有较高的灵活性;
SimpleAdapter:有最好的扩充性,可以自定义出各种效果。
SimpleCursorAdapter:可以适用于简单的纯文字型ListView,它需要Cursor的字段和UI的id对应起来。如需要实现更复杂的UI也可以重写其他方法。可以认为是SimpleAdapter对数据库的简单结合,可以方便地把数据库的内容以列表的形式展示出来。

ArrayAdapter

列表的显示需要三个元素:
a.ListVeiw 用来展示列表的View。
b.适配器 用来把数据映射到ListView上的中介。
c.数据 具体的将被映射的字符串,图片,或者基本组件。

public class ArrayAdapterActivity extends ListActivity {

public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
String[] strs = {"1","2","3","4","5"};
ArrayAdapter<String> adapter = new
ArrayAdapter<String>(this,
android.R.layout.simple_expandable_list_item_1,strs);
setListAdapter(adapter);
}

}
}
SimpleAdapter

simpleAdapter的扩展性最好,可以定义各种各样的布局出来,可以放上ImageView(图片),还可以放上Button(按钮),CheckBox(复选框)等等。

SimpleAdapter adapter = new SimpleAdapter(this, getData(), R.layout.simple, new String[] { "title",  "img" }, new int[] { R.id.title, R.id.img });
SimpleAdapter adapter = new SimpleAdapter(
上下文,
List<Map<String, Object>>类型数据,
每个Item布局文件,
new String[] { “title”}/*在Map的键值*/,
new int[] { R.id.title,}/*在布局中的ID*/
);
SimpleCursorAdapter
ListAdapter adapter = new SimpleCursorAdapter(
this,//上下文
android.R.layout.simple_list_item_1,
//每一项的布局
cur, //Cursor指针
new String[] {People.NAME}, //值
new int[] {android.R.id.text1}//ID
);
BaseAdapter
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder = null;
if (convertView == null) {
holder=new ViewHolder();
convertView = mInflater.inflate(R.layout.vlist2, null);
holder.img = (ImageView)convertView.findViewById(R.id.img);
holder.title = (TextView)convertView.findViewById(R.id.title);
holder.info = (TextView)convertView.findViewById(R.id.info);
holder.viewBtn =
(Button)convertView.findViewById(R.id.view_btn);
convertView.setTag(holder);
}else {
holder = (ViewHolder)convertView.getTag();
}
holder.img.setBackgroundResource((Integer)mData.get(position).get("img"));
holder.title.setText((String)mData.get(position).get("title"));
holder.info.setText((String)mData.get(position).get("info"));
return convertView;
}

系统首先调用getCount()函数,根据他的返回值得到listView的长度.然后根据这个长度,调用getView()逐一绘制每一行。如果你的getCount()返回值是0的话,列表将不显示同样return 1,就只显示一行。

getView()有三个参数,position表示将显示的是第几行,covertView是从布局文件中inflate来的布局。我们用LayoutInflater的方法将定义好的vlist2.xml文件提取成View实例用来显示。然后将xml文件中的各个组件实例化(简单的findViewById()方法)。这样便可以将数据对应到各个组件上了。但是按钮为了响
应点击事件,需要为它添加点击监听器,这样就能捕获点击事件。至此一个自定义的listView就完成了,现在让我们回过头从新审视这个过程。系统要绘制ListView了,他首先获得要绘制的这个列表的长度,然后开始绘制第一行,怎么绘制呢?调用getView()函数。在这个函数里面首先获得一个View(实际上是一个
ViewGroup),然后再实例并设置各个组件,显示之。好了,绘制完这一行了。那再绘制下一行,直到绘完为止。在实际的运行过程中会发现listView的每一行没有焦点了,这是因为Button抢夺了listView的焦点,只要布局文件中将Button设置为没有焦点就OK了。

<Button android:id="@+id/view_btn" android:focusable="false"/>

ListView

ListView常见的XML属性

ListView常用的内置列表项布局

如果一个窗口仅需要显示一个列表,则可以直接让Activity继承ListActivity来实现

public class MainActivity extends ListActivity {
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
String ctype[] = {“项目1”,“项目2”,“项目3”,“项目4”};
ArrayAdapter<CharSequence> adapter = new
ArrayAdapter<CharSequence>(this,
android.R.layout.simple_list_item_1, ctype);
this.setListAdapter(adapter);
}
protected void onListItemClick(ListView l, View v, int position, long id) {
Toast.makeText(MainActivity.this,l.getItemAtPosition(position).toString(),0).show();
super.onListItemClick(l, v, position, id);
}
}
ListView中item与控件抢夺焦点的解决方法
  • 将ListView中的Item布局中的子控件focusable属性设置为false
  • 在getView方法中设置button.setFocusable(false)
  • 设置item的根布局的属性
    android:descendantFocusability="blocksDescendant"
    我们可以发现,其实这三种方法都是为了让Button等控件不能获取焦点,从而使得item可以响应点击事件。

第三种方法使用起来相对方便,因为它是将item布局中的其他所有控件都设置为不能获取焦点。

android:descendantFocusability属性共有三个取值,分别为

beforeDescendants:viewgroup会优先其子类控件而获取到焦点
afterDescendants:viewgroup 只有当其子类控件不需要获取焦点时才获取焦点
blocksDescendants:viewgroup 会覆盖子类控件而直接获得焦点
ListView滚动事件

ListView的滚动有三种状态
第一是静止状态,SCROLL_STATE_IDLE
第二是手指滚动状态,SCROLL_STATE_TOUCH_SCROLL
第三是手指不动了,但是屏幕还在滚动状态。SCROLL_STATE_FLING

getListView().setOnScrollListener(new OnScrollListener() {  
listViewScrollState;
public void onScrollStateChanged(AbsListView view, int scrollState) {
listViewScrollState = scrollState;
switch(scrollState){
case OnScrollListener.SCROLL_STATE_IDLE://空闲状态
break;
case OnScrollListener.SCROLL_STATE_FLING://滚动状态
break;
case OnScrollListener.SCROLL_STATE_TOUCH_SCROLL://触摸后滚动
break;
}
}

/** 
* 正在滚动
* firstVisibleItem第一个Item的位置
* visibleItemCount 可见的Item的数量
* totalItemCount item的总数
*/
firstVisibleItem the index of the first visible cell (ignore if visibleItemCount == 0)
visibleItemCount the number of visible cells
totalItemCount the number of items in the list adaptor

public void onScroll(AbsListView view, int firstVisibleItem,int visibleItemCount, int totalItemCount) {
//控制显示和隐藏的代码...
}
判断滚动到底部和顶部的方法
lv.setOnScrollListener(new OnScrollListener() { 

public void onScrollStateChanged(AbsListView view, int scrollState) {
switch (scrollState) {
// 当不滚动时
case OnScrollListener.SCROLL_STATE_IDLE:
// 判断滚动到底部
if (lv.getLastVisiblePosition() == (lv.getCount() - 1)) {

}


// 判断滚动到顶部
if(lv.getFirstVisiblePosition() == 0){

}
break;
}
}
public void onScroll(AbsListView view, int firstVisibleItem,int visibleItemCount, int totalItemCount) {

}

);
getListView().setOnScrollListener(new OnScrollListener() {  
public void onScrollStateChanged(AbsListView view, int scrollState){
}

public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
if(firstVisibleItem==0){
Log.e("log", "滑到顶部");
}

if(visibleItemCount + firstVisibleItem == totalItemCount){
Log.e("log", "滑到底部");
}
}
});

Android 中提供了较多的数据存储和交换方式,如SharedPreference+Preference Activity,File,SQLite+Content Provider,以及XML JSON 格式数据解析等。它们各有各的优点,而它们的优点也决定了它们的使用范围。

SharedPreferences

SharedPreferences 类提供给开发人员保存和获取基本数据类型的键值对的方式。该类主要用于类似基本类型,等简单变量,在应用程序结束后,数据仍旧会保存。这些数据会以XML文件的形式存储在apk的包里面。

SharedPreferences的获取:

SharedPreferences的获取方式有两种:

  1. getSharedPreferences():如果需要多个使用名称来区分的共享文件,则可以使用该方法,其第一参数就是共享文件的名称,对于使用同一个名称获得的多个SharedPreferences引用,其指向同一个对象。
  2. getPreferences():如果Activity仅需要一个共享文件,则可以使用该方法。因为只有一个文件,它并不需要提供名称。
向SharedPreferences类中增加值:

完成向SharedPreferences类中增加值的步骤如下:

  1. 调用SharedPreferences类的edit方法获得SharedPreferences.Editor对象
  2. 调用诸如putBoolean(),putString()等方法增加数据
  3. 使用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.getString("name","");
String password = sharedPrefereces.getString("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

  1. Preference screen定义文件存储在res/xml文件夹中.

  2. Preference Screen 定义格式如下:

    <?xml version=“1.0” encoding=“utf-8”?>
    <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" >
    </PreferenceScreen>
  3. PreferenceScreen 内部还可以嵌套另一个PreferenceScreen ,每个PreferenceScreen 将表示为一个可选的元素,单击它会出现一个新的屏幕。

  4. 在每个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

  1. 继承PreferenceFragment
  2. 在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;
}
}
  1. 使用Preference Screen设置的Shared Preference:

包括Activity ,Service 和BroadCast Receiver都能访问存储在应用程序沙袋中的Shered Preference值。可以使用如下方法:

SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
  1. 设置监听器
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().openRawResource(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_URI

每个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

所示表示请求一条记录。

  • 使用UriMatcher来解析URI请求:

通过解析可以确定这个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" />
  • 实现与Provider的接口

为了验证能够通过一个应用访问另一个应用开放的内容提供者,编写了一个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) {
//当Loader被初始化后,调用该方法返回一个Cursor Loader对象。
}
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.newSerializer();
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.endTag(null, "name");

xml.startTag(null, "password");
xml.text(user.getPassword());
xml.endTag(null, "password");
xml.endTag(null, "user");
}xml.endTag(null,"register");
xml.endDocument();
fos.close();
Toast.makeText(getApplicationContext(),"创建成功", 0).show();

使用XmlPushParser来解析一个SD卡中的Xml文件
XmlPullParser xml = Xml.newPullParser();
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();// 如果遇到一个大项则创建一个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下,主要有以下几个类: 

JSONObjectJSON对象,其包含一对(Key/Value)数值。最外被大括号包裹,其中的KeyValue被冒号":"分隔。在KeyValue之间是以逗号","分隔。例如:{"JSON": "Hello, World"}, Value的类型包括:BooleanJSONArrayJSONObjectNumberString或者默认值JSONObject.NULL
JSONStringer:JSON文本构建类 ,这个类可以帮助快速和便捷的创建JSON text。其最大的优点在于可以减少由于格式的错误导致程序异常,引用这个类可以自动严格按照JSON语法规则创建JSON text。每个JSONStringer实体只能对应创建一个JSON text。.
JSONArray:它代表一组有序的数值。数值以逗号”,”分隔(例如 [value1,value2,value3]。它的内部具有查询行为,get()和opt()两种方法都可以通过index索引返回指定的数值,put()方法用来添加或者替换数值。同样这个类的value类型可以包括:BooleanJSONArrayJSONObjectNumberString或者默认值JSONObject.NULL object
JSONTokener:Json解析类 
JSONException: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.toString());


解析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);

为什么要将资源独立出来

编写良好的代码往往在程序中调用它的资源,而不是直接将其写入代码中,这样做的好处是:

  1. 将应用程序资源存储于某个特定的位置更有利于管理他们,而且使得代码更容易阅读与管理。
  2. 类似字符串这样的外部资源能使得应用程序更容易根据不同的地区和语言来进行本地化处理。
  3. 不同的资源对于不同的设备而言是很必要的。

资源在Android工程资源目录中的存储方式


字符串资源

  • 基本字符串
<string name="header">Android简介</string>
<string name="content">Android是由安迪.鲁宾开发出来的一款移动操作系统,而后由Google公司收购</string>
<string name="footer">由xiaohai.lin编辑</string>

当字符串包含撇号或单引号的时候需要进行转义或者使用双引号括起来,如果包含双引号那么则需使用转义符号来转义。
对于字符串资源,可以为其添加3种具有HTML风格的属性,分别是加粗,斜体和下划线,可以使用

<b>,<i><u>

标记来指定这些风格。

  • 格式化字符串资源
<string name="demo_string3">The match is %1$d : %2$d Jimmy &lt;b&gt;%3$s&lt;/b&gt; the game </string>
tv = (TextView) findViewById(R.id.stringWithFormat);
String matchInfo = getResources().getString(R.string.demo_string3);
String escapeStr = TextUtils.htmlEncode("Won");
String finalStr = String.format(matchInfo, 5,2,escapeStr);
CharSequence result = Html.fromHtml(finalStr);
tv.setText(result);
  • 复数字符串资源
<plurals name = "unicorn">
<item quantity = "one">One item </item>
<item quantity = "other">%d items </item>
</plurals>
String unicon = getResources().getQuantityString(R.plurals.unicorn,unicorn,unicorn);

在XML中使用

@string/content

在java代码中

String strings = getResources().getString(R.string.content)返回的是字符串内容本身,所有的HTML格式均被删除
CharSequence texts = getResources().getText(R.string.content);返回的是带格式的内容

颜色资源

<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="red">#ff0000</color>
<color name="green">#00ff00</color>
<color name="blue">#0000ff</color>
注意#aarrggbb中的aa表示透明度0表示完全透明,F表示完全不透明
</resources>

在XML中使用

color/red

在java代码中

getResources().getColor(R.color.color1)

尺寸资源

<resources>
<dimen name="header">20px</dimen>
<dimen name="content">18px</dimen>
<dimen name="footer">10px</dimen>
</resources>

在XML中使用

"@dimen/header“

在java代码中

getResources().getDimension(R.dimen.content)

px: 对应屏幕上的每一个点
In:每英寸等于2.54cm
Pt: 磅 1/72厘米
Dp:一种基于屏幕密度的抽象单位,每英寸160点的显示器,1dp=1px
Sp:主要处理字体大小,可以根据用户字体大小首选项进行缩放
mm: 毫米

数组资源

<?xml version="1.0" encoding="utf-8"?>
<resources>
<string-array name="contract">
<item>项目1</item>
<item>项目2</item>
</string-array>
</resources>

在XML中使用

android:entries="@array/contract“

在java代码中

getResources().getStringArray(R.array.contract)

共有三类

<array>普通的数组
<string-array>字符数组
<integer-array>整形数组

图片资源

在Java代码中使用:

ImageView.setImageResources(R.drawable.head);

XML代码中使用

@drawable/head

样式资源

在res目录下的value下的style.xml下建立如下资源

<style name="basic">
<item name="android:textSize">58px</item>
<item name="android:textColor">#f60</item>
</style>
<style name="textStyle" parent="basic">
<item name="android:gravity">center</item>
</style>

使用如下:

<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/hello_world"
style="@style/textStyle"/>

布局资源

Java代码中:

R.layout.文件名

XML代码中:

@layout/文件名

主题资源

主题资源与样式资源类似,定义主题资源的资源文件也是保存在res/values目录下,其根元素同样是在该标记中也是使用

View 与 ViewGroup


Android中的所有可视化组件都是从View派生出来的,这些可视化组件通常被称为控件或者小组件,ViewGroup类是对View类的扩展,它用来包含多个视图,ViewGroup主要用于管理子视图布局和创建可重用的复合组件。

布局

LinearLayout

按照垂直方向或者水平方向对齐每个视图

RelativeLayout

相对于指定控件位置的设定项

FrameLayout

所有添加到这个布局中的视图都以类似堆栈的方式显示。第一个添加的控件被放在最底层,最后一个添加到框架布局中的视图显示在最顶层,上一层的控件会覆盖下一层的控件。布局中所有的控件都会默认出现在视图的左上角
如下所示:

AbsoluteLayout

只有两个属性,不大常用 

android:layout_x
android:layout_y

TableLayout

TableLayout布局由许多的TableRow组成,列是由行中的控件数目决定的。
TableLayout布局不会显示行、列 、单元格的边框线。下面是TableLayout的几个重要属性:
android:stretchColumns=“0,2“ //结果如下所示,第一列和第三列的控件将会尽量扩充

未指定android:shrinkColumns属性的时候(将按键3挤出了界面)

指定android:shrinkColumns=“1”属性的时候(相当于自动换行)

指定android:collapseColumns属性的时候会隐藏指定列

android:layout_column=“2“ //子组件从第2列开始
android:layout_span=“2“ //子组件横跨2列

TabLayout

为了使用Tab,必须使用TabHost和TabWidget,TabHost必须是布局文件中的根节点,它包含了tabWidget显示tabs,以及FrameLayout来显示tab的内容。

主布局

<TabHost xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id ="@android:id/tabhost"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<TabWidget
android:id ="@android:id/tabs"
android:layout_width="fill_parent"
android:layout_height="wrap_content" >
</TabWidget>
<FrameLayout
android:id ="@android:id/tabcontent"
android:layout_width="fill_parent"
android:layout_height="fill_parent" >
</FrameLayout>
</LinearLayout>
</TabHost>

Activity1布局

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="@drawable/android1" />
</LinearLayout>

Activity2布局

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="@drawable/android2" />
</LinearLayout>

Activity3布局

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="@drawable/android3" />
</LinearLayout>
public class MainActivity extends TabActivity {
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TabHost tabhost = this.getTabHost();
Intent intent = new Intent();
intent.setClass(this,activity1.class);
tabhost.addTab(tabhost.newTabSpec("Tab1").setIndicator("tab1",getResources().getDrawable(R.drawable.android1)).setContent(intent));
intent = new Intent();
intent.setClass(this,activity2.class);
tabhost.addTab(tabhost.newTabSpec("Tab2").setIndicator("tab2",getResources().getDrawable(R.drawable.android2)).setContent(intent));
intent = new Intent();
intent.setClass(this,activity3.class);
tabhost.addTab(tabhost.newTabSpec("Tab3").setIndicator("tab3",getResources().getDrawable(R.drawable.android3)).setContent(intent));
tabhost.setCurrentTab(1);
}
}

GridLayout

GridLayout是Android 4.0 SDK中新引进的布局:
GridLayout布局使用虚细线将布局划分为行、列和单元格。

  1. 它与LinearLayout布局一样,也分为水平和垂直两种方式,默认是水平布局,一个控件挨着一个控件从左到右依次排列,通过指定android:columnCount设置列数的属性后,控件会自动换行进行排列。
  2. 若要指定某控件显示在固定的行或列,只需设置该子控件的android:layout_row和android:layout_column属性即可.
  3. 如果需要设置某控件跨越多行或多列,只需将该子控件的android:layout_rowSpan或者layout_columnSpan属性设置为数值,再设置其layout_gravity属性为fill即可,前一个设置表明该控件跨越的行数或列数,后一个设置表明该控件填满所跨越的整行或整列。
<GridLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:columnCount="4">
<TextView
android:gravity="center"
android:layout_width="wrap_content"
android:layout_height="90dp"
android:layout_columnSpan="4"
android:layout_gravity="fill"
android:hint="请用键盘输入运算"
/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="C" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="/" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="*" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="DEL" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="7" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="8" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="9" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="-" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="4" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="5" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="6" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="+" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="1" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="2" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="3" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_rowSpan="2"
android:layout_gravity="fill_vertical"
android:text="=" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="0" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="." />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="+/-" />
</GridLayout>

布局的优化

  1. 使用merge标签:
    当包含有merge标签的布局被添加到另一个布局的时候,该布局的merge节点会被删除,而该布局的子View将会被直接添加到新的父布局。

  2. 使用include标签:
    Include 标签是用来把一个布局的内容插入到另一个布局中。通过标签可以将一个非常庞大的布局文件分解成若干个较小的布局文件,而且这些小的布局文件也可以被多次引用,从而达到一个重用的目的。

    <include android:id=“@+id/my_actionbar”
    layout=“@layout/actionbar”/>

    结合使用merge和include标签能够创建灵活的,可复用的布局定义,而不会创建深度嵌套的布局结构.

  3. View Stub
    使用提高了复用性,但是带来了另一个问题,就是布局文件中的控件并不一定在程序启动时全都用到,有一些控件只在特定的情况下才会被使用到。填充每个额外的View都需要耗费时间和资源,View的数量越多消耗的时间和资源就越可观,要想在复杂的布局内填充的View的数量最少,可以使用View Stub。
    ViewStub是一个不可见的,轻量级的View。它没有尺寸,也不会绘制以及以某种形式参与到布局中来。只有显式地调用inflate方法或者setVisibility(View.VISIBLE)的时候,这个View Stub才会填充。下面是View Stub的一个例子,按下”点击显示Stab View”按键后”按键1”,“按键2”会显示出来,局部代码见备注:

ViewStub的布局参数会随着加载的视图一同被添加到ViewStub父容器。我们也可以通过使用inflatedId属性来定义或重命名要加载的视图对象的Id值。

public class MainActivity extends Activity implements OnClickListener {

private Button btn = null;
private ViewStub viewStub = null;

protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btn = (Button) findViewById(R.id.btn);
btn.setOnClickListener(this);
viewStub = (ViewStub) findViewById(R.id.viewStub);
}
public void onClick(View v) {
if (viewStub != null) {
View anotherlayoutView = viewStub.inflate();// inflate()被调用时,
// 被加载的视图替代viewstub并且返回自己的视图对象
//anotherlayoutView相当于是包含进来的那个布局文件
anotherlayoutView.findViewById(R.id.btn1).setOnClickListener(
new OnClickListener() {
public void onClick(View v) {
Log.i("MainActivity", "按键1按下:" + v.getId());

}
});
anotherlayoutView.findViewById(R.id.btn2).setOnClickListener(
new OnClickListener() {
public void onClick(View v) {
Log.i("MainActivity", "按键2按下:" + v.getId());

}
});
viewStub = (ViewStub) findViewById(R.id.viewStub);
View view = findViewById(R.id.inflateViewStub);
Log.i("MainActivity", "" + view.toString());
}
}
}
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.example.testviewstub.MainActivity" >

<Button
android:id="@+id/btn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="点击显示Stab View" />

<ViewStub
android:id="@+id/viewStub"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:inflatedId="@+id/inflateViewStub"
android:layout="@layout/anotherlayout" />
</LinearLayout>
<!-- inflatedId就是新加载进来的view的id,如果需要获取这个view,就要用这个inflatedId,原来的id已经被取代了-->

在上面的例子中通过”viewStub” 可以找到被定义的ViewStub对象。加载布局资源文件”anotherlayout”后, ViewStub对象从其父容器中移除。可以通过id”inflateViewStub”找到由布局资源”anotherlayout”创建的View。这个视图对象最后被指定为宽120dip,高40dip:

LayoutInflater

LayoutInflater的作用:

LayoutInflater作用是将layout的xml布局文件实例化为View类对象。
在一个Activity里如果直接用findViewById()的话,对应的是setConentView()的那个layout里的组件.如果当前Activity里如果用到别的layout,比如对话框上的layout,你还要设置对话框上的layout里的组件(像图片ImageView,文字TextView)上的内容,就必须用inflate()先将对话框上的layout找出来,然后再用这个layout对象去找到它上面的组件,如:

Viewview = View.inflate(this,R.layout.dialog_layout,null);  
TextViewdialogTV = (TextView)view.findViewById(R.id.dialog_tv);   
dialogTV.setText(“你好");
获取LayoutInflater的三种方法:
LayoutInflater inflater =(LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View layout = inflater.inflate(R.layout.main, null);
LayoutInflater inflater = LayoutInflater.from(context); 
View layout = inflater.inflate(R.layout.main, null);
LayoutInflater inflater = getLayoutInflater();
View layout = inflater.inflate(R.layout.main, null);
Inflate 方法介绍
public View inflate(int Resourece,ViewGroup root)

作用:从指定的XML资源文件中填充一个新的视图层次结构
Resourece:View的layout的ID
root: 生成的层次结构的根视图 return 填充的层次结构的根视图。如果参数root提供了,那么root就是根视图;否则填充的XML文件的根就是根视图。
inflate只会把Layout形成一个以view类实现成的对象,有需要时再用setContentView(view)显示出来

Fragment是Android 3.0 新增加的部分,它的作用是将Activity拆分成多个完全独立封装的可重用的组件,每个组件都有它们的生命周期和UI布局。这样就可以为不同屏幕大小的设备创建动态灵活的UI。
一个Fragment必须被嵌入到一个Activity中,它的生命周期被其所属的宿主Acitivity的生命周期所影响。当Acitivity被暂停的时候,其中的所有Fragment被暂停,当Acitivity被销毁的时候,所有隶属于它的Fragment也将被销毁,当Acitivity处于Resumed状态的时候,可以单独得对每个Fragment进行添加或者删除操作。
和Activity不同的是Fragment不需要在Android Manifest文件中注册。

创建并添加Fragment

在layout文件夹下新建一个名为fragment1.xml的布局文件

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
android:layout_width="match_parent"
android:layout_height="match_parent" >
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="This is fragment 1" />
</LinearLayout>

新建一个fragment2.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
android:layout_width="match_parent"
android:layout_height="match_parent" >
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="This is fragment 2" />
</LinearLayout>

新建一个类Fragment1,这个类继承自Fragment

public class Fragment1 extends Fragment {  
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment1, container, false);
}
}

新建一个类Fragment2 :

public class Fragment2 extends Fragment {  
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment2, container, false);
}
}

静态创建Fragment

然后打开或新建activity_main.xml作为主Activity的布局文件,在里面加入两个Fragment的引用,使用android:name前缀来引用具体的Fragment:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:baselineAligned="false" >
<fragment
android:id="@+id/fragment1"
android:name="com.example.fragmentdemo.Fragment1"
android:layout_width="0dip"
android:layout_height="match_parent"
android:layout_weight="1" />
<fragment
android:id="@+id/fragment2"
android:name="com.example.fragmentdemo.Fragment2"
android:layout_width="0dip"
android:layout_height="match_parent"
android:layout_weight="1" />
</LinearLayout>

动态创建Fragment Fragment Manager

每个Activity都包含一个Fragment Manager来管理寄生于它的Fragment们,可以通过使用getFragmentManager方法来访问FragmentManger:

FragmentManager fragmentManager = getFragmentManager();

获得FragmentManager后可以做如下操作:

  1. 通过findFragmentById()或findFragmentByTag(),获取activity中的Fragment引用。
  2. 通过popBackStack()方法,从activity的后退栈中弹出fragment
  3. 通过addOnBackStackChangedListerner()注册一个侦听器监视后退栈的变化。
  4. 通过beginTransaction()获取FragmentTransaction。

Fragment Transaction 用来在Activity内添加,删除替换Fragment,使用Fragment Transaction 可以让布局成为动态。提交一组Fragment的变化叫做一个事务。事务通过FragmentTransaction来执行。还可以把每个事务保存在activity的后退栈中,这样就可以让用户在Fragment变化之间导航.

FragmentTransaction fragtransaction = fragmentManager.beginTransaction();
  • 添加Fragment

    fragmentsaction.add(用于存放flagment的容器ID,Fragment对象);
    //将Fragment对象添加到Fragment容器标签所指定的地方。
  • 删除Fragment

    Fragment fragment = fragmentManerger.findFragmentById(Fragment容器ID);
    fragmentsaction.remove(fragment);
  • 替换Fragment

    fragmentsaction.replace(用于存放flagment的容器ID,Fragment对象);
  • 当用户想要Back按键会返回到前一个布局,或者想要回滚到前一个已经执行的Transaction可以在调用commit方法前,调用addToBackStack将Fragment添加到back堆栈

    fragtransaction.addToBackStack(null);
  • 设置切换动画

    transaction.setTransition(FragmentTransaction.TRANSIT_ENTER_MASK);
    transaction.setCustomAnimations(enter, exit);
  • 提交事务

    fragtransaction.commit();

主界面布局

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
android:id="@+id/main_layout"
android:layout_width="match_parent"
android:layout_height="match_parent" >
</LinearLayout>
public class MainActivity extends Activity {  
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Display display = getWindowManager().getDefaultDisplay();
if (display.getWidth() > display.getHeight()) {
Fragment1 fragment1 = new Fragment1();
getFragmentManager().beginTransaction().replace(R.id.main_layout, fragment1).commit();
} else {
Fragment2 fragment2 = new Fragment2();
getFragmentManager().beginTransaction().replace(R.id.main_layout, fragment2).commit();
}
}
}

Fragment生命周期与Activity生命周期

  • onAttach:当fragment和它的父activity有关联的时候被调用,在此处可以获取Activity的引用。为进一步初始化做准备。

  • onCreate:调用该方法来进行Fragment的初始化。与Activity不同Fragment UI不在onCreate方法中初始化。

  • onCreateView:一旦Fragment被创建,要创建它自己的用户界面的时候调用该方法,在里面主要完成创建或者填充Fragment UI的工作,获取它所包含的View的引用以及绑定到该View的数据,创建所需的Server和Timer。

  • onActivityCreated:一旦父Activity和Fragment的UI创建后,通知fragment activity已经创建完成,在这个方法中主要完成父Activity被初始化完成或者Fragment的View被完全填充后才能做的事情。要和父Activity的UI交互也要等到这个阶段。

  • onStart:使fragment对用户可见(基于包含它的activity已经启动了)

  • onResume:使fragment与用户可进行交互(基于包含它的activity已经处于resume状态)在这个方法中主要工作是恢复所有暂停的Fragment需要的UI更新,线程或进程。这些在非活动状态是处于暂停的。

  • onPause:fragment不再与用户进行交互,要么由于activity处于暂停态,要么由于在activity中一个fragment的操作正在被修改,需要暂停UI的更新,挂起线程或者暂停那些不需要更新的CPU的集中处理,由于调用这个方法后,进程可能被终止,所以要保存所有的编辑和状态改变信息。

  • onStop:fragment不再与用户可见,要么因为它的activity被停止,要么因为在activity中一个fragment的操作正在被修改,在这里暂停其余的UI更新,挂起线程或者暂停不需要的处理

  • onDestroyView: 当Fragment的View被分离的时候调用该方法,这里主要用于清除资源相关的View。

  • onDestroy: 在整个生命周期结束的时候调用该方法去做fragment状态的最终清理,包括清除所有的资源,包括线程和关闭数据库连接。

  • onDetach:当fragment与其父activity分离的时候调用该方法。

onPause:fragment不再与用户进行交互,要么由于activity处于暂停态,要么由于在activity中一个fragment的操作正在被修改,需要暂停UI的更新,挂起线程或者暂停那些不需要更新的CPU的集中处理,由于调用这个方法后,进程可能被终止,所以要保存所有的编辑和状态改变信息。
onStop:fragment不再与用户可见,要么因为它的activity被停止,要么因为在activity中一个fragment的操作正在被修改,在这里暂停其余的UI更新,挂起线程或者暂停不需要的处理
onDestroyView: 当Fragment的View被分离的时候调用该方法,这里主要用于清除资源相关的View。
onDestroy: 在整个生命周期结束的时候调用该方法去做fragment状态的最终清理,包括清除所有的资源,包括线程和关闭数据库连接。
onDetach:当fragment与其父activity分离的时候调用该方法。

内置的Fragment

  • DialogFragment
    对话框式的Fragments。可以把fragmentdialog并入到到activity的返回栈中,使用户能再返回到这个对话框。
  • ListFragment
    显示一个列表控件,就像ListActivity类,它提供了很多管理列表的方法,onListItemClick和setListAdapter等。
  • PreferenceFragment
    显示一个Preference对象组成的列表,类似PreferenceActivity,主要用来创建设置界面。

Fragment之间进行通信

通常情况下,Activity都会包含多个Fragment,这时会遇到多个Fragment之间如何进行通信的问题。

fragment1.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    >  
<TextView
android:id="@+id/fragment1_text"
android:text="This is fragment 1" />
</LinearLayout>

fragment2.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
android:orientation="vertical">
<TextView
android:text="This is fragment 2" />
<Button
android:id="@+id/button"
android:text="Get fragment1 text"
/>
</LinearLayout>
public class Fragment2 extends Fragment {  
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment2, container, false);
}
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
Button button = (Button) getActivity().findViewById(R.id.button);
button.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
TextView textView = (TextView)
getActivity().findViewById(R.id.fragment1_text);
Toast.makeText(getActivity(), textView.getText(), Toast.LENGTH_LONG).show();
}
});
}
}

兼容多种屏幕方案

创建窄屏主布局文件

打开或新建res/layout/activity_main.xml作为程序的主布局文件

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="horizontal"
tools:context=".MainActivity" >
<fragment
android:id="@+id/menu_fragment"
android:name="com.example.fragmentdemo.MenuFragment"
android:layout_width="fill_parent"
android:layout_height="fill_parent"/>
</LinearLayout>

创建宽屏主布局文件

在res目录下新建layout-large目录,然后这个目录下创建新的activity_main.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
xmlns:tools="http://schemas.android.com/tools"
android:orientation="horizontal"
android:baselineAligned="false"
tools:context=".MainActivity"
>
<fragment
android:id="@+id/left_fragment"
android:name="com.example.fragmentdemo.MenuFragment"
android:layout_width="0dip"
android:layout_height="fill_parent"
android:layout_weight="1"
/>
<FrameLayout
android:id="@+id/details_layout"
android:layout_width="0dip"
android:layout_height="fill_parent"
android:layout_weight="3">
</FrameLayout>
</LinearLayout>

menu_fragment.xml文件
左边菜单面板的布局,这个是窄屏的第一个页面,用于触发两个子活动,宽屏的时候作为左面板

<?xml version="1.0" encoding="UTF-8"?>  
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent" >
<ListView
android:id="@+id/menu_list"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
></ListView>
</LinearLayout>


public class MenuFragment extends Fragment implements OnItemClickListener {  
private ListView menuList;
private ArrayAdapter<String> adapter;
private String[] menuItems = { "Sound", "Display" };
private boolean isTwoPane;
public void onAttach(Activity activity) {
super.onAttach(activity);
adapter = new ArrayAdapter<String>(activity, android.R.layout.simple_list_item_1, menuItems);
}
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.menu_fragment, container, false);
menuList = (ListView) view.findViewById(R.id.menu_list);
menuList.setAdapter(adapter);
menuList.setOnItemClickListener(this);
return view;
}
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
if (getActivity().findViewById(R.id.details_layout) != null) {
isTwoPane = true;
} else {
isTwoPane = false;
}
}
public void onItemClick(AdapterView<?> arg0, View view, int index, long arg3){
if (isTwoPane) {
Fragment fragment = null;
if (index == 0) {
fragment = new SoundFragment();
} else if (index == 1) {
fragment = new DisplayFragment();
}
getFragmentManager().beginTransaction().replace(R.id.details_layout, fragment).commit();
} else {
Intent intent = null;
if (index == 0) {
intent = new Intent(getActivity(), SoundActivity.class);
} else if (index == 1) {
intent = new Intent(getActivity(), DisplayActivity.class);
}
startActivity(intent);
}

}

sound_fragment.xml布局文件

<?xml version="1.0" encoding="utf-8"?>  
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#00ff00"
android:orientation="vertical" >
<TextView
android:layout_centerInParent="true"
android:textSize="28sp"
android:textColor="#000000"
android:text="This is sound view"
/>
</RelativeLayout>

SoundFragment类

public class SoundFragment extends Fragment {  
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.sound_fragment, container, false);
return view;
}
}

display_fragment.xml布局文件

<?xml version="1.0" encoding="utf-8"?>  
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#0000ff"
android:orientation="vertical" >
<TextView
android:layout_centerInParent="true"
android:textSize="28sp"
android:textColor="#000000"
android:text="This is display view"
/>
</RelativeLayout>

DisplayFragment类

public class DisplayFragment extends Fragment {  
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.display_fragment, container, false);
return view;
}
}

sound_activity.xml

<?xml version="1.0" encoding="utf-8"?>  
<fragment xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/sound_fragment"
android:name="com.example.fragmentdemo.SoundFragment"
android:layout_width="match_parent"
android:layout_height="match_parent" >
</fragment>

SoundActivity

public class SoundActivity extends Activity {  
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.sound_activity);
}
}

display_activity.xml

<?xml version="1.0" encoding="utf-8"?>  
<fragment xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/display_fragment"
android:name="com.example.fragmentdemo.DisplayFragment"
android:layout_width="match_parent"
android:layout_height="match_parent" >
</fragment>

DisplayActivity类

public class DisplayActivity extends Activity {  
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.display_activity);
}
}

Fragment与Activity共享事件处理方法

在Fragment需要和主Activity共享和事件的地方,可以在Fragment中创建一个callback接口,主Acitivity来实现它:如下所示:

Public interface sharedFunction{
public void sharefun();
}

在Acitivity中实现后,在Fragment中可以如下访问:

sharedFunction shared = (sharedFunction )activity;
Shared. sharefun();

啥是Content provider

Content Provider 用于保存和获取数据并使其对所有应用程序可见。这是不同应用程序间共享数据的唯一方式。在Android中没有提供所有应用共同访问的公共存储区域。
Content Provider 内部如何保存数据由其设计者决定,但是所有的Content Provider 都实现一组通用的方法用来提供数据的增,删,查,改功能。
客户端通常不会直接调用这些方法,大多数是通过Content Resolver 对象实现对Content Provider 的操作,开发人员可以通过调用Activity或者其他应用程序组件的实现类中的getContentResolver来获取ContentResolver对象。

数据模型

Content Provider 使用基于数据库模型的简单表格来提供其中的数据,这里每一行代表一条记录,每列代表特定类型和含义的数据。每条记录包含一个数值型的_ID字段,它用于在表格中唯一标识该记录。

URI

每个Content Provider 提供公共的URI来唯一标识其数据集,管理多个数据集(多个表格)的Content Provider 为每个数据集提供了单独的URI,所有为provider提供的URI都以content://作为前缀,用于识别应该使用哪个provider及其中的哪个表格。
如果自定义一个Content Provider 则应该为其URI也定义一个常量。Android为当前平台提供的Content Provider 定义了一个CONTENT_URI常量,匹配电话号码到联系人表格的URI和匹配保存联系人照片的表格的URI分别如下:
android.provider.Contacts.Phones.CONTENT_URI
android.provider.Contacts.Photos.CONTENT_URI

A:标准的前缀,用于表示该数据由Content Provider管理。
B:URI的授权部分,它标识该Content Provider。对于第三方应用,该部分应该是完整的类名(使用小写形式)来唯一确定。
C:Content Provider 的路径部分,用于决定哪类数据被请求,如果Content Provider 仅提供一种数据类型,这部分可以省略,如果provider提供几种类型,包括子类,这部分可以由几个部分组成。
D:被请求的特定记录的ID值,这是被请求数据记录的_ID,如果请求不限于单条记录该部分及前面的斜线应该删除.

系统定义的Content Provider

  • Browser:读取或者修改书签,浏览历史或者网络搜索
  • CallLog:查看或更新通话历史
  • Contacts:读取,修改或保存联系人信息
  • LiveFolders:由Content Provider 提供内容的特定文件夹
  • MediaStore:访问声音,视频和图片
  • Setting:查看和获取蓝牙设置,铃声等偏好设置
  • SearchRecentSuggestions:能被配置以使用查找意见的provider操作
  • SyncStateContract:用于使用数据数组账号关联数据的Content Provider约束。
  • UserDictonary:在可预测文本输入时,提供用户定义单词给输入法使用,应用程序和输入法能够增加数据到该字典,单词能关联频率信息和本地化信息。

自定义Content Provider

  1. 建立数据存储系统。大多数Content Provider 使用Android文件存储方法或者SQLite数据库保存数据,但是开发人员可以使用任何方式存储
  2. 继承ContentProvider类来提供数据访问
  3. 在应用程序的AndroidManifest文件中声明Content Provider
继承ContentProvider类

开发人员定义ContentProvider类的子类,以便使用ContentResolver和Cursor类带来的便捷来共享数据。要达到这个目的原则上要实现如下的几个方法

由于这些ContentProvider方法能够被位于不同进程和线程的不同Content Resolver对象调用,它们必须以线程安全的方式实现。
此外开发人员可能也想调用Content Resolver.notifyChange()方法以便在数据修改时候通知监听器

定义CONTENT_URI:

开发人员必须为该值定义一个唯一的字符串

public static final Uri  CONTENT_URI= Uri.parse("content://com.example.employee");
````
如果有子表可以使用如下定义:

public static final Uri CONTENT_URI= Uri.parse(“content://com.example.employee/dba”);

##### 定义内容提供者将返回给客户端的列名:
如果开发人员使用的是底层数据库,这些列明通常与SQL列明相同,确保包含名为_ID的整数列作为记录的ID值。
如果使用SQLite数据库,_ID字段应该是INTEGER PRIMARY KEY AUTOINCREMENT.
##### 注释每列的数据类型
如果开发人员正在处理新数据类型,则必须定义新的MIME类型,以便在ContentProvider.getType方法中返回.
##### 声明内容提供者



啥是Intent

在一个Android程序中,主要由3种组件(Activity,BroadCast,Service)组成的,这三种组件是独立的,但是它们之间可以相互调用,协调工作。这些组件之间的通信主要是由Intent来完成的。Intent在其中起着一个媒体中介的作用,专门提供组件相互调用的相关信息,实现调用者与被调用者之间的解耦。
Intent负责对应用中一次操作的动作,动作涉及的数据以及附加数据进行描述,Android则根据此Intent的描述负责找到对应的组件,将Intent传递给调用的组件,并完成组件的调用。

Intent对象的组成

  • 组件名字 Component:定位合适的目标组件
  • 动作 Action:用来标示要广播的事件,动作很大程度上决定了数据和附加信息,就像一个方法名决定了参数和返回值一样,因此应尽可能明确指定动作,同时必须确保动作字符串的唯一性,一般使用java包名。
  • 数据 data :数据是作用Intent上的数据的URI和数据的MIME类型,不同的数据具有不同的规范,当匹配一个Intent到一个能够处理数据的组件的时候,明确其数据类型(MIME)和URI很重要。
  • 种类 Catoragry :用于作为被执行动作的附加信息
  • 附加信息 Extra :传输数据的重要方法
  • 标志 Flag :主要用来指示Android程序如何去启动一个活动,例如,活动应该属于哪个任务。和启动后如何对待它,例如它是否属于最近的活动列表。
组件名称

组件名称是可选的,如果设置了,Intent对象传递到指定的类的实例,如果没有设置,Android使用Intent中的其他信息来定位合适的目标组件。组件名字的设置可用如下方法。

动作 标准的Activity动作


在上述两个表中,需要将其转换为对应的字符串信息,比如将ACTION_TIME_TICK转换为android.intent.action.TIME_TICK
除了预定义的动作,我们还可以自定义动作字符串,来启动应用程序中的组件,这些新发明的字符串应该包含一个应用程序包作为前缀,如com.exemple.SHOW_COLOR

数据

种类


附加信息

标志


Intent 作用

Intent用处可归纳如下几点:

  1. 负责Android设备上安装的任意应用程序组件之间的交互,不管它们是哪个应用程序的一部分都是如此,这就把设备从一个包含相互独立的组件集合的平台变成一个互联的系统。
  2. 通过定义隐式或者显式的Intent来启动Activity或者Service。
  3. 用于在系统范围内广播消息,应用程序可以通过注册一个Broadcast Receiver来监听和响应这些广播的Intent,这样就可以基于内部的,系统的或者第三方应用程序的事件创建事件驱动的应用程序。
  4. Android通过广播Intent来公布系统事件,比如网络状态,电池电量的改变。本地Android应用简单得注册监听特定的广播Intent并作出相应的响应的组件,这样就可以通过监听相同的Intent 的BroadCast Receiver来替换本地提供的应用程序。
使用Intent 启动 Activity
  • 显式启动Activity (条件:目标Activity必须在manifest文件中注册):

    Intent intent = new  Intent(MainActivity.this,Detail.class);
    startActivity(intent);

    上述代码执行后,新的Acitivity将会被创建,启动和运行,并移动到Activity堆栈的顶部。

  • 隐式启动Activity
    隐式启动可以要求系统启动一个可执行给定动作的Activity,而不必知道需要启动哪个应用程序或者Acitivity,这种情况下运行时会自动解析Intent来选择最合适的Activity,如果有多个Acitivity都能够执行指定动作,那么系统将会向用户呈现选项,让用户来选择。

方法一:指定ACTION以及执行当前ACTION所需数据的URI

Intent intent = new Intent(Intent.ACTION_DIAL,Uri.parse(“tel:555-2368”));

方法二:使用自定义的Activity来响应当前的隐式Intent

Intent intent = new Intent("com.androidDemo.intent.action.ShowView");

在manifest中添加意图过滤器intent-filer

<activity android:name=".BasicViewActivity" android:label="Basic View Tests">
<intent-filter>
    <action android:name=" com.androidDemo.intent.action.ShowView "/>
        <category android:name=“android.intent.category.DEFAULT”/>(一定要加)
</intent-filter>
</activity>
  • 关闭Activty
    finish()
    如果当前的Activity不是主活动,那么执行finish方法后,返回到调用它的那个Activity,否则将返回到主屏幕中。
    关闭Activity还可以使用finishActivity方法实现该方法用来关闭使用startActivityForResult方法启动的Activity。
使用Intent在Activity之间交换数据

Extra作为一个Bundle对象存储在Intent中,用于向Intent附加基本类型值,可以使用putExtra可以添加附加的数据,在目标Activity中使用getExtras方法来取出数据。
下面是使用Intent在Acitivity之间传输数据的代码片段:
发送方:

Intent intent = new Intent(MainActivity.this,RegisterActivity.class);
Bundle bundle = new Bundle();
bundle.putCharSequence("user",user);
bundle.putSerializable("info",info);
intent.putExtras(bundle);
startActivity(intent);

接收方:

Intent intent = getIntent();
Bundle bundle = intent.getExtas();
bundle.getString("usr");
bundle.getSerializable("info");
调用另一个Activity并返回结果

通过startAcitivity启动Acitivity,在关闭的时候不会返回任何数据。因此如果需要在调用Acitivity后返回数据则需要使用startActivityForResult来实现,代码块如下:

发送方:

startActivityForResult(intent,request_code);
protected void onAcitvityResult(int requestCode,int resultCode,Intent data){
if(requestCode==xxxx&&resultCode=yyyyy){
data.getExtas();
...............................
}
}

接收方:

setResilt(0x717,intent);
finish();
确定Intent能否解析

在使用Intent启动一个Acitivity之前,必须先确定用户设备上安装了能够处理当前请求的应用程序。这个可以通过调用Intent的resolveActivity方法,并向该方法传入Package Manager 进行查询。具体的代码块如下:

PackageManager pm = getPackageManager();
ComponentName cn = intent.resolveAcitivity(pm);
if(cn == null){
//表示没有找到对应的应用程序
//可以选择禁止相关的功能,或者到应用市场下载对应的应用程序
}else{
//表示找到对应的应用程序
startActivity(intent);
}
使用Intent 启动和绑定 Service
  • 启动服务
    intent = new Intent(this, EchoService.class);
    startService(intent)
  • 停止服务
    intent = new Intent(this, EchoService.class);
    stopService(intent);
  • 绑定服务
    intent = new Intent(this, EchoServices.class);
    bindService(intent, conn, Context.BIND_AUTO_CREATE);
  • 解除绑定服务
    unbindService(conn);
    使用Intent 发送BroadCast Intent
    Intent intent = new Intent(Constant.ACTION_NAME);
    sendBroadcast(intent);//普通广播
    sendOrderedBroadcast(intent, null);//有序广播
    sendStickyBroadcast(intent);//粘性广播

意图过滤器 IntentFilter

当在android程序中使用显示意图的时候,意图对象中只用组件名字内容就可以决定那个组件应该获得这个意图,而使用隐式意图的时候android程序必须查找一个最适合的组件去处理意图,该过程是通过比较意图对象内容和意图过滤器来完成的。
如果一个组件没有任何Intent过滤器,它仅仅能接收显示的意图,而声明了意图过滤器的组件可以接收显示和隐式意图。
当一个意图对象的动作,数据(URI和数据类型),种类,这三者都符合一个意图过滤器的时候,才能考虑是否接收意图,而附加信息和标识在解析哪个组件接收意图时不起作用。

意图过滤器的动作匹配

动作测试:

<intent-filter>
<action android:name="com.example.project.SHOW_CURRENT"/>
<action android:name="com.example.project.SHOW_RECENT"/>
<action android:name="com.example.project.SHOW_PEDDING"/>
......
</intent-filter>

尽管意图对象仅定义一个动作,在意图过滤器中可以列出多个,列表不能为空,即过滤器中必须至少包含一个标签,否则会阻塞所有Intent
规则:
如果Intent Filter 包含了指定的动作,那么就认为动作匹配了,如果检测到没有任何一个动作和Intent指定的动作相匹配,就认为动作匹配失败了。

意图过滤器的种类匹配

种类测试:

<intent-filter>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
......
</intent-filter>

在接收隐式意图的Activity必须在过滤器中包含android.catagory.DEFAULT.
Intent Filter 必须包含待解析Intent的所有Category,但是可以包含Intent不包含的其他category。一个没有指定category的Intent Filter只能和没有任何category的Intent相匹配。

意图过滤器的数据匹配

数据测试:

<intent-filter>
<data android:mimeType="video/mpeg" android:scheme="http"/>
<data android:mimeType="audio/mpeg" android:scheme="http"/>
......
</intent-filter>

每个标签可以指定URI和数据类型,URI可以分成scheme,host,port,path这几个独立的部分,格式如下

scheme://host:port/path

例如:content://com.example.project:200/folder/subfolder/etc
其中scheme是content,host是com.example.project,port是200,path是/folder/subfolder/etc,host和port组成了URI授权,如果host没有指定则忽略port。这些属性都是可选的但是相互之间并非完全独立的,如果授权有效,则sheme必须指定,如果path有效,则sheme和授权必须指定。当意图对象中的URI与过滤器中的URI规范相比较的时候,它仅与过滤器中实际提到的URI部分相比较,例如过滤器仅指定了sheme那么所有具有该sheme的URI都能匹配该过滤器,如果过滤器指定了scheme和授权没有指定path那么,不管path如何,具有该sheme授权的URI都能匹配,如果过滤器中指定了sheme,授权和path则仅有具有相同sheme,授权和path的URI能够匹配。然而过滤器中的path中可以包含通配符来允许部分匹配。
(也就是只要Intent的数据是Intent Filter数据集合中的一项就可以通过匹配)mimeType:Intent对象和过滤器都能使用*来包含子类型,例如text/*或者audio/

BroadCast Receiver是啥

Broadcast Receiver是用于接收广播通知的组件。广播是一种同时通知多个对象的事件通知机制。
Android中的广播来源于系统事件,例如按下拍照键,电池电量低,安装新应用等。还有普通应用程序。如启动特定线程,文件下载完毕等。
广播接收器通常初始化独立的组件或者在onReceiver方法中发送通知给用户,如果广播接收器需要完成更加耗时的任务,应该启动一个服务而不是一个线程,因为不活跃的广播接收器可能被系统停止。

BroadCast Receiver分类

  • 普通广播
    sendBroadcast()这个方法的广播能够按照注册的先后顺序发送给所有广播接收者,如果设置了广播接收者的优先级,优先级如果恰好与注册顺序相同,则不会有任何问题,如果顺序不一样,会出leaked IntentReceiver 这样的异常,并且在前面的广播接收者不能调用abortBroadcast()方法将其终止,如果调用会出BroadcastReceiver trying to return result during a non-ordered broadcast的异常,先接收到广播的receiver可以修改广播数据。
  • 有序广播
    sendOrderedBroadcast()方法priority的属性能起作用,并且在队列前面的receiver可以随时终止广播的发送。还有这个api能指定final的receiver,这个receiver是最后一个接收广播时间的receiver,并且一定会接收到广播事件,是不能被前面的receiver拦截的。这个特性可以用来统计系统中能监听某种广播的Receiver的数目。
  • 粘性的广播
    sendStickyBroadcast()字面意思是发送粘性的广播,使用这个api需要权限android.Manifest.permission.BROADCAST_STICKY,粘性广播的特点是Intent会一直保留到广播事件结束,而这种广播也没有所谓的10秒限制,10秒限制是指普通的广播如果onReceive方法执行时间太长,超过10秒的时候系统会将这个广播置为可以被销毁的候选,一旦系统资源不够的时候,就会撤销这个广播。

使用BroadCast Receiver 监听广播

要让BroadCast Receiver 监听广播需要完成如下操作:

  1. 在代码中进行动态注册广播,或者在Manifest 清单文件中静态注册
    二者的对比如下:
  2. 使用意图过滤器Intent Filter 来指定它要监听的BroadCast Intent
  3. 创建新的BroadCast Receiver 重写onReceive方法,在接收到一个通过Intent Filter 过滤后的BroadCast Intent,就会执行onReceive方法,在onReceive方法中可以更新内容,启动Service,更新UI或者使用Notification Manager来通知用户。BroadCast 也运行在主线程中,因此主要的处理工作不能在BroadCast Receiver中直接完成
动态注册广播
  • 动态注册:
    IntentFilter intentFilter = new IntentFilter(Constant.ACTION_NAME);
    broadCast = new EchoBroadCast();
    registerReceiver(broadCast, intentFilter);
  • 取消注册:
    unregisterReceiver(broadCast);
  • 发送广播:
    Intent intent = new Intent(Constant.ACTION_NAME);
    Bundle b = new Bundle();
    b.putString("Message", "This is The Message From MainActivity!");
    intent.putExtras(b);
    sendBroadcast(intent);
  • 服务类的编写:
    public class EchoBroadCast extends BroadcastReceiver {

    public void onReceive(Context context, Intent intent) {
    String ACTION = intent.getAction();
    if(ACTION.equals(Constant.ACTION_NAME)){
    Log.i(Constant.TAG_NAME, intent.getExtras().getString("Message"));
    }
    }
    }
    静态注册BroadCast Receiver
    <receiver android:name="com.example.testbroadcastreceiverregiststatic.EchoBroadCast" >
    <intent-filter>
    <action android:name="com.example.testbroadcastreceiverregiststatic.ACTION" />
    </intent-filter>
    </receiver>

OrderedBroadCast

当发送有序广播的时候,Intent将会按照优先级顺序被传递给所有具有合适权限的已经注册的接收器,优先级越大越先接收到。

启动广播代码:

sendOrderedBroadcast(intent, null);

广播中的重要方法:

setResultExtras(b); // 优先接收到Broadcast的接收者可以通过setResultExtras(Bundle)方法将处理结果存入Broadcast中,然后传给下一个接收者
Bundle b = getResultExtras(true);
int count = b.getInt("Number");// 将数据取出
abortBroadcast();// 终止广播

清单文件中注册:

<receiver  android:name="com.example.testbroadcastordered.FirstReceiver">
<intent-filter android:priority="4" >
<action android:name="com.example.testbroadcastordered.ACTION" />
</intent-filter>
</receiver>

第一个广播代码:

public void onReceive(Context context, Intent intent) {
Log.i(Constant.Tag_NAME, "FirstReceiver");
Bundle b = intent.getExtras();
int count = b.getInt("Number");// 将数据取出
count++; // 修改数据
b.putInt("Number", count); // 将数据存入
setResultExtras(b); // 优先接收到Broadcast的接收者可以通过setResultExtras(Bundle)方法将处理结果存入Broadcast中,然后传给下一个接收者
}

其他广播代码:

public void onReceive(Context context, Intent intent) {
Log.i(Constant.Tag_NAME, "SecondReceiver");
Bundle b = getResultExtras(true);
int count = b.getInt("Number");// 将数据取出
count++; // 修改数据
b.putInt("Number", count); // 将数据存入
setResultExtras(b); // 优先接收到Broadcast的接收者可以通过setResultExtras(Bundle)方法将处理结果存入Broadcast中,然后传给下一个接收者
}

最后一个广播代码:

public void onReceive(Context context, Intent intent) {
Log.i(Constant.Tag_NAME, "LastReceiver");
Bundle b = getResultExtras(true);
int count = b.getInt("Number");// 将数据取出
count++; // 修改数据
Log.i(Constant.Tag_NAME, "一共经过了:" + count + " 个广播");
abortBroadcast();// 终止广播
}

StickyBroadCast

Sticky Intent 可以保存它们最后一次广播的值,并且当有一个新的接收者,被注册为接收该广播的时候,它们会把这些值作为Intent返回,当调用regesterReceiver来指定一个匹配的Sticky BroadCast Intent 的intentFilter 时,返回值将是最后一次Intent广播。
要广播自己的Sticky,必须拥有BROADCAST_STICKY用户权限,不是必须指定一个接收器来获取Sticky Intent的当前值。

注册:

IntentFilter filter = new IntentFilter("com.example.testbroadcaststicky.ACTION");
registerReceiver(new EchoBroadCast(), filter);

发送:

intent = new Intent("com.example.testbroadcaststicky.ACTION");
intent.putExtra("Number",88888888);
sendStickyBroadcast(intent);

移除:

removeStickyBroadcast(intent);

设置权限:

android.permission.BROADCAST_STICKY
public void onClick(View v) {
switch (v.getId()) {
case R.id.btnSendStickyBroadCast:
intent = new Intent("com.example.testbroadcaststicky.ACTION");
for(int i=0;i<10;i++){
intent.putExtra("Number",i);
sendStickyBroadcast(intent);
}
break;
case R.id.btnSendAnotherStickyBroadCast:
intent = new Intent("com.example.testbroadcaststicky.ACTION");
intent.putExtra("Number",88888888);
sendStickyBroadcast(intent);
break;
case R.id.btnRemoveStickyBroadCast:
if(isregistered){
removeStickyBroadcast(intent);
isregistered = false;
}
break;
case R.id.btnRegisterStickyBroadCast:
IntentFilter filter = new IntentFilter("com.example.testbroadcaststicky.ACTION");
registerReceiver(new EchoBroadCast(), filter);
isregistered = true;
Log.i("EchoBroadCast", "注册成功");
break;
default:
break;
}
}



从上面截图可以看出在发送10次Intent后最后一个intent数值为9,下一次注册StickyBroadCast的时候接收到的是上一次的最后一个Intent,数值为9,如果再发送一次值为88888888的Intent,这时候最后一个Intent的值为88888888,如果再次注册,返回的是88888888

广播接受者实现过程需要注意的点

  1. 广播接收者的生命周期是非常短暂的,在接收到广播的时候创建,onReceive()方法结束之后销毁。
  2. 广播接收者中不要做一些耗时的工作,否则会弹出Application No Response错误对话框。
  3. 最好也不要在广播接收者中创建子线程做耗时的工作,因为广播接收者被销毁后进程就成为了空进程,很容易被系统杀掉。
  4. 耗时的较长的工作最好放在服务中完成。