分类目录归档:程序生活

Program Life – Web

Cocoa关于对象及其保留计数器的三条规则

1.如果使用new,alloc,copy操作获得一个对象,则该对象的保留计数器的值为1

2.如果通过其他方法获得一个对象,则认为该对象的保留计数器的值为1,并且已经自动被设置为自动释放,不需要手动release

3.如果保留了某对象,则必须保持retain方法和release方法的使用次数相等。

@autoreleasepool在ARC和MRC下的区别

MRC这个词应该是我编的,ARC,Automatic Reference Counting,手工引用计数就应该是:Manual Reference Counting,那就应该是MRC喽,不过没有见人这样用过。

ARC引入了新的语句管理自动释放池语法:

@autoreleasepool {
// Code, such as a loop that creates a large number of temporary objects.
}

测试了一下,在ARC情况下和MRC情况下对象的释放有不同。

比如,有这样的代码:

#import <UIKit/UIKit.h>
@interface ViewController : UIViewController{
NSString *myString;
}
@end

声明了一个myString成员变量。

然后编写下面的代码:

– (void)viewDidLoad

{
[super viewDidLoad];
[self performSelector:@selector(test)];
NSLog(@”myString: %@”,myString);
}

-(void)test{
@autoreleasepool {
NSString *string= [[NSString alloc] initWithFormat:@”First Name: %@”, @”tom”];
NSLog(@”string: %@”,string);
myString=string;
}
}

该项目在ARC情况下日志结果:

而在MRC情况下日志结果:

哦,这里说一下怎么切换Xcode项目从ARC到MRC,先选中项目根结点:

然后,在右侧:

因为条目很多,可以在搜索框中输入arc,这样就可以看到下面标识的条目了。

开始我以为@autoreleasepool等同于之前的NSAutoreleasePool类的api,只是起到语法糖的作用,节省了编写代码时间。上面的例子说明,只见还是有区别的。

为此,我查询了clang的文档,找到目前能说服我的解释。

这里简要介绍一下llvm和clang:

  • llvm,low level virtual machine,是底层编译库
  • clang,使用llvm底层库,编译器

可以参考这篇中文文章了解:LLVM与Clang介绍

下面解释一下在ARC和MRC中的不同处理:

  • ARC,将释放所有在@autorelesepool块中的对象,这就是为什么本文示例使用了默认修饰符(strong),相当于做了retain,也一样被释放的原因
  • MRC,在这种情况下@autorelesepool块等同于调用NSAutoreleasePool类的api

具体请参见:Clang的Automatic Reference Counting文档@autoreleasepool节

移动应用设计入门

随着智能手机、平板电脑的快速普及,越来越多的企业意识到建立自己的APP应用和移动网站,也有越来越多设计师开始转战移动平台。本篇主要介绍移动平台的一些入门知识和各平台的设计要求。

 

一、移动产品的实现方式

移动产品的实现方式主要有三种:①Native App;② Web App;③ Hybrid App

 

① Native App指的是本地化应用,就是我们从应用商店下载安装的独立应用,类似于PC平台上的客户端,Native App的主要优势有:
最佳的用户体验,最华丽的交互,操作流畅
可节省带宽成本
能够轻松调用图片相机,各类传感器,麦克风,电话….
可以使用PUSH推送
Native App有着非常明显的优势,也是用户接受程度最高的呈现方式,但开发原生应用的成本比较高,而且维护更新滞后,访问路径封闭;如果不是用户常用的应用,很难长时间存活在用户手机里。

② Web App 通常是指触屏站,就是我们通过手机浏览器访问的Html5网站,Html5支持一些新标签和脚本,可以做出类原生应用的效果和动画,Web App的主要优势有:

实时更新
不需要针对各平台开发不同的版本,开发成本低
输入网址即可访问,不需要下载安装更新。

但Web App的缺点也比较明显:
部分浏览器无法调用相册, 硬件资源和传感器
无法使用推送功能
性能较差
浏览器适配容易出问题
缓存小,以iPhone为标准,所有的图片和脚本都要小于25KB
目前只兼容webkit内核的手机浏览器, WP,Symbian等平台的浏览器无法正常访问。Web App主要服务于产品的轻度用户,或作为Native App宣传下载的中转站。

 

③ Hybrid App 是指混合模式应用,同时使用网页语言与程序语言编写,包含原生视图和Web视图两种方式,使用方式和Native App一致,而又继承了Web App实时更新开发成本低等优点。

Hybrid App通常分为三种类型:多View混合型,单View混合型,Web主体型。

 

我们先来认识一下什么是多View混合型:

天猫App的产品详情页使用的就是多View混合型,基本信息用的Native view, 而数据格式较为混乱的商品详情页就直接用的Web view视图。这种移动应用主体通常是Native App,Web技术只是起到补充作用。
再来了解一下什么是单View混合型:

即在同一个View内,同时包括Native View和Web View。互相之间是覆盖(层叠)的关系。这种方式的体验要优于多View混合型,一般用于Native View中部分数据接口不方便实现的页面,普通用户基本上看不出与原生视图的区别。
而Web主体型可以理解为一个Native的外壳内嵌入了纯Html网页,这种方式的app想通过商店审核非常困难,而且用户体验也非常的糟糕,目前的技术不是太成熟,缺钱缺人缺时间的情况下可以考虑使用这种方式。

二、移动产品的需求特性

一般企业级的App都是根据Web主站的需求做移动化定制,规划需求时主要考虑用户的使用环境。
根据移动平台的用户使用环境,需求主要考虑:
App作为主站的快捷工具,只需实现核心需求,考虑添加场景需求,而不是所有需求的复制;
信息处理方式多样化:视觉、听觉和运动触觉;手机是各个终端的组合体:电话、互联网、电脑、信用卡、电视、媒体播放器、收音机、录音笔、摄像头…需要充分利用;
要利用好手机随身携带的优势,比如推送通知,LBS定位;
手机的输入效率有限,需要避免复杂的编辑操作和危险操作需求;
网络环境不稳定,避免单页面呈现内容过多;
手机平台无资源管理器,屏幕小,硬件能力有限,只适用于微任务,代替不了PC端;
移动平台本身具有短路径的缺陷(无法在众多APP间自由跳转)。

 

三、移动产品的交互特性

① 操作行为的革新
PC端的输入设备是键盘+鼠标,而移动端主要靠手势

② 传感器的利用
移动设备除了各种手势操作,各种传感器也应该是交互设计可以利用的利器,下面简单介绍几种常用的传感器类型:
重力感应器,最常应用与横竖屏切换,平衡球游戏等.

速度感应器,应用最成功的是微信的摇一摇,然后市面出现了大批的摇晃类操作的应用都是利用的加速度传感器;

方位感应器,主要应用于指南针,地图等。

光线感应器主要应用与屏幕亮度自动调节,自动切换白天夜间模式;
图中的新闻客户端之所以没有设计成自动切换,可能是考虑到绝大都数用户都不习惯夜间模式,或者不喜欢应用的强制切换。
除了上述的几个常用传感器,还有方向传感器(赛车类游戏)、距离传感器(接电话时屏幕自动熄灭)、压力传感器(部分机型有,可以检测楼层、海拔)等。

③小屏幕,单窗口

在设计Web网页时,我们会有全局导航,页签,栅格,面包屑等方法处理复杂信息的呈现,而在移动端,会有小屏幕单窗口的特性,而且手指的精准度远差与鼠标,所以移动应用的设计相比网页有更多的要求:

最小点击热区不小于44*44
不超过4个次级关系的页面层级
明确的操作反馈和提示
使用不同平台的标准控件
页面跳转的意向化过渡
具备趣味性

 

④平台操作的差异化

虽然很多Android的应用和iOS应用无差异,但各平台的操作习惯和实体按键并不相同,iOS应用的所有操作都是通过手势完成,而Android和WindowsPhone却有使用率非常高的实体键,而且高级手势在android和WP端并不能实现。

 

那么各平台的设计规范又有哪些区别和优势,这个..还是等下一个章节再讲吧。

HTML与原生应用合体或成开发者第三选择

今天似乎人人都已陷入HTML5与原生手机应用非此即彼的论战中,并极力鼓吹其中一方的发展潜力。虽然苹果和谷歌已经各自偏向一方,但有些公司却已经做出第三种选择——将这两者结为一体的“混合应用”。

混合应用开发过程采纳了原生应用功能,同时也融合了更具前瞻性的HTML5技术。

混合应用是一种需要下载,但有部分或者所有用户面植入了浏览器元素的应用程序。对用户来说,混合应用与原生应用并无二致——它们都需要通过应用商店 渠道下载,都可以保存的在手机,运行方式与原生应用并无差别。但对开发者来说,这其中的差异却不容忽视,因为这意味着他们无需针对各个手机操作系统重新编 写应用,而是可以选择用HTML、CSS和JavaScript编写其中一部分代码,并在多个平台上运行应用程序。

hybrid apps(from venturebeat)

hybrid apps(from venturebeat)

“混合”这一词实际上道出了这种应用的多种可能性。例如美国银行、Facebook和Yelp的iPhone应用程序,就有部分内容是直接将公司网 站的部分页面转移过来。而像《Tower Madness》游戏等其他应用,则嵌入了一些以HTML5编写的页面。但还有一些应用,例如Harmounius(一款绘图画板)或Logitec’s Squeezebox Controller的UI则完全采用HTML技术。

从商业角度来看,尽早采用HTML5技术是最明智的做法。有些行业巨头已经将HTML当作唯一可行的跨平台开发技术。传说中的Facebook项目 “Project Spartan”(游戏邦注:有人称它是一个基于HTML5的网页应用商店),以及微软宣布开发者将可使用HTML5和JavaScript为 Windows 8编写应用程序等消息,更增加了HTML5获胜的筹码。甚至有人认为现在的开发商面临的并非“是否”,而是“何时”采纳HTML技术的问题。

有些公司并不急于追赶HTML5潮流的一个主要原因是,他们对HTML5应用无法接入移动设备原始相关功能的这一情况颇有顾虑。纯粹的移动网页应用 (游戏邦注:这里指那些运行于浏览器的应用,而非混合应用)目前还不能访问摄像头、麦克风、通讯录等手机功能。虽然W3C已经启动支持网页应用访问这类移 动设备功能的项目,但移动浏览器现在还不具备这些功能已是不争的事实,而这一点却是许多创新型手机应用的必备条件。

但在混合应用领域,PhoneGap库等开源框架却可以让JavaScript代码访问手机的罗盘、照相等功能成为可能,甚至可以搜索或创建联系人列表、约会安排等其他多种网页应用无法接入的手机功能。

接入移动设备功能并非混合应用与移动网页应用的唯一区别。这两者之间的另一大差异表现在,混合应用多数需通过应用商店渠道下载,而不是在浏览器运行,用户需要下载安装才行。

另外,混合应用的HTML页面可通过网络服务器传送,但这一点并非必备条件。如果要提高运行性能,混合应用还可以植入一个包含其所需的网页资源(例如HTML、JavaScript、CSS和图像)的副本,以便用户快速访问内容,而不必等待网络服务器将内容传送过来。

除此之外,混合应用的另一个特点在于,它与网页应用又有共通之处。混合应用并不像原生应用那样,直接使用手机操作系统所支持的图像API和UI,其 多数页面采用的是浏览器的渲染引擎,这与网页应用一致。这就意味着从当前来看,只有原始编写页面才可以实现像游戏一般高质量的图像效果,不过这一点与商务 型应用的关系倒不大。现在我们还无法在手机平台看到以HTML编写的《真人快打3》。

庆幸的是,目前主流智能手机和平板电脑都有强大的HTML渲染引擎,它们已经可以支持多数即将到来的HTML5和CSS3技术标准。

Sencha Touch、jQuery Mobile和dojox.mobile等JavaScript工具包完全可与混合应用开发模型兼容,使这类应用从外观和感觉与原生应用不分彼此。由此可 见,在现代手机硬件设备上运行的混合应用,可以使用HTML5、CSS3和JavaScript实现高度的交互性,创建出色的用户界面。

如果你无法使用HTML满足特定图像或交互性需求,不妨选择结合网页和原生应用功能的混合应用。混合应用中的有趣典型之一就是韩国信用卡Lotte,其应用程序有100个页面以HTML编写而成,仅用一小部分采用了增强现实感功能的原始页面。

还有不少公司在打算转向HTML5网页应用的同时,也正在开发混合应用。从战略性角度来看,开发公司需认真考虑是否尽早采用HTML开发手机应用。 混合应用模式虽然并不适用于所有的应用开发,但至少为大量的可下载应用提供了一个划算的解决方案,也可以算是开发者逐渐向HTML5领域演变的一种铺垫。 (本文为游戏邦/gamerboom.com编译,如需转载请联系:游戏邦)

Android实例剖析笔记(四)

 NoteEditor深入分析

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

最终通过LogCat可以得到下图:

那么下面就按照上述顺序对此类进行剖析。首先是onCreate方法,一开始先获取导致进入“日志编辑”界面的intent,分析其操作类型可得知是“编辑日志”还是“新增日志”。

       final Intent intent = getIntent();
// Do some setup based on the action being performed.
final String action = intent.getAction();

若是“编辑日志”,则设置当前状态为“编辑”,并保存待编辑日志的URI.

             mState = STATE_EDIT;
mUri = intent.getData();

若是“新增日志”,则设置当前状态为“新增”,并通过content provider向数据库中新增一个“空白日志”,后者返回“空白日志”的URI.

          mState = STATE_INSERT;
mUri = getContentResolver().insert(intent.getData(), null);

然后不管是“编辑”或“新增”,都需要从数据库中读取日志信息(当然,若是“新增”,读出来的肯定是空数据)。

mCursor = managedQuery(mUri, PROJECTION, null, null, null);

最后,类似于web应用中使用的Session,这里也将日志文本保存在InstanceState中,因此,若此activity的实例此前是处于stop状态,则我们可以从它那取出它原本的文本数据.

        if (savedInstanceState != null)
{
mOriginalContent = savedInstanceState.getString(ORIGINAL_CONTENT);
}

第二个来分析onResume函数,首先把游标置于第一行(也只有一行)

            mCursor.moveToFirst();

然后取出“正文”字段,这时有一个比较有趣的技巧,“设置文本”并不是调用 setText,而是调用的setTextKeepState,后者相对于前者有一个优点,就是当界面此前stop掉,现在重新resume回来,那么此 前光标所在位置仍然得以保存。而若使用setText,则光标会重置到行首。

             String note = mCursor.getString(COLUMN_INDEX_NOTE);
mText.setTextKeepState(note);

最后,将当前编辑的正文保存到一个字符串变量中,用于当activity被暂停时使用。

            if (mOriginalContent == null)
{
mOriginalContent = note;
}

通过前面的图可以得知,activity被暂停时,首先调用的是onSaveInstanceState函数。

outState.putString(ORIGINAL_CONTENT, mOriginalContent);

这里就仅仅将当前正编辑的正文保存到InstanceState中(类似于Session)。最后来看onPause函数,这里首先要考虑的是若activity正要关闭,并且编辑区没有正文,则将此日志删除。

            if (isFinishing() && (length == 0) && !mNoteOnly)
{
setResult(RESULT_CANCELED);
deleteNote();
}

否则的话,就更新日志信息

                ContentValues values = new ContentValues();
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 intent = new Intent(null, getIntent().getData());
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发出请求,由后者完成实际的数据库操作。

    private final void cancelNote()
{
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)返回数据类型

这五个功能分别对应下述五个可以重载的方法:

public int delete(Uri uri, String selection, String[] selectionArgs)
{
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 String DATABASE_NAME = “note_pad.db”;
private static final int DATABASE_VERSION = 2;
private static final String NOTES_TABLE_NAME = “notes”;

实际的数据库操作其实都是通过一个私有静态类DatabaseHelper实现的,其构造函数负责创建指定名称和版本的数据库,onCreate函数则创建指定名称和各个数据域的数据表(就是简单的建表SQL语句)。onUpgrade负责删除数据表,再重新建表。

private static class DatabaseHelper extends SQLiteOpenHelper
{
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函数的用处了,也正是通过它的解析,才能区分开到底是对全部日志还是对某一条日志进行操作。

public String getType(Uri uri)
{
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 = new UriMatcher(UriMatcher.NO_MATCH);
sUriMatcher.addURI(NotePad.AUTHORITY, “notes”, NOTES);
sUriMatcher.addURI(NotePad.AUTHORITY, “notes/#”, NOTE_ID);

sNotesProjectionMap这个私有字段是用来在上层应用使用的字段和底层数据库字段之间建立映射关系的,当然,这个程序里两处对应的字段都是一样(但并不需要一样)。

private static HashMap<String, String> 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);
}

数据库的增,删,改,查操作基本都一样,具体可以参考官方文档,这里就仅仅以删除为例进行说明。一般可以分为三步来完成,首先打开数据库

        SQLiteDatabase db = mOpenHelper.getWritableDatabase();

然后根据URI指向的是日志列表还是某一篇日志,到数据库中执行删除动作

    switch (sUriMatcher.match(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在底层数据库中已经发生了变化。

        getContext().getContentResolver().notifyChange(uri, null);

  对NotePad的改进

首先我想指出NotePad的一个bug,其实这个小bug在2月份就有人向官方报告了,参见http://code.google.com/p/android/issues/detail?id=1909。NoteEditor类中的变量mNoteOnly根本就是没有用处的,因为它始终都是false,没有任何变化,所以可以删除掉。第二点是在NoteEditor类中,有下面这样的语句:

setResult(RESULT_OK, (new Intent()).setAction(mUri.toString()));
setResult(RESULT_CANCELED);

可到底想展示什么技术呢?实际上并没有完整展现出来,这里我对其进行修改后来指明。参见http://code.google.com/p/android/issues/detail?id=1671)。首先在NotesList类中增加一个变量

private static final int REQUEST_INSERT = 100;//请求插入标识符

然后修改onOptionsItemSelected函数如下:

   @Override
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。

    protected void onActivityResult(int requestCode, int resultCode, Intent data)
{
if(requestCode == REQUEST_INSERT)
{
if(resultCode==RESULT_OK)
{
Log.d(TAG, “OK!!!”);
}
else if(resultCode==RESULT_CANCELED)
{
Log.d(TAG, “CANCELED!!!”);
}
}
}

试试,当你在NoteEditor中保存或放弃日志时,观察LogCat,你可以看到下面这样的画面:

Android实例剖析笔记(三)

Activity的生命周期

Activity类中有许多onXXX形式的函数可以重载,比如 onCreate,onStart,onStop,onPause,那么它们的调用顺序到底是如何的呢?下面就通过一个实验来进行分析。在做这个实验之 前,我们先得知道如何在Android中进行Log输出的。我们要使用的是android.util.log类,这个类相当的简单易用,因为它提供的全是 一些静态方法:

Log.v(String tag, String msg);        //VERBOSE
Log.d(String tag, String msg);       //DEBUG
Log.i(String tag, String msg);        //INFO
Log.w(String tag, String msg);     //WARN
Log.e(String tag, String msg);      //ERROR

前面的tag是由我们定义的一个标识,一般可以用“类名_方法名“来定义。要在Eclipse中查看输出的log信息,需要打开Logcat(WindowàShow ViewàotheràAndroidàLogCat即可打开)

  实验一

我们要做的实验非常简单,就是有两个Activity(我这里分别叫做frmLogin和hello2),t它们各自有一个button,可以 从第一个跳到第二个,也可以从第二个跳回到第一个。配置文件AndroidManifest.xml非常简单,第二个activity并没有多余的信息需 要指定。

    <application android:icon=”@drawable/icon” android:label=”@string/app_name”>
<activity android:name=”.frmLogin”
android:label=”@string/app_name”>
<intent-filter>
<action android:name=”android.intent.action.MAIN” />
<category android:name=”android.intent.category.LAUNCHER” />
</intent-filter>
</activity>
<activity android:name=”hello2″ android:label=”@string/app_name”>
</activity>
</application>

第一个activity的代码如下:

public class frmLogin extends Activity
{
private final static String TAG = “FrmLogin”;

/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
Log.v(TAG,”onCreate”);
setContentView(R.layout.main);
this.setViewOneCommand();
}

public void setViewOneCommand()
{
Button btn = (Button)findViewById(R.id.btnGo);
btn.setOnClickListener(new View.OnClickListener()
{
public void onClick(View v)
{
Intent intent = new Intent();
intent.setClass(frmLogin.this, hello2.class);
startActivity(intent);
finish();
}
});
Button btnExit=(Button)findViewById(R.id.btnExit);
btnExit.setOnClickListener(new View.OnClickListener()
{
public void onClick(View v)
{
frmLogin.this.finish();
}
});
}

@Override
protected void onDestroy()
{
super.onDestroy();
Log.v(TAG,”onDestroy”);
}

@Override
protected void onPause()
{
super.onPause();
Log.v(TAG,”onPause”);

}

@Override
protected void onRestart()
{
super.onRestart();
Log.v(TAG,”onRestart”);
}

@Override
protected void onResume()
{
super.onResume();
Log.v(TAG,”onResume”);
}

@Override
protected void onStart()
{
super.onStart();
Log.v(TAG,”onStart”);
}

@Override
protected void onStop()
{
super.onStop();
Log.v(TAG,”onStop”);
}
}

我在每个onXXX方法中都加入了log方法,值得注意的一点是按钮单击事件处理函数中,在最后我调用了finish();待会我会将此行注释 掉进行对比实验。第二个activity的代码和第一个完全一样,只是将setClass的两个参数反一下,这样就可以简单地实现在两个Activity 界面中来回切换的功能了。下面开始实验,第一个实验室从第一个activity跳到第二个activity(此时第一个关闭),然后从第二个跳回第一个 (此时第二个关闭)。运行后观察LogCat,得到如下画面:

 

然后来进行第二个实验,对代码进行调整,我们把第一个activity中的finish()注释掉,从第一个activity跳到第二个(此时第一个没有关闭),然后第二个直接关闭(则第一个会重新来到前端),结果如图所示,可以看出调用了FrmLogin的onRestart而不是onStart,因为第一个activity只是stop,而并没有被destory掉。

 

前面两个实验都很好理解,可第三个实验就让我不明白了,过程如下:从第一个activity跳到第二个activity(此时第一个不关闭), 然后第二个跳回第一个(此时第二个也不关闭),然后第一个再跳回第二个(此时第一个不关闭),照上面来推断,应该还是会调用onRestart才对,可实 际上它调用的却是onStart,why???
这里先不讨论例子了,来看看官方文档对Activity生命周期的介绍。

1. Android用Activity Stack来管理多个Activity,所以呢,同一时刻只会有最顶上的那个Activity是处于active或者running状态。其它的Activity都被压在下面了。

2. 如果非活动的Activity仍是可见的(即如果上面压着的是一个非全屏的Activity或透明的Activity),它是处于paused状态的。在 系统内存不足的情况下,paused状态的Activity是有可被系统杀掉的。只是不明白,如果它被干掉了,界面上的显示又会变成什么模样?看来下回有 必要研究一下这种情况了。

3. 几个事件的配对可以比较清楚地理解它们的关系。Create与Destroy配成一对,叫entrie lifetime,在创建时分配资源,则在销毁时释放资源;往上一点还有Start与Stop一对,叫visible lifetime,表达的是可见与非可见这么一个过程;最顶上的就是Resume和Pause这一对了,叫foreground lifetime,表达的了是否处于激活状态的过程。

4. 因此,我们实现的Activity派生类,要重载两个重要的方法:onCreate()进行初始化操作,onPause()保存当前操作的结果。除了Activity Lifecycle以外,Android还有一个Process Lifecycle的说明:

在内存不足的时候,Android是会主动清理门户的,那它又是如何判断哪个process是可以清掉的呢?文档中也提到了它的重要性排序:

1. 最容易被清掉的是empty process,空进程是指那些没有Activity与之绑定,也没有任何应用程序组件(如Services或者IntentReceiver)与之绑定 的进程,也就是说在这个process中没有任何activity或者service之类的东西,它们仅仅是作为一个cache,在启动新的 Activity时可以提高速度。它们是会被优先清掉的。因此建议,我们的后台操作,最好是作成Service的形式,也就是说应该在Activity中 启动一个Service去执行这些操作。

2. 接下来就是background activity了,也就是被stop掉了那些activity所处的process,那些不可见的Activity被清掉的确是安全的,系统维持着一个 LRU列表,多个处于background的activity都在这里面,系统可以根据LRU列表判断哪些activity是可以被清掉的,以及其中哪一 个应该是最先被清掉。不过,文档中提到在这个已被清掉的Activity又被重新创建的时候,它的onCreate会被调用,参数就是onFreeze时 的那个Bundle。不过这里有一点不明白的是,难道这个Activity被killed时,Android会帮它保留着这个Bundle吗?

3. 然后就轮到service process了,这是一个与Service绑定的进程,由startService方法启动。虽然它们不为用户所见,但一般是在处理一些长时间的操作(例如MP3的播放),系统会保护它,除非真的没有内存可用了。

4. 接着又轮到那些visible activity了,或者说visible process。前面也谈到这个情况,被Paused的Activity也是有可能会被系统清掉,不过相对来说,它已经是处于一个比较安全的位置了。

5. 最安全应该就是那个foreground activity了,不到迫不得已它是不会被清掉的。这种process不仅包括resume之后的activity,也包括那些 onReceiveIntent之后的IntentReceiver实例。在Android Application的生命周期的讨论中,文档也提到了一些需要注意的事项:因为Android应用程序的生存期并不是由应用本身直接控制的,而是由 Android系统平台进行管理的,所以,对于我们开发者而言,需要了解不同的组件Activity、Service和IntentReceiver的生 命,切记的是:如果组件的选择不当,很有可能系统会杀掉一个正在进行重要工作的进程。

  自定义控件

这里主要介绍下“编辑日志”中使用的一个自定义EditText控件,它的效果如下图:

主要功能就是在文本语句之间绘制分割线。

  public static class LinedEditText extends EditText
{
private Rect mRect;
private Paint mPaint;
// we need this constructor for LayoutInflater
public LinedEditText(Context context, AttributeSet attrs)
{
super(context, attrs);
mRect = new Rect();
mPaint = new Paint();
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setColor(0x800000FF);
}
@Override
protected void onDraw(Canvas canvas)
{
int count = getLineCount();
Rect r = mRect;
Paint paint = mPaint;
for (int i = 0; i < count; i++)
{
int baseline = getLineBounds(i, r);
canvas.drawLine(r.left, baseline + 1, r.right, baseline + 1, paint);
}
super.onDraw(canvas);
}
}

主要工作就是重载onDraw方法,利用从TextView继承下来的getLineCount函数获取文本所占的行数,以及 getLineBounds来获取特定行的基准高度值,而且这个函数第二个参数会返回此行的“外包装”值。再利用这些值绘制这一行的线条。为了让界面的 View使用自定义的EditText类,必须在配置文件中进行设置

<view xmlns:android=”http://schemas.android.com/apk/res/android”
class=”com.example.android.notepad.NoteEditor$LinedEditText”
android:id=”@+id/note”
android:layout_width=”fill_parent”
android:layout_height=”fill_parent”
android:background=”@android:color/transparent”
android:padding=”5dip”
android:scrollbars=”vertical”
android:fadingEdge=”vertical”
android:gravity=”top”
android:textSize=”22sp”
android:capitalize=”sentences”
/>

这里class=”com.example.android.notepad.NoteEditor$LinedEditText”就指明了应当使用自定义的LinedEditText类。

0
0

Android实例剖析笔记(二)

简介

android提供了三种菜单类型,分别为options menu,context menu,sub menu。

options menu就是通过按home键来显示,context menu需要在view上按上2s后显示。这两种menu都有可以加入子菜单,子菜单不能种不能嵌套子菜单。options menu最多只能在屏幕最下面显示6个菜单选项,称为iconmenu,icon menu不能有checkable选项。多于6的菜单项会以more icon menu来调出,称为expanded menu。options menu通过activity的onCreateOptionsMenu来生成,这个函数只会在menu第一次生成时调用。任何想改变options menu的想法只能在onPrepareOptionsMenu来实现,这个函数会在menu显示前调用。onOptionsItemSelected 用来处理选中的菜单项。

context menu是跟某个具体的view绑定在一起,在activity种用registerForContextMenu来为某个view注册context menu。context menu在显示前都会调用onCreateContextMenu来生成menu。onContextItemSelected用来处理选中的菜单项。

android还提供了对菜单项进行分组的功能,可以把相似功能的菜单项分成同一个组,这样就可以通过调用setGroupCheckable,setGroupEnabled,setGroupVisible来设置菜单属性,而无须单独设置。

  Options Menu

Notepad中使用了options menu和context menu两种菜单。首先来看生成options menu的onCreateOptionsMenu函数。

  menu.add(0, MENU_ITEM_INSERT, 0, R.string.menu_insert)
.setShortcut(‘3’, ‘a’)
.setIcon(android.R.drawable.ic_menu_add);

这是一个标准的插入一个菜单项的方法,菜单项的id为MENU_ITEM_INSERT。有意思的是下面这几句代码:

 Intent intent = new Intent(null, getIntent().getData());
intent.addCategory(Intent.CATEGORY_ALTERNATIVE);
menu.addIntentOptions(Menu.CATEGORY_ALTERNATIVE, 0, 0,
new ComponentName(this, NotesList.class), null, intent, 0, null);

这到底有何用处呢?其实这是一种动态菜单技术(也有点像插件机制),若某一个activity,其类型 是”android.intent.category.ALTERNATIVE”,数据 是”vnd.android.cursor.dir/vnd.google.note”的话,系统就会为这个activity增加一个菜单项。在 androidmanfest.xml中查看后发现,没有一个activity符合条件,所以这段代码并没有动态添加出任何一个菜单项。

为了验证上述分析,我们可以来做一个实验,在androidmanfest.xml中进行修改,看是否会动态生成出菜单项。

实验一

首先我们来创建一个新的activity作为目标activity,名为HelloAndroid,没有什么功能,就是显示一个界面。

public class HelloAndroid extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.setContentView(R.layout.main);
}
}

它所对应的布局界面XML文件如下:

<?xml version=”1.0″ encoding=”utf-8″?>
<LinearLayout xmlns:android=”http://schemas.android.com/apk/res/android”
android:orientation=”vertical”
android:layout_width=”fill_parent”
android:layout_height=”fill_parent”
>
<TextView
android:layout_width=”fill_parent”
android:layout_height=”wrap_content” android:id=”@+id/TextView01″/>

<Button android:id=”@+id/Button01″ android:layout_height=”wrap_content” android:layout_width=”fill_parent” android:text=”@string/txtInfo”></Button>
</LinearLayout>

然后修改androidmanfest.xml,加入下面这段配置,让HelloAndroid满足上述两个条件:

    <activity android:name=”HelloAndroid” android:label=”@string/txtInfo”>
<intent-filter>
<action android:name=”com.android.notepad.action.HELLO_TEST” />
<category android:name=”android.intent.category.ALTERNATIVE”/>
<data android:mimeType=”vnd.android.cursor.dir/vnd.google.note” />
</intent-filter>
</activity>

好了,运行下试试,哎,还是没有动态菜单项加入呀!怎么回事呢?查看代码后发现,原来是onPrepareOptionsMenu搞的鬼!这个 函数在onCreateOptionsMenu之后运行,下面这段代码中,由于Menu.CATEGORY_ALTERNATIVE是指向同一个组,所以 把onCreateOptionsMenu中设置的菜单项给覆盖掉了,而由于onPrepareOptionsMenu没有给 Menu.CATEGORY_ALTERNATIVE附新值,故Menu.CATEGORY_ALTERNATIVE还是为空。

   Intent intent = new Intent(null, uri);
intent.addCategory(Intent.CATEGORY_ALTERNATIVE);
menu.addIntentOptions(Menu.CATEGORY_ALTERNATIVE, 0, 0, null, specifics, intent, 0,items);

好的,那我们暂时把上面这几句给注释掉,当然,也可以不注释这几句,在onCreateOptionsMenu中改groupid号,即将Menu.CATEGORY_ALTERNATIVE改为Menu.first,其他的也行,但注意不要改为menu.none,这样会覆盖掉。

menu.add(0, MENU_ITEM_INSERT, 0, R.string.menu_insert)
.setShortcut(‘3’, ‘a’)
.setIcon(android.R.drawable.ic_menu_add);

添加的菜单。因为menu.none也为0。运行后就可以看到动态菜单出来了!

上面这个options menu是在NotesList界面上没有日志列表选中的情况下生成的,若先选中一个日志,然后再点”menu”,则生成的options menu是下面这样的:

哎,又动态增加了两个菜单项”Edit note”和”Edit title”,这又是如何动态加入的呢?这就是onPrepareOptionsMenu的功劳了。

    Uri uri = ContentUris.withAppendedId(getIntent().getData(), getSelectedItemId());

首先获取选中的日志(若没有选择,则uri为空)

  Intent[] specifics = new Intent[1];
specifics[0] = new Intent(Intent.ACTION_EDIT, uri);
MenuItem[] items = new MenuItem[1];

然后为选中的日志创建一个intent,操作类型为Intent.ACTION_EDIT,数据为选中日志的URI.于是会为选中的日志创建一个”Edit note”菜单项。

 Intent intent = new Intent(null, uri);
intent.addCategory(Intent.CATEGORY_ALTERNATIVE);
menu.addIntentOptions(Menu.CATEGORY_ALTERNATIVE, 0, 0, null, specifics, intent, 0,
items);

这几句和上面onCreateOptionsMenu函数中类似,用于动态增加菜单项,若某一个activity,其类型是”android.intent.category.ALTERNATIVE”,数据是”vnd.android.cursor.item/vnd.google.note”的话,系统就会为这个activity增加一个菜单项。在androidmanfest.xml中查看后发现,TitleEditor这个activity符合条件,于是系统就为TitleEditor这个activity动态添加一个菜单项”Edit title”。

else {
menu.removeGroup(Menu.CATEGORY_ALTERNATIVE);
}

若日志列表为空,则从菜单中删除组号为Menu.CATEGORY_ALTERNATIVE的菜单项,只剩下”Add note”菜单项。

  处理“选中菜单项”事件

菜单项选中事件的处理非常简单,通过onOptionsItemSelected来完成,这里只是简单地调 用 startActivity(new Intent(Intent.ACTION_INSERT, getIntent().getData()));这个intent的操作类型为Intent.ACTION_INSERT,数据为日志列表的URI, 即”content:// com.google.provider.NotePad/notes”

     @Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case MENU_ITEM_INSERT:
// Launch activity to insert a new item
startActivity(new Intent(Intent.ACTION_INSERT, getIntent().getData()));
return true;
}
return super.onOptionsItemSelected(item);
}

  Context Menu

下面介绍另一种菜单—上下文菜单,这通过重载onCreateContextMenu函数实现。首先确认已经选中了日志列表中的一个日志,若没选择,则直接返回。Cursor指向选中的日志项。

   Cursor cursor = (Cursor) getListAdapter().getItem(info.position);
if (cursor == null) {
// For some reason the requested item isn’t available, do nothing
return;
}

然后,设置上下文菜单的标题为日志标题

        // Setup the menu header
menu.setHeaderTitle(cursor.getString(COLUMN_INDEX_TITLE));

最后为上下文菜单增加一个菜单项

        // Add a menu item to delete the note
menu.add(0, MENU_ITEM_DELETE, 0, R.string.menu_delete);

对于上下文菜单项选中的事件处理,是通过重载onContextItemSelected实现的。

        switch (item.getItemId()) {
case MENU_ITEM_DELETE: {
// Delete the note that the context menu is for
Uri noteUri = ContentUris.withAppendedId(getIntent().getData(), info.id);
getContentResolver().delete(noteUri, null, null);
return true;
}
}
return false;
}

对于日志的删除,首先调用ContentUris.withAppendedId(getIntent().getData(), info.id);来拼接出待删除日志的URI.然后getContentResolver().delete(noteUri, null, null);调用下层的Content Provider去删除此日志。

  实验二

来做个简单实验,在上述代码基础上增加一个上下文菜单项。首先在onCreateContextMenu函数中增加一个上下文菜单项:

menu.add(0,MENU_ITEM_INSERT,0,R.string.menu_insert);

然后为其在onContextItemSelected函数中增加一个处理过程:

case MENU_ITEM_INSERT:
{
new AlertDialog.Builder(this).setIcon(R.drawable.app_notes)
.setTitle(R.string.app_name).setMessage(R.string.error_message).setPositiveButton(R.string.button_ok, new OnClickListener(){

public void onClick(DialogInterface dialog, int which) {
// TODO Auto-generated method stub

}

}).show();
return true;
}

实验结果如下:


Android实例剖析笔记(一)

开卷语

俗话说,“熟读唐诗三百首,不会作诗也会吟”。最近收集了很多Android的示例代码,从这些代码的阅读和实验中学习到很多知识,从而产生写这个系列的打算,目标就是一步步跟着实例进行动手实作,真正从“做”中体会和学习Android开发。

本文是这个系列的第一篇,目标是Android自带的一个范例程序:记事本,将分为四篇文章进行详细介绍。

  预备知识

搭建开发环境,尝试编写”Hello World”,了解Android的基本概念,熟悉Android的API(官方文档中都有,不赘述)。

  程序截图

先来简单了解下程序运行的效果

  程序入口点 

类似于win32程序里的WinMain函数,Android自然也有它的程序入口点。它通过在AndroidManifest.xml文件中配置来指明,可以看到名为NotesList的activity节点下有这样一个intent-filter,其action为android.intent.action.MAIN, Category指定为 android.intent.category.LAUNCHER,这就指明了这个activity是作为入口activity,系统查找到它后,就会创建这个activity实例来运行,若未发现就不启动(你可以把MAIN改名字试试)。

[code lang=”xml”]
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
[/code]

  NotesList详解

就从入口点所在的activity(见图1)开始,可以看到这个activity最重要的功能就是显示日志列表。这个程序的日志都存放在Sqlite数据库中,因此需要读取出所有的日志记录并显示。先来看两个重要的私有数据,第一个PROJECTION字段指明了“日志列表“所关注的数据库中的字段(即只需要ID和Title就可以了)。

[java]
private static final String[] PROJECTION = new String[] {
Notes._ID, // 0
Notes.TITLE, // 1
};
[/java]

第二个字段COLUMN_INDEX_TITLE指明title字段在数据表中的索引。

private static final int COLUMN_INDEX_TITLE = 1;

然后就进入第一个调用的函数onCreate。

[java]</div>
<div>Intent intent = getIntent();
if (intent.getData() == null)
{
intent.setData(Notes.CONTENT_URI);
}</div>
<div>[/java]

因为NotesList这个activity是系统调用的,此时的intent是不带数据和操作类型的,系统只是在其中指明了目标组件是Notelist,所以这里把”content:// com.google.provider.NotePad/notes”保存到intent里面,这个URI地址指明了数据库中的数据表名(参见以后的NotePadProvider类),也就是保存日志的数据表notes。

        Cursor cursor = managedQuery(getIntent().getData(), PROJECTION, null, null, Notes.DEFAULT_SORT_ORDER);

然后调用managedQuery函数查询出所有的日志信息,这里第一个参数就是上面设置的” content:// com.google.provider.NotePad/notes”这个URI,即notes数据表。PROJECTION 字段指明了结果中所需要的字段,Notes.DEFAULT_SORT_ORDER 指明了结果的排序规则。实际上managedQuery并没有直接去查询数据库,而是通过Content Provider来完成实际的数据库操作,这样就实现了逻辑层和数据库层的分离。

 SimpleCursorAdapter adapter = new SimpleCursorAdapter(this, R.layout.noteslist_item, cursor,
new String[] { Notes.TITLE }, new int[] { android.R.id.text1 });
setListAdapter(adapter);

查询出日志列表后,构造一个CursorAdapter,并将其作为List View的数据源,从而在界面上显示出日志列表。可以看到,第二个参数是R.layout.noteslist_item,打开对应的noteslist_item.xml文件,

[xml]</div>
<div><TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@android:id/text1"
android:layout_width="fill_parent"
android:layout_height="?android:attr/listPreferredItemHeight"
android:textAppearance="?android:attr/textAppearanceLarge"
android:gravity="center_vertical"
android:paddingLeft="5dip"
android:singleLine="true"
/></div>
<div>[/xml]

就是用来显示一条日志记录的TextView,最后两个字段指明了实际的字段映射关系,通过这个TextView来显示一条日志记录的title字段。

  处理“选择日志”事件

既然有了“日志列表”,就自然要考虑如何处理某一条日志的单击事件,这通过重载onListItemClick方法来完成,

[java]</div>
<div>@Override
protected void onListItemClick(ListView l, View v, int position, long id) {
Uri uri = ContentUris.withAppendedId(getIntent().getData(), id);String action = getIntent().getAction();
if (Intent.ACTION_PICK.equals(action) || Intent.ACTION_GET_CONTENT.equals(action)) {
// The caller is waiting for us to return a note selected by
// the user.  The have clicked on one, so return it now.
setResult(RESULT_OK, new Intent().setData(uri));
} else {
// Launch activity to view/edit the currently selected item
startActivity(new Intent(Intent.ACTION_EDIT, uri));
}
}</div>
<div>[/java]

首先通过”content:// com.google.provider.NotePad/notes”和日志的id 号拼接得到选中日志的真正URI,然后创建一个新的Intent,其操作类型为Intent.ACTION_EDIT,数据域指出待编辑的日志URI(这里只分析else块)。

  Intent深度剖析

那么,上面这句startActivity(new Intent(Intent.ACTION_EDIT, uri))执行后会发生什么事情呢?这时候Android系统就跳出来接管了,它会根据intent中的信息找到对应的activity,在这里找到的是NoteEditor这个activity,然后创建这个activity的实例并运行。

那么,Android又是如何找到NoteEditor这个对应的activity的呢?这就是intent发挥作用的时刻了。

new Intent(Intent.ACTION_EDIT, uri)

这里的Intent.ACTION_EDIT=” android.intent.action.EDIT”,另外通过设置断点,我们看下这里的uri值:

可以看到选中的日志条目的URI是:content://com.google.provider.NotePad/notes/1。然后我们再来看下Androidmanfest.xml,其中有这个provider

<provider android:name=”NotePadProvider”
android:authorities=”com.google.provider.NotePad”
/>

发现没有?它也有com.google.provider.NotePad,这个是content://com.google.provider.NotePad/notes/1的一部分,同时

    <activity android:name=”NoteEditor”
android:theme=”@android:style/Theme.Light”
android:label=”@string/title_note”
android:screenOrientation=”sensor”
android:configChanges=”keyboardHidden|orientation”
>
<!– This filter says that we can view or edit the data of
a single note –>
<intent-filter android:label=”@string/resolve_edit”>
<action android:name=”android.intent.action.VIEW” />
<action android:name=”android.intent.action.EDIT” />
<action android:name=”com.android.notepad.action.EDIT_NOTE” />
<category android:name=”android.intent.category.DEFAULT” />
<data android:mimeType=”vnd.android.cursor.item/vnd.google.note” />
</intent-filter>
<!– This filter says that we can create a new note inside
of a directory of notes. –>
<intent-filter>
<action android:name=”android.intent.action.INSERT” />
<category android:name=”android.intent.category.DEFAULT” />
<data android:mimeType=”vnd.android.cursor.dir/vnd.google.note” />
</intent-filter>
</activity>

上面第一个intent-filter中有一个action 名为android.intent.action.EDIT,而前面我们创建的Intent也正好是Intent.ACTION_EDIT=” android.intent.action.EDIT”,想必大家已经明白是怎么回事了吧。

下面就进入activity选择机制了:

系统从intent中获取道uri,得到了content://com.google.provider.NotePad/notes/1,去掉开始的content:标识,得到com.google.provider.NotePad/notes/1,然后获取前面的com.google.provider.NotePad,然后就到Androidmanfest.xml中找到authorities为com.google.provider.NotePad的provider,这个就是后面要讲的contentprovider,然后就加载这个content provider。

        <provider android:name=”NotePadProvider”
android:authorities=”com.google.provider.NotePad”
/>

在这里是NotePadProvider,然后调用NotePadProvider的gettype函数,并把上述URI传给这个函数,函数返回URI所对应的类型(这里返回Notes.CONTENT_ITEM_TYPE,代表一条日志记录,而CONTENT_ITEM_TYPE = ” vnd.android.cursor.item/vnd.google.note “)。

   @Override
public String getType(Uri uri) {
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 = new UriMatcher(UriMatcher.NO_MATCH);
sUriMatcher.addURI(NotePad.AUTHORITY, “notes”, NOTES);
sUriMatcher.addURI(NotePad.AUTHORITY, “notes/#”, NOTE_ID);

然后系统使用获得的” vnd.android.cursor.item/vnd.google.note “和”android.intent.action.EDIT”到androidmanfest.xml中去找匹配的activity.

  <intent-filter android:label=”@string/resolve_edit”>
<action android:name=”android.intent.action.VIEW” />
<action android:name=”android.intent.action.EDIT” />
<action android:name=”com.android.notepad.action.EDIT_NOTE” />
<category android:name=”android.intent.category.DEFAULT” />
<data android:mimeType=”vnd.android.cursor.item/vnd.google.note” />
</intent-filter>

正好NoteEditor这个activity的intent-filter满足上述条件,这样就找到了NoteEditor。于是系统加载这个类并实例化,运行,然后就到了NoteEditor的OnCreate函数中(见后续文章)。

  小技巧

1,在命令行中使用”adb shell”命令进入系统中,然后”cd app”进入应用程序所在目录,”rm XXX”就可以删除你指定的apk,从而去掉其在系统顶层界面占据的图标,若两次”cd data”则可以进入应用程序使用的数据目录,你的数据可以保存在这里,例如Notepad就是把其数据库放在它的databases目录下,名为note_pad.db.

2,第一次启动模拟器会比较慢,但以后就别关闭模拟器了,修改代码,调试都不需要再次启动的,直接修改后run或debug就是。

0
0

关于Activity中各个方法的调用

程序正常启动:onCreate()->onStart()->onResume();
正常退出:onPause()->onStop()->onDestory()

一个Activity启动另一个Activity: onPause()->onStop(), 再返回:onRestart()->onStart()->onResume()

程序按back 退出: onPause()->onStop()->onDestory(),再进入:onCreate()->onStart()->onResume();
程序按home 退出: onPause()->onStop(),再进入:onRestart()->onStart()->onResume();

针对开发者的20款移动开发框架

本文收集了20款针对开发者的移动开发框架,以帮助他们为移动爱好者开发出新颖、有用、有趣味的应用。

1.Fries

Fries是一款稳定的HTML-CSS-JS框架,用于在实际项目和原型设计中创造类似于Android的原生UI界面。该框架包含所有的重要组 件,比如Form、工具栏、列表、按钮、下拉列表及标签。它还专门针对PhoneGap进行了优化,能容易地转换为本地应用。

源代码

2. Appium

Appium是一款开源自动化测试工具。可对任何语言的iOS应用和Android应用进行测试,测试可使用Java、Objective-C、 JavaScript、PHP、Python、Ruby、C#、Clojure、Perl等众多语言编写。目前只针对Mac OS X ,且需要有 Node.js来支撑。

源代码

3. Junior

Junior为前端框架,用来构建基于HTML5的移动Web应用,外观与行为跟本地应用相似。它采用针对移动性能优化的CSS3转换,支持旋转灯箱 效果,包含多样的Ratchet UI组件。整个框架使用Zepto(类似jQuery语法的轻量级移动设备js类库),且整合了backbone.js 的视图和路由。Junior十分易于使用,且提供详细的文档及案例,便于学习。

源代码

4. Enyo

Enyo,为JavaScript开发框架,最初发布于HP TouchPad的webOS之上。现在发布了2.0版本,成为跨平台框架,不再只针对 webOS(1.0版本只针webOS)。新版本的Enyo支持桌面与移动,可工作于所有主流浏览器,拥有丰富的跨平台UI组件,以及构建应用所需的强大 的布局库。

源代码

5. Sidetap

Sidetap是一款简洁轻量级的移动Web应用开发框架(缩减压缩后只有2KB)。它专注于提供类似于Facebook移动应用这样的侧导航形式。导航部分解决后,利用它创建简单的移动Web应用就变得相当简单了。

源代码

6. Mobello

Mobello是一个开源JavaScript UI框架,目的是简化移动Web应用的开发过程。利用该框架,可在移动端提供与本地应用相似的体验。 它针对触控事件进行了优化,并提供20多种广泛应用的UI组件。它还提供了集成开发环境Mobello Studio,在其中可利用HTML5、CSS和 Mobello框架开发移动应用。

源代码

7. Moobile

Moobile是基于MooTools的移动Web应用框架,是一个新项目。它专注于提供类iOS的体验,并对按钮、图片、列表等提供较好的控制。它 还支持各种过渡样式,比如淡入淡出、幻灯片等,并可显示类本地应用的提示框(alert)。Moobile所创建的界面更具有弹性,可很好地工作于 iPhone和iPad上。

源代码

8. Spine Mobile

Spine Mobile是一个构建在SpineJS之上的JavaScript框架,用于构建看起来外观像本地应用的移动Web应用。该框架带有专用控制器、面板布局、硬件加速的转换和触摸事件。

源代码

9. Zoey

它是一个采用HTML5-CSS3技术实现的框架,用于构建移动应用。它基于Zepto.js构建,轻量,压缩后只有6kb。Zoey拥有的大量UI 控件,比如:导航、列表、按纽、控件分组、表单、表格。这个框架支持iOS和Android,并自带一个覆盖所有功能的程序骨架。

源代码

10. iUI

iUI为移动Web框架。该框架包含JavaScript库、CSS和图片集,用于开发可触摸Web应用。它所创建的应用有着iPhone SDK构建的本机应用程序那样的外观和感觉,可运行于大部分智能手机和平板电脑上,只要它包含一个符合标准的Web浏览器。

源代码

11. Lungo.js

Lungo.jS是一个使用HTML5、CSS3和 JavaScript技术的移动Web开发框架。所创建应用可运行于所有流行平台之上(iOS、 Android、Blackberry和WebOS)。它支持触控事件,如单击、双击和滑动。无需使用图片,全部采用向量声称。

源代码

12. Wink Toolkit

Wink Toolkit为JavaScript框架,用来创建移动Web应用。该框架的核心提供了开发移动应用应具备的所有基础功能,从触摸事件处理到DOM操作和CSS转换等。此外,它还提供非常多的UI控件来帮助改进Web应用的外观。

源代码

13. The M Project

The M Project是一款HTML5 JS框架,可构建跨平台的移动Web应用(如OS、Android、Palm webOS、BlackBerry平 台)。其JavaScript部分采用 jQuery,并包含所有jQuery UI核心文件,如离线支持、国际化等。The-M-Project并不是独立的,它需要引入nodeJS和一个称 为Espresso!的构建工具,该工具可使你更容易地结构化代码、构建并运行在内嵌服务器上。

源代码

14. DHTMLX Touch

DHTMLX Touch为JavaScript库,基于HTML5,用于创建移动Web应用。它不只是一组UI小工具,而是一个完整的框架,可以针 对移动和触摸设备创建跨平台的Web应用。它兼容主流的Web浏览器,用它创建的应用,可在iPad、iPhone、Android智能手机等上面流畅运 行。

源代码

15. Zepto.js

Zepto.js是支持移动WebKit浏览器的JavaScript框架,具有与jQuery兼容的语法。轻量级,大小为2-5k的库,通过不错的API处理绝大多数的基本工作。

源代码

16. jQuery Mobile

jQuery Mobile是 jQuery发布的针对手机和平板设备、经过触控优化的Web框架。它基于jQuery,在不同移动设备平台上可提供统一的用户界面。该框架基于渐近增强技术,并利用HTML5和CSS3特性。

源代码

17. Jo

Jo为基于HTML5的开源移动应用框架。该框架提供丰富的平台支持,包括webOS、iOS、Android、Symbian、Safari、 Chrome甚至是 Mac OS®X Dashboard小部件。Jo也兼容PhoneGap。Jo 的简单性和轻量级与 PhoneGap的强大功能 相结合,最终将生成一个有效的工具,可以针对广泛的平台开发丰富的移动本地应用程序。

源代码

18. Sencha Touch

它是一款HTML5移动应用框架。通过它可以创建Web应用,在外观和感觉上与Apple iOS 和Google Android本地应用十分相像。它利用HTML5发布音频/视频,进行本地存储;利用CSS3提供圆角、背景渐变、阴影等广泛使用的样式。

源代码

19. WebApp.Net

WebApp.Net是一款基于Ajax技术的JavaScript框架,用于构建移动Web应用。它提供了一整套组件(开关按钮、单选按钮组等),可帮助开发者创建外观和行为与本地移动应用十分相似的网站。

源代码

20. Helios

Helios为开源框架,为iOS应用提供必要的后台服务,从数据同步、推送通知,到应用内购买、passbook继承。它可帮助开发人员在数分钟的时间内构建出一个包含客户端和服务器端的应用。

源代码

 

原文链接: http://www.admin10000.com/document/2355.html