`
yky28yky
  • 浏览: 12825 次
最近访客 更多访客>>
社区版块
存档分类
最新评论

Android深入探究笔记之一 -- 我的第一个 Android 程序,基于 Intent 的组件交互机制

 
阅读更多

  1. 本文是临时写的。觉得有它才完整。距离我今天安排的睡觉时间还有2个小时,还有其它事情没做,因此仓促的凑上一篇吧。
  2. 什么是Android 。请Google 之。
  3. 搭建环境。
  ** 你需要有Eclipse 或Myeclipse。当然还有JDK。
  ** 下载Android SDK 和ADT 
  SDK 即开发工具包(Software Development Kit)。
  Android SDK 最新版本是2.3 。但本笔记中的大部分范例都是基于Android2.2。下载方式直接搜就行了。所有妄图直接去Android 官网上下载的行为,都将直接出发到我国和谐大局。切记。
  安装ADT。
  下载Android ADT (Android Development Tools),开发者工具。是一套基于Eclipse 的插件。
  最新的在仿佛是0.9.9 吧。依稀记得几天前一哥们硬盘里有这东西。
  安装的方式有两种。
  * 在线安装
  1. 打开Eclipse。
  (突然想到关于兼容的问题,貌似我依稀记得,几个月前我安装的时候遇到过Eclipse 和ADT 插件的版本兼容的问题。
  表现就是ADT 装完了,某些功能始终用不起来,比如模拟器,千呼万唤就是不出来。后来换了一个Eclipse 3.5 的就好了(当时的ADT 大概是4 个月前的最新版)。
  我没有无聊的仔细研究那个版本的ADT 对应哪个版本的Eclipse。总之大家记得用Eclipse3.5 以上的版本就好,或者干脆去官网下最新的Eclipse。这应该就没什么问题,因为Eclipse3.5、3.6 我都有开发过Android 的经验。没有问题)。
  2. 如图所式,点击Eclipse 菜单栏,Help àInstall New Software
  
  "Install New Software"在Eclipse 的不同版本有不同叫法,反正大概就那意思。
  3.如图所示,点击"Add..",在出来的对话框,填入"http://dl-ssl.google.com/android/eclipse/"
  
  .     4.这时候Eclipse 会自动去搜寻可以安装的资源。要稍等一会,前提是你要联网。
  
  5. 然后就一直下一步,在进行到某个时候,会让你选择是否同意某某协议才能继续,于是同意:
  
  点击完成。
  6. 等吧。要抱有打持久战的准备。
  * 推荐另一种方式,下载安装包,离线安装,如:
  
  然后,回到上面第三张图片,点击"Archive"按钮,选择你现在的离线安装包,如图:
  
  这样,也能继续安装步骤。
  安装成功后你将能在Eclipse 工具栏上看到下图所示图标:
  
  ** 安装SDK
  解压它:
  
  但是通过官方途径下载的SDK 是不自带开发包的。
  因为Android 版本太多,你需要通过它的工具,自己去挑选需要的版本的Android 开发包进行下载,点击下图所示的机器人:
  
  然后就可以通过向导
  选择你需要下载的SDK 包,同时文档也需要你自己下载 ,这个很重要,一定要下载。建议选择下载的SDK 版本时,别全选,选择Android2.0 及其以上的就好,2.3我也是今天才听说,没试过水。就算你只选择了这几个包,也够你下很久的。
  下载完成后,你的android-sdk-windows 目录下会多出一个platforms 目录,这下面就存放了你选择的下载好的Android 开发包:
  
  ** 2.0版本的对应的API Level 是5
  ** 2.1版本的对应的API Level 是7
  ** 2.2版本的对应的API Level 是8
  ** 至于我目录中的2、3、4是什么玩意我也没玩过。
  ** 配置path环境变量,便于命令窗口直接敲Addroid 指令。需要将Android SDK 安装目录中的tools 文件夹路径添加到path环境变量中。
  ** 为Eclipse 关联SDK:                   Window  àPreferences,然后如图:                
  
  点击"Browse"选择SDK 所在的路径,注意是跟目录 。
  ** 关联Android 源码,特别是Android学习,通过看源码,能解决很多学习上的问题。对学习有非常大的帮助。很快你们就发现,安装传统的方式,是无法关联Android 源码的。幸好某某大婶研究出了一个方案,就是对应版本的开发包下面新建一个sources 目录,然后将你下载的源码仍在里面。例如,我下载了一份Android 2.2 的源码(网上淘的,只是一些常用的类库啊之类的,不是全部的,全部的据说有几个G ),那么,我需要在platforms/android-8/ 这个目录下新建一个sources 目录,将解压开的源码扔进去。
  4. 写一个HelloWorld 程序
  ** 没多少步骤,只需要新建一个Android 项目就可以,点击"New"--- "Android Project",在非"Java"透视图下,你可能需要点击 "Other",到创建向导中的"Android"目录下,才能选择到"Android Project",具体细节如图:
  
  
  5.项目虽然刚刚才创建出来,其实我们的HelloWorld 程序就已经写好了。Android 项目创建出来以后,什么都不用做,直接部署到手机上,就能够看见"Hello World!"。
  那么这个项目怎样才能运行得起来呢,你也可以将他不部署到真实手机,但是一般情况下,我们用Android 提供的模拟器就可以完成应用程序的初步测试了。
  6. 创建模拟器,点击图标:  
  
  在模拟器列表界面,点击New,进入到创建向导:
  
  创建了之后,将回到模拟器列表页面,选中创建的模拟器,点击"Start",再在弹出界面中点击"Launch",如图::
  
  7.怀着虔诚的心态等待模拟器启动,若迟迟不见动静,则找到占用端口号为"5544"的进程,Kill丫的。,启动成功后,将看到模拟器界面,如图(忘了写模拟器编程汉语环境了,简单步骤为,进入。。。额。。欢迎使用google和baidu):
  
  8.下面部署我们的HelloWorld,选中项目,右键点击"Run As"-"Android Application"
  9. 等候运行结果,查看模拟器,运行后,模拟器画面为:
  
  9.解析以及说明项目目录结构:
  粗略的看几个文件夹:
  "src"当然是用来存放源文件的。
  "res"肯定是"Resources"的缩写,一个良好的Android 应用程序,应该要尽量将所有应用中用到的资源,如图片,XML配置,甚至包括字符串等等,都存放在这个文件夹底下统一管理。
  "gen"下是一些由系统控制的资源,例如R 文件,或者后面笔记中接触到的AIDL实现服务之间的进程通信时所自动产生的类文件。它们不由我们控制。
  然后再来细看它们底下的子文件夹和文件: 
  先来看"gen"下的"R.java"。这个文件如果你尝试着去更改它,会发现是无效的。并且它默认是存放在与应用包名相同的包下面的,尝试去移动它也必将失败。因为它不由我们管理。每当我们在"res"文件夹及其子文件件下添加一个资源时,然后刷新R文件,都会发现它会生成一个变量名和资源文件名相同的静态变量。再结合"res"文件夹的意义,我们不难猜出这个R文件就是访问"res"文件夹下的资源的索引集合。只要是在"res"下的资源,我们都能通过R文件访问到。这个类的代码为:       public final class R { public static final class drawable { public static final int icon=0x7f020001; } public static final class layout { public static final int main=0x7f030000; } public static final class string { public static final int app_name=0x7f040001; public static final int hello=0x7f040000; } } 可以看到,它的机制类似于这样,每一个res目录下的子目录都对应R这个类中的一个静态内部类(drawable-hdpi等三个对应一个drawable类)。
  例如,layout文件夹下我们观察到有一个main.xml 文件,于是观察R 文件,果然是有一个layout 内部类,它里面又有一个main 静态变量。
  那么是不是我们通过R.layout.main 就可以访问到main.xml 呢。因为我们的分析结果不是R 文件里的变量都是到某一个具体资源的索引么? 完全正确!说R.java.main 可以访问到main.xml 完全没问题。只不过这个访问是由Android 内部完成的。
  相当于送快递一样,寄的人给个地址,说送到某某某地某某街某某门牌某某公司某某人,因为这个地址足够详细,所以快递很快找到目标,然后完成任务。
  那么Android 为什么要采取这种机制呢?将应用的资源与应用本身剥离,有什么好处?我觉得首先有一个好处就是,加载资源这一行为由Android 来做,它肯定比程序员更了解该怎样来管理这个资源 ,例如,资源过大,不能持久保存、资源很小,我加载在内存里,先保存着,下次有人用的时候,我再拿出来。操作系统本身最了解自己的内存状态和运行效率 ,因此,它也更会管理内存和优化软件的运行效率。个人看法。
  AndroidManifest.xml 文件
  这个文件我将他看成应用的功能清单,类似于struts中的struts-config.xml。每当我们要到一种组件时,总是要现在这个文件中声明(或者说注册)。它初始时就默认定义了应用的一些基本属性,文件内容为:                   先介绍跟元素manifest,这是站在整个程序角度来描述整个应用,此元素内部可以声明组件,申请权限等等,其属性:
  xmlns:android:命名空间,这没什么说的,照着写就是了。
  package:应用所在的包,包名要尽量避免大众化--我的意思是,要尽量降低你的应用和世界上其它应用包名相同的情况。因为当应用安装软件到手机后,包名将作为操作系统的进程名。这个应用包名仿佛承载着区别应用与应用的一个标志。只要遵守一般Java 项目中的包命名规范,这种情况应该也很少,就算有,让这本来几率就低的两个软件正好运行在同一部手机上,几率更是直逼彩票获奖率。
  而且也许Android 有处理这种问题的机制,我还在查。
  android:versionCode:应用的版本号,默认为1
  android:versionName:版本名称
  application:此标签用来描述应用中的组件声明和应用的全局属性。此标签只能有一个。
  android:icon:应用的图标:@drawable/donkey 这个表达式将在后面进一步说明。
  android:label:@string/app_name 对应了一个字符串,也可以给这个标签直接写上一个字符串,如"宇宙级五子棋"等。这个属性设置的值,将出现在用户的手机软件标题上,例如用户安装了你的软件,那么他的软件列表里就将出现你的软件,那么显示的软件名称就是这家伙。这个值还将默认作为主窗口的默认标题。
  Activity 标签 :一个Activity 通常表现为软件上一个单独的屏幕界面,它们或者独占软件的整个屏幕,或者被改造为一个对话框。它通常对应一个继承了Activity 的类,因此,若我们要新建一个窗口,一个办法是新建一个类,继承Activity,再将此Activity 照猫画虎注册在本XML 文件中,并在合适的实际,激活它就可以了。
  Activity 这个名字乍看有点怪,起个名字叫"活动",明明就是一个界面嘛。
  其实这是另一个角度的抽象,我们想,其实一个程序,不就是一个"大的活动"么,这个"活动"里又包含了多个"子活动"。
  那么Android 将一个通常作为窗口这样一个控件称之为"活动",那么,肯定是将它看做一个"活动"的容器,它可以承载很多活动。
  例如,"鼠标点击","键盘按下"等等。或者从另一个角度,例如"用户输入数据"、"用户想看一个对话框"、"用户想把一个控件拖动一下"等等活动。这些活动都是发生在"窗口"上的,或者是通过"窗口"来触发的(其实这种情况下,也可以看成是窗口在处理活动,只是它调用了另一个控件来辅助而已)。于是,从这个角度来看,将这个控件取名为Activity 再合适不过了。
  android:name:这个属性指定Activity 的类名。".MainActivity"中的"."(点) 就表示当前应用的包名,也就是manifest 标签的package 属性的值:      wjh.android.helloworld。这样".MainActivity"就成了:wjh.android.helloworld.MainActivity,查看"src"文件夹下,发现果然存在这个包和这个类。
  试验表明,此处可以直接写成"MainActivity"(扔掉前面的点),这是一种特殊情况,因为这个类刚好在wjh.android.helloworld 包下面。
  另外,试验也表明,将这里直接写成:wjh.android.helloworld.MainActivity 类的完整路径也是可以的。
  最可怕的是,将这个Activity 类,定义在一个与应用包名完全不相关的包下面,如"aa.bb.cc",然后将这里改成"aa.bb.cc.HelloActivity"居然也是可以的。我发誓在两分钟之前,我一直觉得是不可以的。现在想想为什么我会这么觉得呢--经验和思想带出的直觉而已。直觉真可怕。
  android:label:既然上面分析Activity 可以看着一个屏幕窗口,那么,这个窗口也应该有标题,此属性设置的值将作为窗口的标题。这里可以直接写上想显示在窗口上的标题的字符串。
  intent-filter: 我现在觉得我不该这样一个个说,好像泄露了很多不该在HelloWorld 中说的话题。这个标签有体现了一个Android 的设计思想,它将用户与软件组件之间、组件与组件之间的交互行为抽象为Intent(意图)。很形象也很抽象。
  例如,用户想看到另一个窗口,于是他点击了现在的窗口的"Open New Window",另一个窗口打开了。这其中,经历了一个转换,就是程序,将用户的"意图"转换成了程序能描述出来的、并能执行的指令。
  这个转换最后就成了,"将现在的Activity(现在的窗口)切换到另一个Activity(用户想看到的新的窗口)"。处理方式就是,将现在的Activity 关闭掉或者暂停掉,然后激活另一个Activity--Android 只允许应用程序中同时最多存在一个正激活的Activity。由此转换,成功破译用户的需求,并执行成功。
  这有一个问题。想想struts 里面,当ActionServlet 接收到用户的请求,表达了要连接到一个Action 时,它是怎么处理的--它需要解析ActionMapping,得到path,再得到Action。这里的path,相当于提供了外界访问到此Action 的一种途径。
  我想要说的问题是,用户说:"我想要看到一个显示最新新闻列表的窗口,快给我"。
  程序破译后,要停止现在的Activity 而打开一个新的Activity 。但是它如何知道要打开哪一个Activity 才能满足用户的"最新新闻列表的窗口"需求呢?或者就算我们知道,可是要如何连接到这个Activity 呢?写死?当然可以。但是太笨,太不灵活。这样的软件,没有持久生命力。
  那么struts 是通过path 来找到Action 的。Android 是通过什么来找Activity 的呢(这里我们只讨论Activity,其实这套机制同样适用于Android 的其它组件)?就是Intent!意图!
  那么上面的这段配置:
  
  
  
  
  就是定义了一个对Activity 的访问规则。既然Activity 是通过意图来实现激活的,那么,很显然要说明的是,哪些意图才能够激活我?这就有点过滤器的概念了,过滤掉不能访问我的意图,或者不是准备访问我的意图。这里这段意图过滤器就是说明:当一个Intent 以"android.intent.action.MAIN"作为action(动作)名称的、并且以"android.intent.category.LAUNCHER"作为category(类别)名称的时候,这个时候,他将被我的(当前intent-filter 的Activity) Filter 截获,并允许它激活我。
  (我希望我说清楚了。因为这个机制很重要。现在不明白,建议在一个月后再回头来看,也许会发现我的说法有误,请一定发邮件告诉我)。
  理清楚这个机制以后,我们应该能明白为什么这个"MainActivity"可以作为启动窗体?一定是Android 内部弄出了一个Intent,然后让这个Intent 的action 等于"android.intent.action.MAIN",并设置它的category 为"android.intent.category.LAUNCHER"。于是这个Intent 满足了被MainActivity 截获(或者说识别?或者说接收?反正你能明白这个意思就好)到的条件 。
  其内部判断是哪一个Activity 已经满足条件啦?是"ManActivity"呗,于是启动它,就让他作为应用显示的第一个窗口,做为主窗口!因此,又额外得出一个结论,若想让一个Activity 成为主窗口,只需要定义它的intent-filter 为上面两项就可以了--因为,Android 内部就是在匹配这两项。
  这样,也许我们能看到,intent-filter 的好处之一。就像struts 一样,ActionServlet只知道:http://localhost:8080/android/intent.do这个请求,要找Path 为"/intent.do"这个Action 去处理。但是它不关心这个Action 是谁,类名是什么。不关心,这个由你配!
  Intent 也一样,它的机制之一是可以不关心将连接到的Activity 是谁,并且能够激活这个组件。
  好了,这里同样可以这么干。我不是要启动一个窗口么?如果我的程序要足够灵活,就是说我要让我具体启动哪一个窗口(激活哪一个Activity)可配。
  我就可以借鉴这种方式,给这个要启动的窗口(Activity) 定义一个intent-filter,说,只要你在程序中表示出action为"aaa" 就行了,我就知道你要启动我这个intent-filter 对应的Activity 啦:                   
  于是程序中,当你需要打开NewActivity 这个屏幕窗口时,只需要:              Intent intent = new Intent(); intent.setAction("aaa"); startActivity(intent);        这几行代码细节读者若没有这些经验可以不比理会,只需要知道,这里通过一个"aaa"就激活"NewActivity"这个窗口啦!我要是以后对程序改动,想让这个窗口更换为NewActivity222,只需要该配置就可以啦:                           Android 底层一定有个机制,实现了,根据Intent 的"aaa"找到".NewActivity222"。有MVC 经验的人,或者有struts 经验的人可能更深刻,这不是控制器么?!
  这整个过程,不是MVC 模式么?!
  实际上就是这样的,我说这么多不该在HelloWorld 中说的话,就是想说这个意思!
  J2EE 的兄弟们!咱们回老本行啦!
  (忽然想:R.java 这个文件好像也和这个模式占点边了。)
  这只是Intent 的特点之一,它与struts 的path 还是有区别的,于是引出第二大特点 。path 的值是一个简单的字符串。而此处是一个复杂的Java 对象,这意味着,它可以携带数据 。例如,当我要修改员工的信息时,是不是要在一个页面中跳转到另一个页面呢?并且传入要修改的员工的id。在Android,这个id 怎样从一个组件传递到另一个组件呢?很简单,我们不是依靠Intent 来联系的么?Intent 不是一个Java 对象么?那么好,我在设计Intent 的时候就考虑到这样一种情况,给Intent 设计一个用于携带数据的属性,当需要两个页面交互数据时,通过向Intent 的这个属性存取参数即可。
  接着说目录下的其它文件:
  values/strings.xml                 Hello World, HelloActivity! Hello Word 
  这个文件首先有点意思的是,我们观察知道:stringx.xml 这个文件最终在R 文件中对应了一个名为string 的内部类。并且每一个有效的 标签,对应其内部的一个常量。
  那么上文中我在描述"大概目录结构"时的说法是有误的 ,我说,res 目录下的子目录都对应了R 文件的一个静态内部类。应该说这个情况在大多数时候是这样的,但是在values 文件夹却除外(并不严谨,因为我没有做很仔细的实验,有可能还有其它情况也排除在外)。首先我们并没有看到在R 类中新添加一个values 子类。哦,那么它肯定是用文件名来做为静态内部类的类名的~~~额,可是,我这个文件名叫strings 也,而R 文件中对应的内部类却是string。如果说这个还不能说明问题的话,那么我们再在这个文件夹下新建一个文件,右键-新建Android XML File ,然后选择文件类型为Values --这文件夹下只能放这种类型的XML 文件,其实也就是,这个文件夹下,只能放以 作为根元素的XML 文件。
  于是我们新建一个aaa.xml 的values 文件,打开看,果然自动添加了一对。
  那按照我们的猜想,R 文件中,应该产生一个aaa 的内部类吧。打开一看,希望落空,没有这东西。
  那么我们在aaa.xml 中写点东西吧,也许是没有东西才没生成呢?
  于是我们写一对:sss
  再看R 类。还是没有aaa 这个类。那ns 这个定义的字符串资源哪去了呢?--跑到string 里面去了。
  我觉得我这说得有点偏了,于是直接说结论吧:values 下只能放Values 这种类型的XML 文件,也就是说,此文件夹下虽然有多少个xml 文件不确定,但是有几种标签是确定的,因为他们都是 的子元素嘛,Android 规定了其下面只能有八种标签(但是Item除外),都是用来定义一些常量的。例如颜色值,样式等等。
  实际上是每一种标签对应一个静态类,比如,我们在aaa.xml 写上一对:
  #330000FF
  看R 文件,马上发现生成了一个color 类。当写第二个这种标签时,就只是在原有基础上添加常量,不会新建类。其它六种都是这样。
  为什么Item 标签除外呢,它干的事吧,我写一段大家就明白:
  您好
  #7f040000
  这种写法,同样会在R 类的string 和color 中添加两个新的变量。
  有什么用呢?倒确实会在有时候用到,例如我们可以这样写:
  我自己分类的资源类型
  查看R文件,发现新建了一个cus 类。
  这是我至今发现的唯一一个能自己在R 中添加静态类的方法。
  layout/main,xml:界面布局文件                     :布局方式--线性布局。关于几种布局的详细情况请参看文档:
  
  :相当于html 中的 标签,显示一段文本。
  android:layout_width="fill_parent":控件宽度为填充父元素,在这里,父元素就是整个屏幕,所以就是满屏宽。
  android:layout_height="wrap_content":控件高度为包裹内容,就是刚刚好能够满足内容正常显示的高度。
  public class MainActivity extends Activity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); } } 这里是自定义一个MainActivity 继承了Activity。表示这是一个可当做窗口展示的控件。并且我们希望它作为启动窗口,于是在定义它的配置文件里,为他定义了一个意图过滤器。
  但是,这个控件到底怎样显示东西呢?
  首先:
  onCreate 方法:表示Activity 已经准备就绪,只差显示了,这个时候系统会自动调用这个方法,因此它不由程序员管理。这个方法在Activity 的整个生命周期内只会被调用一次。但是,这时候Activity 还一片空白,什么也没有,我们希望让它能显示一个我们定义的界面,在这个时机就加上去最合适不过。于是:
  调用setContentView() 方法,这个方法能为整个Activity 设置一个界面。而,这个界面被我们定义在了layout/main,xml 里面了,怎样才能访问这个文件呢?显然是R.layout.xml --注意此处的R 是我们自己应用下的R 类。系统本身也有这样一个类,因此,使用的时候,要按包名区分清楚。
  于是setContentView(R.layout.main); 这句代码的意思再清楚不过,就是将main.xml 定义的内容,作为MainActivity 的界面。
  貌似终于要完了,但是我紧记还有一点很重要,就是如何在XML 中访问R 文件,其实在上面我们已经多次接触到了,第一处:
  AndroidManifest,xml 中
   节点的:android:icon="@drawable/icon"
  它的意思表示访问R. drawable.icon,对应了drawable下面的一个icon图标文件
  android:label="@string/app_name"
  它的意思表示访问:R.string.app_name,对应了strings.xml 中的一个字符串
  main,xml 中
   的android:text="@string/hello"
  它表示访问R.string.hello,也就是字符串"Hello World, HelloActivity!",这也是为什么我们能什么代码也不写,就在模拟器屏幕上看到这样一个字符串了。
  总结:很重要。@string/hello 中:@表示应用中的R 文件,@string 就表示R文件下的string 内部类,/表示访问其内部元素,hello 就是R 文件下的string 内部类里面的hello 静态变量。这个表达式我们将用得很频繁.
  还有一个,有几个目录:
  drawable-hdpi:用于存放高分辨率的图片。
  drawable-ldpi:用于存放低分辨率的图片。
  drawable-mdpi:用于存放中等分辨率的图片。
  它们对应R 中的drawable 子类。
  为什么要有这样的区分。因为手机的特殊性,款式不同,它的分辨率差别可能很大:有的屏幕很小,有的很大。我需要能灵活的自动适应这种情况。
  于是就把分辨率不同的但是文件名相同 的几幅图片分别存放在对应分辨率等级的文件夹下 ,例如,我们打开这几个文件夹,可以看到系统自带的icon.png 图片,它作为了应用的图标。Android内部会自动根据用户手机的分辨率而挑选适合的图片挂上去。这也是将资源交给Android 统一管理的另一个好处,它比我们更懂每个用户的手机特征,因此只有它最适合完成屏幕适配和国际化。这里就是屏幕适配。
  这个机制不但适用于图片,还适应于所有res目录下的资源。例如新建layout-320x240文件夹,就表示此文件夹下的界面文件,适配320x240 这种分辨率的手机。
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics