NoteEditor深入分析
首先来弄清楚“日志编辑“的状态转换,通过上篇文章的 方法来做下面这样一个实验,首先进入“日志编辑“时会触发onCreate和onResume,然后用户通过Option Menu选择”Edit title”后,会触发onSaveInstanceState和onPause,最后,用户回到编辑界面,则再次触发onResume。
最终通过LogCat可以得到下图:

那么下面就按照上述顺序对此类进行剖析。首先是onCreate方法,一开始先获取导致进入“日志编辑”界面的intent,分析其操作类型可得知是“编辑日志”还是“新增日志”。
// Do some setup based on the action being performed.
final String action = intent.getAction();
若是“编辑日志”,则设置当前状态为“编辑”,并保存待编辑日志的URI.
mUri = intent.getData();
若是“新增日志”,则设置当前状态为“新增”,并通过content provider向数据库中新增一个“空白日志”,后者返回“空白日志”的URI.
mUri = getContentResolver().insert(intent.getData(), null);
然后不管是“编辑”或“新增”,都需要从数据库中读取日志信息(当然,若是“新增”,读出来的肯定是空数据)。
最后,类似于web应用中使用的Session,这里也将日志文本保存在InstanceState中,因此,若此activity的实例此前是处于stop状态,则我们可以从它那取出它原本的文本数据.
{
mOriginalContent = savedInstanceState.getString(ORIGINAL_CONTENT);
}
第二个来分析onResume函数,首先把游标置于第一行(也只有一行)
然后取出“正文”字段,这时有一个比较有趣的技巧,“设置文本”并不是调用 setText,而是调用的setTextKeepState,后者相对于前者有一个优点,就是当界面此前stop掉,现在重新resume回来,那么此 前光标所在位置仍然得以保存。而若使用setText,则光标会重置到行首。
mText.setTextKeepState(note);
最后,将当前编辑的正文保存到一个字符串变量中,用于当activity被暂停时使用。
{
mOriginalContent = note;
}
通过前面的图可以得知,activity被暂停时,首先调用的是onSaveInstanceState函数。
这里就仅仅将当前正编辑的正文保存到InstanceState中(类似于Session)。最后来看onPause函数,这里首先要考虑的是若activity正要关闭,并且编辑区没有正文,则将此日志删除。
{
setResult(RESULT_CANCELED);
deleteNote();
}
否则的话,就更新日志信息
if (!mNoteOnly)
{
values.put(Notes.MODIFIED_DATE, System.currentTimeMillis());
if (mState == STATE_INSERT)
{
String title = text.substring(0, Math.min(30, length));
if (length > 30)
{
int lastSpace = title.lastIndexOf(‘ ‘);
if (lastSpace > 0)
{
title = title.substring(0, lastSpace);
}
}
values.put(Notes.TITLE, title);
}
}
values.put(Notes.NOTE, text);
getContentResolver().update(mUri, values, null, null);
}
}
在生成Option Menu的函数onCreateOptionsMenu中,我们再一次看到下面这段熟悉的代码了:
intent.addCategory(Intent.CATEGORY_ALTERNATIVE);
menu.addIntentOptions(Menu.CATEGORY_ALTERNATIVE, 0, 0,
new ComponentName(this, NoteEditor.class), null, intent, 0, null);
这种生成动态菜单的机制在Android实例剖析笔记(二)这 篇文章中已经介绍过了,就不赘述了。最后,来看下放弃日志和删除日志的实现,由于还没有接触到底层的content provider,这里都是通过getContentResolver()提供的update,delete,insert来向底层的content provider发出请求,由后者完成实际的数据库操作。
{
if (mCursor != null)
{
if (mState == STATE_EDIT)
{
// Put the original note text back into the database
mCursor.close();
mCursor = null;
ContentValues values = new ContentValues();
values.put(Notes.NOTE, mOriginalContent);
getContentResolver().update(mUri, values, null, null);
}
else if (mState == STATE_INSERT)
{
// We inserted an empty note, make sure to delete it
deleteNote();
}
}
setResult(RESULT_CANCELED);
finish();
}
private final void deleteNote()
{
if (mCursor != null)
{
mCursor.close();
mCursor = null;
getContentResolver().delete(mUri, null, null);
mText.setText(“”);
}
}
剖析NotePadProvider
NotePadProvider就是所谓的content provider,它继承自android.content.ContentProvider,也是负责数据库层的核心类,主要提供五个功能:
1)查询数据
2)修改数据
3)添加数据
4)删除数据
5)返回数据类型
这五个功能分别对应下述五个可以重载的方法:
{
return 0;
}
public String getType(Uri uri)
{
return null;
}
public Uri insert(Uri uri, ContentValues values)
{
return null;
}
public boolean onCreate()
{
return false;
}
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder)
{
return null;
}
public int update(Uri uri, ContentValues values, String selection,
String[] selectionArgs)
{
return 0;
}
这些都要你自己实现,不同的实现就是对应不同的content-provider。但是activity使用content-provider不是直接创建一个对象,然后调用这些具体方法。
而是调用managedQuery,getContentResolver().delete,update等来实现,这些函数其实是先找到符合条 件的content-provider,然后再调用具体content-provider的函数来实现,那又是怎么找到content- provider,就是通过uri中的authority来找到content-provider,这些都是通过系统完成,应用程序不用操心,这样就达到 了有效地隔离应用和内容提供者的具体实现的目的。
有了以上初步知识后,我们来看NotePadProvider是如何为上层提供数据库层支持的。下面这三个字段指明了数据库名称,数据库版本,数据表名称。
private static final int DATABASE_VERSION = 2;
private static final String NOTES_TABLE_NAME = “notes”;
实际的数据库操作其实都是通过一个私有静态类DatabaseHelper实现的,其构造函数负责创建指定名称和版本的数据库,onCreate函数则创建指定名称和各个数据域的数据表(就是简单的建表SQL语句)。onUpgrade负责删除数据表,再重新建表。
{
DatabaseHelper(Context context)
{
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
@Override
public void onCreate(SQLiteDatabase db)
{
db.execSQL(“CREATE TABLE ” + NOTES_TABLE_NAME + ” (”
+ Notes._ID + ” INTEGER PRIMARY KEY,”
+ Notes.TITLE + ” TEXT,”
+ Notes.NOTE + ” TEXT,”
+ Notes.CREATED_DATE + ” INTEGER,”
+ Notes.MODIFIED_DATE + ” INTEGER”
+ “);”);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)
{
Log.w(TAG, “Upgrading database from version ” + oldVersion + ” to ”
+ newVersion + “, which will destroy all old data”);
db.execSQL(“DROP TABLE IF EXISTS notes”);
onCreate(db);
}
}
在Android实例剖析笔记(一)这篇文章中我们已经见识到了getType函数的用处了,也正是通过它的解析,才能区分开到底是对全部日志还是对某一条日志进行操作。
{
switch (sUriMatcher.match(uri))
{
case NOTES:
return Notes.CONTENT_TYPE;
case NOTE_ID:
return Notes.CONTENT_ITEM_TYPE;
default:
throw new IllegalArgumentException(“Unknown URI ” + uri);
}
}
上面的sUriMatcher.match是用来检测uri是否能够被处理,而sUriMatcher.match(uri)返回值其实是由下述语句决定的。
sUriMatcher.addURI(NotePad.AUTHORITY, “notes”, NOTES);
sUriMatcher.addURI(NotePad.AUTHORITY, “notes/#”, NOTE_ID);
sNotesProjectionMap这个私有字段是用来在上层应用使用的字段和底层数据库字段之间建立映射关系的,当然,这个程序里两处对应的字段都是一样(但并不需要一样)。
static
{
sNotesProjectionMap = new HashMap<String, String>();
sNotesProjectionMap.put(Notes._ID, Notes._ID);
sNotesProjectionMap.put(Notes.TITLE, Notes.TITLE);
sNotesProjectionMap.put(Notes.NOTE, Notes.NOTE);
sNotesProjectionMap.put(Notes.CREATED_DATE, Notes.CREATED_DATE);
sNotesProjectionMap.put(Notes.MODIFIED_DATE, Notes.MODIFIED_DATE);
}
数据库的增,删,改,查操作基本都一样,具体可以参考官方文档,这里就仅仅以删除为例进行说明。一般可以分为三步来完成,首先打开数据库
然后根据URI指向的是日志列表还是某一篇日志,到数据库中执行删除动作
case NOTES:
count = db.delete(NOTES_TABLE_NAME, where, whereArgs);
break;
case NOTE_ID:
String noteId = uri.getPathSegments().get(1);
count = db.delete(NOTES_TABLE_NAME, Notes._ID + “=” + noteId
+ (!TextUtils.isEmpty(where) ? ” AND (” + where + ‘)’ : “”), whereArgs);
break;
}
最后,一定记得通知上层:其传递下来的URI在底层数据库中已经发生了变化。
对NotePad的改进
首先我想指出NotePad的一个bug,其实这个小bug在2月份就有人向官方报告了,参见http://code.google.com/p/android/issues/detail?id=1909。NoteEditor类中的变量mNoteOnly根本就是没有用处的,因为它始终都是false,没有任何变化,所以可以删除掉。第二点是在NoteEditor类中,有下面这样的语句:
setResult(RESULT_CANCELED);
可到底想展示什么技术呢?实际上并没有完整展现出来,这里我对其进行修改后来指明。参见http://code.google.com/p/android/issues/detail?id=1671)。首先在NotesList类中增加一个变量
然后修改onOptionsItemSelected函数如下:
public boolean onOptionsItemSelected(MenuItem item)
{
switch (item.getItemId())
{
case MENU_ITEM_INSERT:
this.startActivityForResult(new Intent(Intent.ACTION_INSERT, getIntent().getData()), REQUEST_INSERT);
return true;
}
return super.onOptionsItemSelected(item);
}
最后重载onActivityResult函数来处理接收到的activity result。
{
if(requestCode == REQUEST_INSERT)
{
if(resultCode==RESULT_OK)
{
Log.d(TAG, “OK!!!”);
}
else if(resultCode==RESULT_CANCELED)
{
Log.d(TAG, “CANCELED!!!”);
}
}
}
试试,当你在NoteEditor中保存或放弃日志时,观察LogCat,你可以看到下面这样的画面:












Launchpad
Mission Control
Dock

Finder







废纸篓

预览
恢复文件 


强大的搜索“善用搜索”

懒得打字?动动嘴就行~
音量/键盘灯/屏幕亮度 调整
快捷键
系统编好设置


输入用户设定的密码解锁 权限 ,如果没有密码 直接空着不填点‘确定’


‘安全性与隐私’这是决定你mac安全性的一扇大门,在安全与隐私中的‘通用’里用户可以设定/修改 用户登陆密码
另一个十分常用的功能就是—让Mac成为WIFI热点
蓝牙文件交换




字典



Safari






下载 app应用程序
App Store里下载,里面的软件都是需要购买的(俗称 正版),当然比如QQ、PPS、迅雷等常用软件都是免费的,




iTunes


在Dock中显示iTunes播放专辑封面
Remote
Quick Time






iCloud 
邮件:此功能需要iCloud邮箱开启
通讯录:可以与iOS上的通讯录同步,或在iCloud上备份
日历与提醒事项:在日历中添加的任务可自动同步到iOS设备上,提醒事项也是如此
Safari:同步Safari中的标签页 以及在iOS上浏览过的网页
照片流:简单来说就是在iPhone上照张照片,立即会在Mac的iPhoto中出现~~借给朋友时 应该把此项功能关闭
文稿与数据:iWork办公套件,在iPhone上修改文稿的时候 会自动同步到Mac上,还有游戏存档~~
回到我的Mac:简单意思就是远程访问,复杂的来说 我也没有用过……
查找我的Mac:与查找我的iPhone一样,丢了 被偷了可以找回来(除非那贼太笨或压根就是个苹果白痴…个人建议在‘关闭’这个功能的时候也要输入账户密码)


日历



Time Machine
外置硬盘:品牌无所谓,后期需要把硬盘格式转成“mac os x扩展日志式 ”
Time Capsule:苹果自家的无线硬盘/路由器,目前有2TB和3TB版本,价格略高
硬盘分区:这个方法有种脱裤子放屁的味道~给喜欢折腾的用户吧

分享功能

例如,在‘预览’程序中 要分享图片至新浪微博上,可以点击 分享 按钮,并分享至‘新浪微博’即可

也可以通过‘信息’的方式发到好友的手机上(苹果设备),一切都是免费的


通知中心
”

无:即不提醒通知
横幅:提醒用户,变回向右滑动消失
提示:可以从中操作通知选项
系统信息




关于电池—锂聚合物电池



光盘刻录
数据光盘:顾名思义就是可以刻录文件,与U盘的方式差不多,受光盘容量限制,使用Finder刻录
音乐光盘:就是CD,可以在播放机中播放的光盘,受刻录时间限制,使用iTunes刻录
视频光盘:就是DVD,可在播放机中播放,受刻录时间限制,使用iDVD刻录
镜像光盘:系统光盘/安装光盘,ISO镜像文件,受光盘容量限制,使用‘磁盘工具’刻录
数据光盘
音乐光盘


视频光盘

镜像光盘

磁盘工具

硬盘分区


钥匙串访问







Parallels Desktop目前在Apple Store上正版售价为¥298,使用PD 需要激活码,目前网上售卖的激活码也在¥188左右
VMware Fusion目前在Apple Store上正版售价为¥319,VM相比PD 要容易破解













