上周花了不到一个周研究了一下android中的Calendar这个东西,下面把我的收获与大家分享一下。
我的研究是从阅读官方文档开始的。官方文档上的开头部分涉及到了一个ContentProvider和ContentResolver的东西。这个概念在android开发中很重要。
为了在应用程序之间交换数据,android提供了ContentProvider,ContentProvider是不同应用程序之间进行数据交换的标准API,当一个应用程序需要把自己的数据暴露给其他程序使用时,该应用程序就可以通过提供ContentProvider实现。其他应用程序就可以通过ContentResolver来操作ContentProvider暴露的数据(一般是以数据库的一个表的形式暴露,因此用ContentResolver操作数据时,也很类似对数据库的表的操作)。
ContentProvider也是android应用的四大组件之一,与Activity、Service、BroadcastReceiver相似,它们都需要在AndroidManifest.xml中配置。
还有,一般来说,ContentProvider是单例模式的,当多个应用程序通过ContentResolver来操作ContentProvider提供的数据时,ContentResolver调用的数据将会委托给同一个ContentProvider处理。
一旦某个应用程序通过ContentProvider暴露了自己的数据操作接口,那么不管该应用程序是否启动,其他应用程序都可以通过该接口来操作该应用程序的内部数据,包括增删改查。
还有一个很重要的东西叫Uri(不是Url,其实和Url作用很像的)。通俗一点说就是当你用ContentResolver操作数据时,用Uri来指明数据的url。
使用ContentResolver操作数据的步骤其实很简单:首先调用Activity的getContentResolver( )获取ContentResolver对象,然后根据需要调用ContentResolver的insert( )、update( )、delete( ) 和query方法操作数据即可。为了操作数据,我们需要了解ContentProvider的Uri。这也是上述三个概念的简单联系。
扯了这么多,其实这个Demo中ContentProvider和ContentResolver的概念体现的不是很明显。因为我们用的是操作系统给我们的ContentProvider,然后我们自己获取ContentResolver对象来操作数据,只需要了解系统的响应的ContentProvider的Uri即可。
这个Demo实现了为手机上的某个账户添加新的event(并为新添加的event添加reminder),查询所有账户的所有Calendar,删除event等(修改event只需要类似地调用ContentResolver的相应的方法即可,本Demo没有实现),其他还用到了将手机设置成震动,全局定时器、焦点事件以及TimePickerDialog、DatePickerDialog等,下面上图:
Android4.0第一次使用引导
Android4.0待机
下面是本Demo的效果:
下面是AndroidCalendarProviderTestActivity.java的代码:
package org.ls;
import java.text.SimpleDateFormat;
import java.util.Date;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.ContentResolver;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.provider.CalendarContract.Calendars;
import android.provider.CalendarContract.Events;
import android.text.method.ScrollingMovementMethod;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
public class AndroidCalendarProviderTestActivity extends Activity {
private Button showCalendars;
private Button addEvents;
private Button queryEvents;
private TextView displayEnvents;
private EditText getEventIdEditText;
private Button delEvent;
public static final String[] EVENT_PROJECTION = new String[] {
Calendars._ID, Calendars.ACCOUNT_NAME,
Calendars.CALENDAR_DISPLAY_NAME };
private static final int PROJECTION_ID_INDEX = 0;
private static final int PROJECTION_ACCOUNT_NAME_INDEX = 1;
private static final int PROJECTION_DISPLAY_NAME_INDEX = 2;
long myEventsId = 0;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
displayEnvents = (TextView) findViewById(R.id.displayevents);
displayEnvents.setMovementMethod(ScrollingMovementMethod.getInstance());
showCalendars = (Button) findViewById(R.id.querycalendars);
showCalendars.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Cursor cur = null;
ContentResolver cr = getContentResolver();
Uri uri = Calendars.CONTENT_URI;
// String selection = "((" + Calendars.ACCOUNT_NAME + // 给出查询条件,查询特定用户的日历
// " = ?) AND ("+ Calendars.ACCOUNT_TYPE + " = ?))";
// String[] selectionArgs = new String[]
// {"liushuaikobe@Gmail.com", "com.google"};
cur = cr.query(uri, EVENT_PROJECTION, null, null, null); // 查询条件为null,查询所有用户的所有日历
while (cur.moveToNext()) {
long calID = 0;
String displayName = null;
String accountName = null;
calID = cur.getLong(PROJECTION_ID_INDEX);
displayName = cur.getString(PROJECTION_DISPLAY_NAME_INDEX);
accountName = cur.getString(PROJECTION_ACCOUNT_NAME_INDEX);
showMessageDialog("日历ID:" + calID + "\n" + "日历显示名称:" + "\n"
+ displayName + "\n" + "日历拥有者账户名称:" + "\n"
+ accountName);
}
}
});
addEvents = (Button) findViewById(R.id.addevents);
addEvents.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent itent=new Intent();
itent.setClass(AndroidCalendarProviderTestActivity.this,AddNewEventActivity.class);
startActivity(itent); // 启动添加新event的Activity
}
});
queryEvents = (Button) findViewById(R.id.queryevents);
queryEvents.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
ContentResolver cr = getContentResolver();
Cursor cur = cr.query(Events.CONTENT_URI, new String[] {
Events._ID, Events.TITLE, Events.DESCRIPTION,
Events.DTSTART, Events.DTEND },
/* Events._ID + "=" + myEventsId */null, null, null); // 注释中的条件是是查询特定ID的events
displayEnvents.setText("");
while (cur.moveToNext()) {
Long tempEventsId = cur.getLong(0);
String tempEventsTitle = cur.getString(1);
String tempEventsDecription = cur.getString(2);
String tempEventsStartTime = cur.getString(3);
String tempEventsEndTime = cur.getString(4);
displayEnvents.append(tempEventsId + "\n");
displayEnvents.append(tempEventsTitle + " "
+ tempEventsDecription + "\n");
displayEnvents.append(new SimpleDateFormat(
"yyyy/MM/dd hh:mm").format(new Date(Long
.parseLong(tempEventsStartTime)))
+ "至");
displayEnvents.append(new SimpleDateFormat(
"yyyy/MM/dd hh:mm").format(new Date(Long
.parseLong(tempEventsEndTime)))
+ "\n");
}
}
});
getEventIdEditText = (EditText) findViewById(R.id.geteventid);
delEvent = (Button) findViewById(R.id.delevent);
delEvent.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Long tempEventId = 0l;
try {
tempEventId = Long.parseLong(getEventIdEditText.getText()
.toString());
} catch (Exception e) {
showMessageDialog("请先查询所有event,然后正确填写event的id~");
return;
}
// 另一种删除event方式
// Uri deleteUri =
// ContentUris.withAppendedId(Events.CONTENT_URI, tempEventId);
// int rows = getContentResolver().delete(deleteUri, null,
// null);
ContentResolver cr = getContentResolver();
int rows = cr.delete(Events.CONTENT_URI, Events._ID + "= ?",
new String[] { tempEventId + "" });
showMessageDialog("删除了一个event:" + rows);
Log.i("delete_event", "Rows deleted: " + rows);
}
});
}
public void showMessageDialog(String info) { // 弹出消息对话框,消息的内容是info
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setMessage(info);
builder.setTitle("information");
builder.setPositiveButton("确定", null);
AlertDialog alert = builder.create();
alert.show();
}
}
下面是AddNewActivity.java的代码:
package org.ls;
import java.util.Calendar;
import android.app.Activity;
import android.app.AlarmManager;
import android.app.AlertDialog;
import android.app.DatePickerDialog;
import android.app.PendingIntent;
import android.app.Service;
import android.app.TimePickerDialog;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.provider.CalendarContract.Events;
import android.provider.CalendarContract.Reminders;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnFocusChangeListener;
import android.widget.Button;
import android.widget.DatePicker;
import android.widget.EditText;
import android.widget.TimePicker;
public class AddNewEventActivity extends Activity implements OnClickListener,
OnFocusChangeListener {
public String eventName;
public String eventDescription;
public int[] eventBeginDate = new int[3];
public int[] eventBeginTime = new int[2];
public int[] eventEndDate = new int[3];
public int[] eventEndTime = new int[2];
public int reminderMinutes;
private EditText eventNameText;
private EditText eventDescriptionText;
private EditText eventBeginDateText;
private EditText eventBeginTimeText;
private EditText eventEndDateText;
private EditText eventEndTimeText;
private EditText reminderminutesText;
private Button okButton;
private Button goBackButton;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.add_event);
okButton = (Button) findViewById(R.id.ok);
okButton.setOnClickListener(this);
goBackButton = (Button) findViewById(R.id.goback);
goBackButton.setOnClickListener(this);
eventNameText = (EditText)findViewById(R.id.event_name);
eventDescriptionText = (EditText)findViewById(R.id.event_description);
eventBeginDateText = (EditText) findViewById(R.id.select_begin_date);
eventBeginDateText.setFocusable(true);
eventBeginDateText.setOnFocusChangeListener(this);
eventBeginTimeText = (EditText) findViewById(R.id.select_begin_time);
eventBeginTimeText.setFocusable(true);
eventBeginTimeText.setOnFocusChangeListener(this);
eventEndDateText = (EditText) findViewById(R.id.select_end_date);
eventEndDateText.setFocusable(true);
eventEndDateText.setOnFocusChangeListener(this);
eventEndTimeText = (EditText) findViewById(R.id.select_end_time);
eventEndTimeText.setFocusable(true);
eventEndTimeText.setOnFocusChangeListener(this);
reminderminutesText = (EditText) findViewById(R.id.reminder_minutes);
}
@Override
public void onClick(View v) {
if (v == okButton) {
eventName = eventNameText.getText().toString();
eventDescription = eventDescriptionText.getText().toString();
reminderMinutes = Integer.parseInt(reminderminutesText.getText()
.toString());
// 可在此处添加简单的判断用户输入新event的各项参数的合法性的判断,我假设用户输入的一定是合法的
addEvent(eventName, eventDescription, eventBeginDate, eventBeginTime, eventEndDate, eventEndTime, reminderMinutes); // 添加新event
}
else if (v == goBackButton) {
goBack();
}
}
@Override
public void onFocusChange(View v, boolean hasFocus) {
if (v == eventBeginDateText && hasFocus == true) {
Calendar c = Calendar.getInstance();
new DatePickerDialog(AddNewEventActivity.this,
new DatePickerDialog.OnDateSetListener() {
@Override
public void onDateSet(DatePicker view, int year,
int monthOfYear, int dayOfMonth) {
eventBeginDateText.setText("开始日期:" + year + "-"
+ (monthOfYear+1) + "-" + dayOfMonth);
eventBeginDate[0] = year;
eventBeginDate[1] = monthOfYear;
eventBeginDate[2] = dayOfMonth;
}
}, c.get(Calendar.YEAR), c.get(Calendar.MONTH),
c.get(Calendar.DAY_OF_MONTH)).show();
} else if (v == eventBeginTimeText && hasFocus == true) {
Calendar c = Calendar.getInstance();
new TimePickerDialog(AddNewEventActivity.this,
new TimePickerDialog.OnTimeSetListener() {
@Override
public void onTimeSet(TimePicker view, int hourOfDay,
int minute) {
eventBeginTimeText.setText("开始时间:" + hourOfDay
+ "时" + minute + "分");
eventBeginTime[0] = hourOfDay;
eventBeginTime[1] = minute;
}
}, c.get(Calendar.HOUR_OF_DAY), c.get(Calendar.MINUTE),
true).show();
} else if (v == eventEndDateText && hasFocus == true) {
Calendar c = Calendar.getInstance();
new DatePickerDialog(AddNewEventActivity.this,
new DatePickerDialog.OnDateSetListener() {
@Override
public void onDateSet(DatePicker view, int year,
int monthOfYear, int dayOfMonth) {
eventEndDateText.setText("结束日期:" + year + "-"
+ (monthOfYear+1) + "-" + dayOfMonth);
eventEndDate[0] = year;
eventEndDate[1] = monthOfYear;
eventEndDate[2] = dayOfMonth;
}
}, c.get(Calendar.YEAR), c.get(Calendar.MONTH),
c.get(Calendar.DAY_OF_MONTH)).show();
} else if (v == eventEndTimeText && hasFocus == true) {
Calendar c = Calendar.getInstance();
new TimePickerDialog(AddNewEventActivity.this,
new TimePickerDialog.OnTimeSetListener() {
@Override
public void onTimeSet(TimePicker view, int hourOfDay,
int minute) {
eventEndTimeText.setText("结束时间:" + hourOfDay + "时"
+ minute + "分");
eventEndTime[0] = hourOfDay;
eventEndTime[1] = minute;
}
}, c.get(Calendar.HOUR_OF_DAY), c.get(Calendar.MINUTE),
true).show();
}
}
private void addEvent(String eventName, String eventDescription,
int eventBeginDate[], int eventBeginTime[], int eventEndDate[],
int eventEndTime[], int reminderMinutus) {
long calId = 1;
long startMillis = 0;
long endMillis = 0;
Calendar beginTime = Calendar.getInstance();
beginTime.set(eventBeginDate[0], eventBeginDate[1], eventBeginDate[2], eventBeginTime[0], eventBeginTime[1]); // 注意:月份系统会自动加1
startMillis = beginTime.getTimeInMillis();
Calendar endTime = Calendar.getInstance();
endTime.set(eventEndDate[0], eventEndDate[1], eventEndDate[2], eventEndTime[0], eventEndTime[1]);
endMillis = endTime.getTimeInMillis();
ContentResolver cr = getContentResolver(); // 添加新event,步骤是固定的
ContentValues values = new ContentValues();
values.put(Events.DTSTART, startMillis);
values.put(Events.DTEND, endMillis);
values.put(Events.TITLE, eventName);
values.put(Events.DESCRIPTION, eventDescription);
values.put(Events.CALENDAR_ID, calId);
values.put(Events.EVENT_TIMEZONE, "GMT+8");
Uri uri = cr.insert(Events.CONTENT_URI, values);
Long myEventsId = Long.parseLong(uri.getLastPathSegment()); // 获取刚才添加的event的Id
ContentResolver cr1 = getContentResolver(); // 为刚才新添加的event添加reminder
ContentValues values1 = new ContentValues();
values1.put(Reminders.MINUTES, reminderMinutus);
values1.put(Reminders.EVENT_ID, myEventsId);
values1.put(Reminders.METHOD, Reminders.METHOD_ALERT);
cr1.insert(Reminders.CONTENT_URI, values1); // 调用这个方法返回值是一个Uri
setAlarmDeal(startMillis); // 设置reminder开始的时候,启动另一个activity
showMessageDialog("插入成功!" + "\n" + uri.getLastPathSegment() + "\n"
+ uri.getAuthority());
}
private void setAlarmDeal(long time) { // 设置全局定时器
Intent intent = new Intent(this, AlarmActivity.class);
PendingIntent pi = PendingIntent.getActivity(this, 0, intent, 0);
AlarmManager aManager = (AlarmManager) getSystemService(Service.ALARM_SERVICE);
aManager.set(AlarmManager.RTC_WAKEUP, time, pi); // 当系统调用System.currentTimeMillis()方法返回值与time相同时启动pi对应的组件
}
public void showMessageDialog(String info) { // 弹出消息对话框,消息的内容是info,且点击此对话框的确定按钮后会返回
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setMessage(info);
builder.setTitle("information");
builder.setPositiveButton("确定", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
goBack();
}
});
AlertDialog alert = builder.create();
alert.show();
}
private void goBack() { // 返回
Intent itent = new Intent();
itent.setClass(AddNewEventActivity.this,
AndroidCalendarProviderTestActivity.class);
startActivity(itent);
AddNewEventActivity.this.finish();
}
}
下面是event发生时,同时启动的那个Activity的代码,当某个event发生,除了系统会有Notification的提醒,无论本Demo是否正在运行,手机都会启动这个Activity,将手机设置成为震动,代码如下:
package org.ls;
import android.app.Activity;
import android.content.Context;
import android.media.AudioManager;
import android.os.Bundle;
public class AlarmActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.alarm_activity);
setVibrate(); // 将手机情景模式设为震动
}
private void setVibrate() {
AudioManager audio = (AudioManager)getSystemService(Context.AUDIO_SERVICE);
audio.setRingerMode(AudioManager.RINGER_MODE_VIBRATE);
audio.setVibrateSetting(AudioManager.VIBRATE_TYPE_RINGER, AudioManager.VIBRATE_SETTING_ON);
audio.setVibrateSetting(AudioManager.VIBRATE_TYPE_NOTIFICATION, AudioManager.VIBRATE_SETTING_ON);
}
}
相关的xml文件:
alarm_activity.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/alarminfo"
android:id="@+id/displayinfo"
/>
</LinearLayout>
add_event.xml:
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical"
android:id="@+id/add_new_event">
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<EditText
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:hint="在此填写新event的名字"
android:id="@+id/event_name"
android:selectAllOnFocus="true"
/>
<EditText
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:hint="简要描述新event"
android:id="@+id/event_description"
android:selectAllOnFocus="true"
/>
<EditText
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:hint="点击选择开始日期"
android:id="@+id/select_begin_date"
android:selectAllOnFocus="true"
/>
<EditText
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:hint="点击选择开始时间"
android:id="@+id/select_begin_time"
android:selectAllOnFocus="true"
/>
<!--
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="选择新event的结束时间:"
/>
-->
<EditText
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:hint="点击选择结束日期"
android:id="@+id/select_end_date"
android:selectAllOnFocus="true"
/>
<EditText
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:hint="点击选择结束时间"
android:id="@+id/select_end_time"
android:selectAllOnFocus="true"
/>
<!--
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="为新event的添加reminder:"
/>
-->
<!--
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="新event提前多少分钟提醒您:"
/>
-->
<EditText
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:hint="在此填写新event提前多少分钟提醒您:"
android:selectAllOnFocus="true"
android:id="@+id/reminder_minutes"
/>
<Button
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="确定"
android:id="@+id/ok"
/>
<Button
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="返回"
android:id="@+id/goback"
/>
</LinearLayout>
</ScrollView>
main.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"
android:orientation="vertical" >
<Button
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="查询所有日历"
android:id="@+id/querycalendars"
/>
<Button
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="添加新日历项"
android:id="@+id/addevents"
/>
<Button
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="查询所有event"
android:id="@+id/queryevents"
/>
<EditText
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:hint="在这里输入要删除event的id"
android:id="@+id/geteventid"
/>
<Button
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="删除该event"
android:id="@+id/delevent"
/>
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/hello"
android:id="@+id/displayevents"
/>
</LinearLayout>
AndroidManifest.xml:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.ls"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk android:minSdkVersion="14" />
<application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name" >
<activity
android:label="@string/app_name"
android:name=".AndroidCalendarProviderTestActivity" >
<intent-filter >
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name="AddNewEventActivity"></activity>
<activity android:name="AlarmActivity"></activity>
</application>
<uses-permission android:name="android.permission.READ_CALENDAR"/>
<uses-permission android:name="android.permission.WRITE_CALENDAR" />
</manifest>
另外记住,AlarmActivity一定要在AndroidManifest.xml中注册,否则,到了event执行时,系统不但不会启动AlarmActivity,甚至会练各种Log也不会打印;此外还有,Java中的月份是从0开始的,这一点很容易被忽略,一旦忽略会带来很多匪夷所思的事情,也很让人苦恼。