Android 开发入门与实战(第二版)

978-7-115-31464-2
作者: eoe移动开发者社区 组编 姚尚朗 靳岩
译者:
编辑: 张涛

图书目录:

详情

本书从内容覆盖了Android开发的大部分场景,从andriod基础介绍、环境搭建、SDK介绍、Market使用,到应用剖析、组件介绍、实例演示等方面。从技术实现上,编写了RSS阅读器、googleMap、豆瓣网手机客户端应用等众多方面的实例应用。

图书摘要

Android开发入门与实战(第二版)
eoe移动开发者社区 组编

姚尚朗 靳岩 等 编著

人民邮电出版社

北京

前言

谨以此书献给Android开发者社区里所有的朋友们!

时间很快,距本书第一版出版已经过去3年多时间,在这3年时间里,移动互联网发生了天翻地覆的变化。Android 从最初的移动互联网新秀成长为移动互联网霸主,不仅把Symbian拉下马,还把iPhone(iOS)请下神坛,顺便又把BlackBerry、WebOS等逼上绝路,而Android继续攻城掠地巩固自己的霸主地位。

在这3年时间里,Android开发技术也发生了很多变化,从2009年的1.5版Android SDK 到现在的Android SDK 4.x 版,引入了NDK、NFC,规范了UI 设计指南,发布针对平板电脑的版本,还有诸如无线充电等新的技术。而Android 设备也由第一部 Android G1 发展到现在各种各样不少于 500 种的Android设备。

种种迹象表明,第一版的内容旧了,在出版社再三督促和读者的期待下,我们完成了本书的第二版,就是现在你拿在手中的这个版本。

本次更新相比本书的第一版来说,内容是完全重新写了一遍,几乎没有用第一版书稿的一段文字。我们重新策划、重新组织、重新编撰、重新审核。除了用最新的 SDK 重写了第一版中大家喜欢的内容外,我们在本版中加入开发进阶内容,也重新挑选了6个真实的应用开发案例。

我们会持续更新和完善这本书,努力做一本帮每一位Android开发者入门的标准教程,也非常乐意看到有越来越多的学校和培训机构采用本书作为其标准Android学习教材。

本书适合

本书适合所有对 Android 技术感兴趣的人,特别是还没有进行过 Android系统学习的人,本书会循序渐进地为你揭开Android开发的神秘面纱,让你系统地学习Android开发知识。

本书也非常适合已经学习过Android开发但是知识不成体系的人,特别是被一些培训机构填鸭式教育出来的人,通过本书的学习,可以帮你梳理知识,学会Android开发。

本书也适合那些靠自学成长,但是知识点比较零散的人,通过本书的学习,让你从体系和结构上对Android有一个更清晰的认识,把自己之前学的知识点串起来而成为真正掌握Android开发的高手。

本书还特别适合学校或培训机构用来作为Android教学的标准教材,除了对理论知识的介绍外,书中的真实案例可以让读者更深刻地体验到企业的真实产品需求,让学习和实践联系得更加紧密。

本书不太适合那些已经俱备丰富理论和实战经验的开发者,本书不适合光说不练的开发者。

本书特色

本书遵循第一版的写作宗旨,通过本书的学习,让不懂Android开发的人系统地快速掌握Android开发的知识。书中内容的安排循序渐进、由浅到深,跟随本书的步调,一定可以学会Android开发。本书除了理论知识的介绍外,还加入很多实战经验技巧和实战案例剖析,让大家在学习的时候能理论结合实战,融会贯通,真正掌握Android开发技术和技巧。

本书内容

接下来,针对本书的内容组织和大家做个简短的说明。

第1章 Android 开发扫盲

本章主要给还不熟悉Android开发的读者做个扫盲,让大家对Android开发有一定的认知,了解整个Android行业和Android开发行业。

紧接着阐述了如何搭建标准的开发环境,认知 Android SDK 以及第一个Hello EoE 程序,以下各章主要内容介绍如下。

第2章 Android 开发环境搭建

本章主要介绍了搭建 Android 开发环境需要的条件,诸如系统要求、SDK、IDE 等需求,然后分别介绍了在Windows、Ubuntu 和Mac OS 上搭建开发环境的过程和步骤。总体来看,本章简单且重要,好的开始是继续前进的动力。

第3章 Android SDK 介绍

本章主要介绍了Android SDK 的相关内容,包括其文档解读,示例解读,以及相关API介绍,通过本章学习,可以比较清晰地把握Android SDK 的全貌,熟悉其提供的相关示例,以及附带的工具使用。

第4章 Hello EoE

本章演示了如何创建第一个“Hello EoE”项目,通过这个项目我们了解到如何快速构建一个Android的项目工程,以及如何对Android的项目进行调试。

通过上面3章的内容介绍,大致了解了Android环境的搭建和Android SDK,并通过一个真实的项目工程大致了解了Android项目结构。

第5章 Android 应用程序架构分析

本章主要对Android系统的体系架构进行了简单的分析,让大家能够基本了解Android系统的构成。

第6章 Activity

本章主要对Android中最重要的组件之一Activity进行了基本的讲解。在本章的最开始就已经说明了 Activity 对整个应用程序的重要性。所以,学好Activity可以说是开发Android应用程序必备基础技能之一,尤其是对Activity的生命周期及基本状态的了解也是非常重要的。

第7章 Intents&Intent Filters&Broadcast Receivers

本章为大家讲解了 Android 的灵魂 Intent,程序的跳转和数据的传递基本上都是靠它,另外,也讲解了Intent-Filter和BroadcastReceiver等基础概念。最后的小实例结合了Intent-Filter、BroadcastReceiver及Notification等知识点。

第8章 Service

本章主要讲了什么是 Service,以及Service 的两种形式和生命周期的基本理论知识,之后又结合一个小实例对 IntentService 和 Service 类进行了比较,知道使用 IntentService 来创建启动形式 Service 更为合适。用关于绑定形式Service 实例,进一步演示了如何绑定一个Service 并与之通信交互 。

第9章 Content Providers

本章主要介绍了 ContentProvider 及 ContentResolver的基本概念,并通过两个小实例演示了如何调用系统提供的数据,以及如何通过 ContentResolver调用自定义的ContentProvider。

第10章 用户界面

本章是分量比较重的章节,介绍了用户界面常用的一些布局和控件,并用实际代码演示了基本用法。重点介绍了最常用的线性布局(Linear Layout)和相对布局(RelativeLayout),演示了基本写法和展示了示例图。此外,还介绍了Listview、输入控件(Input Controls)、菜单(Menu)、活动栏(ActionBar)和通知(Notification)的用法。

第11章 线程&进程

在本章学习中,首先介绍了Android系统里线程、进程的基本概念,然后通过一个应用程序的运行过程,讲解了应用运行过程中进程、线程及各类组件的关系。

第12章 信息百宝箱—全面数据存储

在本章中,读者可以学习到SharedPreferances、流文件存储、面向对象的数据库的使用方法和SQLite数据库的使用方法,并且通过一个记事本的实例,完整地展示了SQLite数据库的增、删、改、查的操作方法。

第13章 Widget

本章主要学习了Android中Widget的基本概念、Widget的生命周期、如何设计出更好的Widget,并且和大家一起学习了一个eoeWiki的实例来加深对Widget整体的理解。

上面第5~13章这9章的内容构成了对Android主要知识点的阐述,熟练掌握这些内容后,再加以练习和实战就可以进行一般Android应用的开发。

第14章 网络通信和XML 解析

本章主要介绍了Android应用开发中最常见的网络通信的处理,以及网络传输的XML数据的解析方式,让大家以后在和网络打交道的时候更游刃有余。

第15章 灵活的应用

本章首先介绍了Android中常见的自定义组件的方法,然后介绍了一些关于片断布局的技巧,最后介绍了画布的技巧。希望通过本章的引导式学习让大家能在自己实际的工作中创建更加灵活的应用。

第16章 多设备适配

本章对Android应用中大家最关心的多屏幕、多语言、多版本问题进行了介绍,希望大家通过本章的学习能开发出适配性很好的应用。

第17章 开发好应用

本章简单介绍了如何开发好应用,用“如何更省电”这个最常见的话题来阐述开发思路,最后还介绍了一些需要特定硬件(如NFC)支持的功能是如何开发的。希望通过本章的学习,让大家能继续深入研究Android技术。

第18章 Android UI 设计规范

本章首先介绍了Android UI 的设计规范,进而告诉大家Android 中应该努力遵守的UI设计原则,最后通过3个典型例子介绍了Android中的UI设计规范的实战技巧。

上面第14~18章这5章的内容主要是为了打开大家的思路,让自己能修炼进阶。而接下来的是本书的实战部分,精选了6个真实的案例和大家分享,分别介绍如下。

第19章 综合案例一——图书信息查询

通过本章的学习,可以了解到RelativeLayout 的布局细节,如何通过Intent启动第三方应用,如何通过Intent传递对象,如何从网络下载数据,如何使用豆瓣图书API,如何解析JSON等。

第20章 综合案例二——eoeWiki 客户端

本章以eoeWiki客户端应用为例,以整个软件开发流程为讲解主线,为大家讲述了软件开发各个阶段我们需要做的事情及注意事项,并重点分析了 eoeWiki客户端中的功能模块:滑块特效、网络交互、JSON解析、数据与缓存等。通过本章的学习,我们应该对整个软件开发的流程及每个阶段有一个清楚的了解,并且对网络社交应用有一定的经验积累,可以顺利地进行其他类似社交应用的开发。

第21章 综合案例三——广告查查看看

本章实例是个很时髦的话题,主要讲解如何对加入了广告的应用进行检测分析,并选择性删除。其中也涉及一些基础的小知识,如各大广告平台预览、解析XML文件等。

第22章 综合案例四——手机信息查看小助手

本章以一个手机信息查看助手的应用分析为起点,讲述了如何获取和查看手机设备自身的一些信息,例如,系统信息、硬件配置信息、软件安装情况,以及一些运行时的信息。通过本章的学习,相信能使读者在硬件操作方面积累一定的经验 ,可以顺利地进入手机硬件相关的应用开发。

第23章 综合案例五——土地浏览器实例

在土地浏览器开发这一章中,首先讲到了为什么要开发浏览器,开发浏览器的意义。然后,一一列举了土地浏览器中的各项功能。接下来,根据列举的各项功能,分别详细讲解了各项功能的实现过程。

第24章 综合案例六——地图跟踪

本章我们以一个百度地图的示例程序,学习通过百度地图SDK 开发手机地图应用程序的过程,并讲解了百度地图SDK中主要功能API的用法。

如何阅读

本书建议的阅读方式是按顺序阅读,并且要仔细阅读,通过前面的理论学习后,一定要对书中的案例进行理解、思考和实践,并动手修改案例添加一些新的功能。

读完一遍后,建议将本书放在比较显眼的地方,在实际工作中遇到难题的时候有针对性地翻阅,希望你的每次阅读都会有新的收获。

读者交流专区

作为本书的内容答疑和技术支持,我们在eoeAndroid开发者社区为本书创建了专门的讨论区,如果你对书中内容有不是很理解的地方,都可以来这里提出,我们一定会认真解答。另外,我们还会在这里就书中的问题展开拓展讨论、错误修订和源程序下载等。

讨论专区及源程序下载网址:http://www.eoeandroid.com/group-35-1.html。

友情提醒

Android 还是一个新兴的平台,其截止到我们完稿,都还处在高速发展过程中,版本更新和使用方法都会或多或少发生变化,我们力求撰写的内容能跟上最新的版本,但是上市之后,如果再有新版本发布,我们会及时将需要修改或者完善的内容,包括源程序在“读者交流专区”发放出来,记住最新的源程序或者新版变化到社区中获取。

支持网站和社区:

http://www.eoeandroid.com;

http://www.eoe.cn。

第6章 Android 的核心——Activity

从本章你可以学到:

什么是Activity

掌握Activity的生命周期

掌握Activity四个基本状态

掌握Activity三个重要循环

掌握配置的改变

掌握Activity不同的加载模式

怎么保存和恢复Activity的状态

启动Activity并获取结果

6.1 什么是Activity

Activity是Android四大组件之一,也是Android中最基本的模块之一。在官网中是这样介绍Activity的。

几乎所有的的Activity都是用来与用户交互的,因此Activity主要关注于视图窗体的创建(你可以通过setContentView(View)方法来放置你的UI),而且Activity对于用户来说通常都表现为全屏的窗体,当然,它们也能以其他的方式呈现,比如浮动窗体。

通俗一点来讲,我们可以把手机比作一个浏览器,那么Activity就相当于一个网页。在Activity中,我们可以添加不同的View,并且可以对这些View做一些事件处理。例如,在Activity中添加button、checkbox 等元素。因此,Activity 的概念在某种程度上和网页的概念是相当类似的。网页对于一个完整的Web站点来说有多重要,Activity对Android应用程序就有多重要。

6.2 Activity的生命周期

Activity的重要性在Activity介绍中已经大概描述了,为了更好地使用Activity,接下来我们介绍一下Activity的生命周期。

在讲Activity生命周期之前,我们先看图6-1(Activity的生命周期)。

▲图6-1 Activity生命周期

从图6-1中我们可以看到Activity的生命周期其实就是由以下函数组成的。

public class Activity extends ApplicationContext{

protected void onCreate(Bundle savedInstanceState);

protected void onStart();

protected void onRestart();

protected void onResume();

protected void onPause();

protected void onStop();

protected void onDestroy();

}

通常情况下Activity生命周期的动作如下所示。

onCreate():该方法是在Activity第一次被创建的时候调用的。这个方法通常用来做一些常规的设置,比如创建视图,绑定数据到 list 等。这个方法还提供了一个 Bundle 对象来保存先前冻结的状态,当然,前提是你之前已经将你需要冻结的内容放到了 Bundle 中。之后总是会调用 onStart()方法,并且在调用了这个方法之后,是不能被系统意外杀死的。

onRestart():从名字就能看出,在Activity被停止后,如果需要重新启动,则会调用这个方法,之后会调用onStart()方法。

onStart():该方法在 Activity 将要对用户可见时调用,如果 Activity 将显示在前台,接着调用onResume(),如果Activity将变隐藏,则调用onStop()方法。不能被系统意外杀死。

onResume():该方法是在Activity将开始于用户交互时被调用的,这个时候的Activity在Activity栈中处于最顶部,之后总是调用onPause()方法。也不能被系统意外杀死。

onPause():该方法是在系统准备恢复其他Activity时调用,这个方法通常用来提交未保存变化的持久化数据,停止动画和其他可能消耗CPU的操作等。由于在这个方法返回之前,下一个Activity是无法被恢复的,所以这个方法的实现不宜做耗时的操作。如果调用了该方法之后,Activity又打算重新返回到前台,则会调用onResume()方法,如果Activity变得对用户不可见,则调用onStop()方法。在系统极端低内存的情况下可以被杀死。

onStop():该方法在Activity不再对用户可见时调用,因为其他Activity已经恢复并且正在覆盖当前Activity。这个可能发生在当一个新的Activity正在启动,而已经存在的Activity又被带到了这个Activity的前面,或者这个Activity正在被销毁。调用了这个方法后,可能会被系统意外地杀死。

onDestory():该方法是在 Activity 被销毁之前最后调用的一个方法,这个可能发生在 Activity被完成的时候。

小提示

上述提到的可能被系统意外杀死或者不能被杀死,是指Android系统在运行时,会在内存极端低下的情况下有选择性地杀死某些“不必要”进程以达到缓解内存不足的情况。

6.3 Activity的监控范围内的三个主要循环

Activity的“整个生命周期”是发生在第一次调用onCreate(Bundle)和唯一最后调用onDestroy()方法之间。一个Activity会在onCreate()方法中设置全局状态,并在onDestrory()方法中释放余下的资源。例如:Activity有一个运行在后台的线程用来从网络上下载数据,则这个线程可能在onCreate()方法中被创建,并在onDestroy()方法停止线程。

Activity的“显示生命周期”是发生在调用onStart()方法以及调用相对应的onStop()方法之间。这段期间,用户可以在屏幕上看到 Activity,尽管该 Activity 可能不在前面(可能隐藏被透明的Activity覆盖等)并与用户交互。在这两个方法中间你可以维护所需要的显示给用户的资源。例如:你可以在onStart()方法中注册一个BroadcastReceiver来检测影响你用户界面的改变,并当你的用户不在见到显示的东西时在onStop()方法中撤销该BroadcastReceiver。随着Activity对用户的可见和不可见状态的转变,onStart()方法和onStop()方法能被调用多次。

Activity 的“前台生命周期”(foreground lifetime 的意思就是当前Activity显示在屏幕上并且用户能与之交互的一个状态)发生在调用onResume方法以及相应的onPause方法之间。在这段期间,Activity处在其他Activity的前面并能与用户直接交互。Activity会经常在恢复和暂停的状态中转换。例如,当设备休眠时,当一个新的intent被传递到另一个Activity时。因此在这些方法中代码应该要相当轻量级。

6.4 Activity拥有四个基本的状态

活动中:如果Activity在屏幕前(即在栈的最顶部),它是可视的,可接受用户输入的。

暂停:如果Activity已经失去了焦点,但是仍然可见(即,一个非全屏或者透明的Activity在你的Activity的上方拥有焦点),它的状态是暂停。一个暂停状态下的Activity是完全活着的(它保留了所有状态和成员信息并仍然附加到视图管理器),但在系统极端低内存的情况下可以被杀死。

停止:如果一个Activity完全被另一个Activity遮住了,它的状态是停止的。它虽然仍然保存着所有状态和成员信息,但是,它不再对用户可见,所以它的窗口是隐藏的,这个状态下的Activity往往会在其他地方需要内存时被系统意外杀死。

待用:如果一个Activity处于暂停或者停止状态,系统可以让它完成,或者直接杀掉它的进程。当它再重新显示给用户时,它必须完全重启并恢复到以前的状态。

6.5 Task、栈以及加载模式

在Android应用程序中,应用程序中的Activity是可以启动其他程序的Activity的,例如,你在 A 程序中单击了某一串链接地址,应用会自动调用系统的浏览器帮你打开这个链接(如果你的系统中存在多个浏览器,则会打开多个并让你选择其中一个),虽然A程序和浏览器不属于同一个应用,但是你单击“回退”按钮后,依然可以回退到 A 程序中。像这种无缝的用户体验,主要得益于Android中的Task。

那什么是Task呢?通俗来讲,Task就是一组与用户交互并执行特定工作的Activity的集合。它们都根据被启动的顺序排列在栈中(我们可以称这个栈为“回退栈[back stack]”)。比如我们先启动了A 程序,依次调用了A、B、C3个Activity,之后又通过CActivity 启动了B程序的DActivity,最后又通过DActivity 回到了A 程序并依次退出结束,那么我们可以将由AB CDActivity实例组成的集合称为一个Task,而这些Activity的实例都会根据被启动的顺序存放在栈中(所以,我们讲的A、B、C、D实例组成的集合,不一定只有4个Activity实例,这主要依赖每个Activity的启动模式,后续我们会讲到)。

当用户在应用程序界面(Home界面)单击一个图标(或者Home界面的快捷方式)时,这个应用程序的Task就会启动,如果不存在这个程序的Task(即这个应用程序最近没有被用过),则会创建一个新的Task,并且该程序的“main”Activity会作为栈的根Activity存在。

当当前的Activity启动另一个Activity,新的Activity就会被压入栈的顶部并且得到焦点。上一个Activity仍然存在栈中,但是它停止活动了。当一个Activity停止,系统仍然会保存它的用户界面的当前状态。当用户单击“回退”按钮时,当前Activity就会从栈的顶部被弹出并且被销毁。而之前的Activity将会被恢复(之前的UI状态都将修复)。栈中的Activity是不会进行重新排序的,仅仅只是压入或者弹出栈(被当前 Activity 启动则压入栈,用户单击“回退”按钮离开则弹出)。栈的管理方法是典型的“后进先出”。具体如图6-2所示。

▲图6-2 栈的“后进先出”

比如我们首先启动了Activity1,这时栈中就只存在Activity1一个实例,当使用Activity1启动Activity2 时,Activity1 就被压在了栈下面,Activity2 则被压到了栈的顶部,当我们使用 Activity2再启动Activity3时,Activity2又被压了下去,Activity3则处于栈的顶部。这个时候整个栈中就有了Activity1,Activity2,Activity3三个实例了,并且Activity3处于最顶部,也是获得焦点的Activity。如果我们这个时候按一下“回退”按钮,系统则会将栈最顶部的Activity(即Activity3)弹出并销毁,这个时候Activity2又处于了栈的顶部,并获得了焦点,整个栈里就只有Activity2和Activity1了。

当用户不停地单击“回退”按钮,则在栈中的每一个Activity都会被弹出并恢复前一个,直到用户最后返回到了Home界面,当stack中的所有Activity不再存在了。该Task也就不再存在了。

其实大多数情况下,我们是没必要去关心Activity与Task是如何关联,怎样存在于“回退栈”中的。然而,如果你想不使用Activity的默认行为,比如你希望你的应用程序在启动一个Activity时创建一个新的Task,而不是直接放入当前的Task中,或者当你启动一个Activity,你希望将已经存在在栈中的 Activity 带到栈的顶部,而不是在栈中创建一个新的,或者你想当用户离开 Task 的时候,清除栈中的所有Activity,除了根Activity。

注意

大多数的应用我们不应该打断Activity和Task的默认行为,如果你确定需要为你的Activity修改默认的行为,请谨慎,并多测试可能产生的与用户预期相冲突的行为。

为了达到打断默认行为的效果,我们可以自己定义启动模式(launch Mode),这里有两种方法。

一是使用 manifest 文件。在 manifest 文件中通过指定 Activity 的 launchMode 属性来定义。launchMode属性支持四种不同的值(即四种不同的启动模式)。

1.standard模式

这是默认的模式,系统会在Task中创建新的Activity实例,并且这个Activity能被实例化多次,每个实例都能属于不同的Task,一个Task也能拥有多个实例。

2.singleTop模式

在这个模式下,如果一个 Activity 实例已经存在于当前 Task 的顶部,系统会通过调用它的onNewIntent方法发送intent请求调用已经存在于顶部的Activity实例,而不是创建一个新的Activity实例。Activity可以被实例化多次,每个实例也能属于不同的Task,一个Task也能有多个实例(但是只有当回退栈的顶部不是这个Activity已经存在的实例)。

例如,假设Task的回退栈已经存在根ActivityA,以及ActivityB、C、D(D在栈的顶部),当intent接收到类型D的Activity时,如果D是默认的standard模式,则会创建一个新的ActivityD,栈里的实例就变成了A、B、C、D、D。然而,如果D是singleTop模式,栈中已经存在的ActivityD就会通过onNewIntent方法接受到Intent请求,(因为D在栈的顶部),这个时候栈中仍然是A,B,C,D。然而,如果Intent接受到的Activity类型为B,则还是会创建一个新的B实例到栈中,即使它的加载模式是“singleTop”。

3.singleTask模式

系统会创建一个新的Task并将Activity作为新Task的根(root)实例化。然而,如果Activity的实例已经存在于一个其他的Task中,系统会通过onNewIntent方法发送Intent请求到已经存在的实例,而不是创建一个新的实例。在同一时间,Activity的实例只能有一个。

注意

即使 Activity 启动了一个新的 Task,当我们单击回退按钮时,还是会回到前一个Activity。

4.singleInstance模式

跟singleTask效果一样,不同的是,系统不会加载其他的Activity到包含了这个实例的Task中,Activity实例只有一个并且它是Task的唯一成员。之后被该Activity启动的其他Activity都会在不同的Task中启动。

举个例子,Android的浏览器程序通过指定Activity的singleTask模式声明它的Activity总是在它自己的Task中打开,这也意味着如果你的程序发送Intent去打开浏览器,浏览器的Activity和你程序的Activity不在同一个Task中,而是为浏览器创建一个新的Task,如果浏览器已经有一个Task运行在后台,那么它会被带到前台来处理这个新的Intent。

另外一种打断默认行为效果的方式则是通过Intent中的flag。

在使用 Intent 方式时,我们可以通过设置 Intent 的值为 FLAG_ACTIVITY_NEW_TASK,FLAG_ACTIVITY_SINGLE_TOP以及FLAG_ACTIVITY_CLEAR_TOP来达到我们需要的效果。

FLAG_ACTIVITY_NEW_TASK:它的效果和前面提到的singleTask启动模式的效果是一致的。

FLAG_ACTIVITY_SINGLE_TOP:它的效果和前面提到的singleTop启动模式的效果一致。

FLAG_ACTIVITY_CLEAR_TOP:如果要启动的 Activity 已经运行在当前 Task 中,那么不会再创建该 Activity 的新实例,而是所有在这个 Activity 实例上面的 Activity 都会被销毁,并通过onNewIntent方法启动该Activity(此时,该Activity已经处于栈的顶部)。之前提到的配置launchMode的方式,没有一个属性的效果跟此值的效果一致。

6.6 配置改变

如果设备的配置改变了(定义在Resource.Configuration类中),任何显示在界面上的东西都需要更新以适应配置。由于Activity是与用户交互的主要机制,所以它也包括一些处理配置改变的特殊支持。

除非你指定了,否则配置改变(比如改变屏幕方向,语言,输入设备等)会导致你当前的Activity会销毁,并调用相应的Activity生命周期进程函数onPause(),onStop()以及onDestroy()。如果这个Activity运行在前台或者对用户可见,一旦这个实例(Activity)的onDestroy()被调用后就会马上又创建一个新的该Activity实例,并且前一个Activity实例中的onSaveInstanceState(Bundle)方法中产生的savedInstanceState 也还存在。

这样做是因为任何程序资源,包括布局文件都能在任何配置值被改变的情况上被动地改变,因此唯一安全的处理配置改变的方式就是重新获取所有的资源,包括布局(layout),图片资源(drawables)以及字符资源(strings)。因为Activity必须知道怎样去保存自己的状态和重新创建自己的这种状态,所以根据新配置重新启动一个Activity是非常简便的方式。

当然,在某些特殊的情况下,我们可能希望在某些配置类型改变时绕过重新启动Activity来直接做某些应对配置值改变的情况。这个可以使用在 manifest 文件中配置的 Activity 的 android:configChanges 属性来做到。任何你在 manifest 中定义的配置类型,都会回调你当前 Activity 的onConfigurationChanged(Configuration)方法,而不是重新启动你的Activity。如果一个配置的改变涉及任何你不想处理的,这个Activity还是会被重新启动,而且onConfigurationChanged(Configuration)也不会被调用。

6.7 如何保存和恢复Activity状态

之前我们提到了Activity的生命周期,也稍微了解了onPause和onStop方法,在调用了这两个方法后,Activity暂停或者停止(界面可能直接被覆盖了),但是这个Activity的实例仍然存在于内存中,并且它的信息和状态数据都不会销毁,当Activity重新回到前台后,所有的这些信息和状态又会回到和以前一样。

但是,如果系统在内存不足的情况下调用了onPause或onStop方法,Activity可能会被系统销毁,这个时候,内存中是不会存在Activity实例的,如果该Activity再次回到前台,之前的信息和状态可能无法保存,页面也就无法根据这些信息和状态回到原来的样子。为了避免这种情况,Activity中提供了onSaveInstanceState方法,这个方法接收一个Bundle类型参数,我们可以将状态和数据保存到Bundle对象中,这样的话,就算Activity被系统销毁,只要用户重新启动Activity调用onCreate方法,我们就能在onCreate方法中得到Bundle对象,并根据这个对象中的数据将Activity恢复到之前的样子。

具体可以看以下代码。

@Override

protected void onCreate(Bundle savedInstanceState) {

// TODO Auto-generated method stub

super.onCreate(savedInstanceState);

savedInstanceState.get("preState");

}

@Override

protected void onSaveInstanceState(Bundle outState) {

// TODO Auto-generated method stub

super.onSaveInstanceState(outState);

outState.putString("preState", "eoe");

}

代码解释

我们在onSaveInstanceState方法中将eoe这个值以键为preState存入了outState这个Bundle对象,之后我们就能在onCreate方法中,通过savedInstanceState这个Bundle对象取得eoe这个值了。

注意

onSaveInstanceState方法并不一定会被调用,因为有些场景是不需要保存状态数据的,比如,当用户单击“后退”按钮的时候,因为用户已经明确要关闭当前Activity了。

其实,即使不覆写onSaveInstanceState方法,该方法依然会默认保存Activity的某些状态数据,比如 Activity 里各个 UI 控件的状态。Android 里几乎所有的 UI 控件都适当地实现了onSaveInstanceState方法,所以,当Activity被摧毁并重新恢复时,这些控件会自动保存和恢复状态。比如EditText控件会自动保存和恢复输入的数据,checkbox也会保存它是否已经选中的状态,当然,要做到这点你也需要给这些控件指定 ID,不然这个控件是不会自动进行数据和状态的保存与恢复的。

由于onSaveInstanceState方法不一定会被调用,所以,我们不适合在这个方法中保存持久化数据,例如向数据库中插入记录等,类似这种操作,应该放到 onPause 方法中进行(前面提过)。onSaveInstanceState方法其实只适合保存瞬时状态数据,比如某些成员变量等。

小知识

除了系统因为内存不足,会摧毁你处于暂停或停止状态的Activity之外,系统设置的改变也会导致Activity的摧毁和重建。这个我们在本章上面节点“配置改变”中提到过,所以,如果你想要测试你的程序恢复状态的能力,简单的旋转装置,让屏幕横竖屏切换是非常好的方式。

6.8 启动Activity并得到结果

在Activity中,你可以调用startActivity(Intent)方法被用来启动一个新的Activity,并将这个新的Activity置于Activity栈的最顶部。但是有时候,你却可能希望当一个Activity结束时从这个被结束的Activity中得到一个返回结果,例如,你可能启动了一个Activity让用户在联系人名单上选择一个人,当这个 Activity 结束时,它返回这个被选中的人给你。为了做到这个,你可以调用startActivityForResult(Intent,int),结果将会通过onActivityResult(int,int,Intent)方法返回。

当一个Activity退出时,它可以调用setResult(int)将数据返回到它的父类,当然,它也必须要提供一个结果代码,可以是标准的结果代码RESULT_CANCELED,RESULT_OK,或者任何其他自定义起始于RESULT_FIRST_USER值。另外,也可以返回一个带有你想要的附加数据的Intent。所有的这些信息会随着最初提供的整数标识符显示回父类的Activity.onActivityResult()方法中。

如果子 Activity 因为任何原因失败了(比如报错了),父 Activity 就会收到一个结果代码RESULT_CANCELED。

6.9 Activity小实例

在介绍完Activity相关基础内容后,现在我们来针对Activity开发一个简单的小实例。

这个实例指定了3个界面(Activity)。HelloWorldActivity界面有两个按钮,Button1和Button2 (见图6-3),Button 1 单击后会跳转到Activity B,而Activity B 简单地显示“This is Activity B,Welcome!”(见图6-4)。单击Button2 后则跳转到Activity C,ActivityC 界面有一个输入框以及一个“确定”按钮(见图6-5),当单击“确定”按钮之后,将关闭Activity C,并获取输入框中的内容,回传到HelloWorldActivity,并将回传的内容显示在Button2的下方,如图6-6所示。

▲图6-3 程序主界面
▲图6-4 单击Button1后进入Activity B
▲图6-5 单击Button 2后进入Activity C
▲图6-6 获取Activity C 的值显示在按钮下方

实例编程实现

第1步:新建一个Android项目(相信大家看完前面的章节已经知道如何去创建一个Android项目了,这里就不再赘述),并创建包名,3个Activity(全部继承自Activity类,并重写onCreate方法)以及3个布局文件。如图6-7所示。

▲图6-7 实例项目架构(基于Android 2.2)

第2步:打开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:id="@+id/button1"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:text="Button1"/>

<Button android:id="@+id/button2"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:text="Button2"/>

<TextView android:id="@+id/tvDisplay"

android:layout_width="fill_parent"

android:layout_height="wrap_content"/>

</LinearLayout>

代码解释

定义一个LinearLayout(线性布局),并在这个布局中定义两个Button和一个TextView控件。两个Button的长和宽都是wrap_content(包裹住内容)就可以了。TextView的宽度则是fill_parent (填满父控件)。

注意

上述代码中的Button控件的android:text属性的值理应放置在项目结构中values文件夹下strings.xml文件中定义,并采用@string/xxx来引用对应的值。由于这里主要是介绍Activity,所以就直接将值写在了属性后面。

第3步:打开activityb.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" >

<TextView

android:id="@+id/tvActivityb"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:text="This is ActivityB,Welcome!"/>

</LinearLayout>

代码解释

这里只是定义了一个线性布局,并在布局中定义了一个 Textview 控件,并显示“This isActivityB,Welcome”字样。

第4步:打开activityc.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" >

<EditText

android:id="@+id/etActivityc"

android:layout_width="fill_parent"

android:layout_height="wrap_content"/>

<Button android:id="@+id/buttonc1"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:text="确定"/>

</LinearLayout>

代码解释

定义一个线性布局,并在布局中定义一个EditText和Button控件。

第5步:打开HelloWorldActivity.java文件,找到onCreate方法,编写如下代码。

//将布局文件设置为main.xml

setContentView(R.layout.main);

//得到两个Button控件

Button mButton1 = (Button)findViewById(R.id.button1);

Button mButton2 = (Button)findViewById(R.id.button2);

//为Button1绑定单击事件

mButton1.setOnClickListener(new OnClickListener() {

@Override

public void onClick(View v) {

// TODO Auto-generated method stub

//使用intent启动ActivityB

Intent _intent =

new Intent(HelloWorldActivity.this, ActivityB.class);

startActivity(_intent);

}

});

代码解释

HelloWorldActivity首先将main.xml文件设置为布局文件,之后通过findViewById方法得到两个 Button 控件,由于先打算实现单击 Button1跳转到 ActivityB 这个功能,我们暂时只为 Button1绑定单击事件。

Intent _intent =

new Intent(HelloWorldActivity.this, ActivityC.class)语句新建了一个Intent,这个Intent描述了从HelloWorldActivity跳转到ActivityB的一次操作。

startActivity(_intent)语句用来启动_intent,由_intent描述的这次操作才正式执行。

小知识

什么是Intent?在Android官方文档中是这么定义的,Intent是一次即将操作的抽象描述,现在理解这个定义还有些抽象,但是看完本书就会对这个定义理解了。在Android当中,一共用到了3种Intent,现在使用的是第一种,它的作用就是启动一个新的Activity并且可以携带数据。还有两种分别如下:

(1)通过Intent启动一个服务(Service)。

(2)通过Intent广播事件。

以上两种我们会在后续的章节讲到,这里不再细述。

第6步:打开ActivityB.java文件,这个文件中没有复杂的代码,只是将activityb.xml文件设置为ActivityB的布局文件。具体代码如下。

@Override

public void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activityb);

}

通过这一步之后,从HelloWorldActivity跳转到ActivityB就已经完全实现了。接下来就要实现稍微复杂一点的从 HelloWorldActivity 跳转到 ActivityC,并得到返回值显示在 HelloWorldActivity的逻辑了。

第7步:重新打开HelloWorldActivity.java并为Button2添加监听事件,具体代码如下。

mButton2.setOnClickListener(new OnClickListener() {

@Override

public void onClick(View v) {

// TODO Auto-generated method stub

Intent _intent =

new Intent(HelloWorldActivity.this, ActivityC.class);

startActivityForResult(_intent, 100);

}

});

代码解释

Intent_intent=new Intent(HelloWorldActivity.this,ActivityC.class)跟Button1一致,也是描述一次从 HelloWorldActivity 到 ActivityC 的跳转操作。不同的是,这次启动时,使用的方法是startActivityForResult()方法。

上述代码中的startActivityForResult方法有两个参数,第一个是intent对象,还有一个则是“请求码”(requestCode),这个请求码是用来区分不同的请求。

例如,AActivity使用了startActivityForResult 方法启动了BActivity以及CActivity,在回调的时候,AActivity中的回调方法只有一个,这样,我们就能够根据不同的requestCode在不同的时机只取BActivity或CActivity返回的值。

小知识

startActivity与startActivityForResult的区别。

startActivity在启动了其他Activity之后是不会再回调回来的,相当于启动者与被启动者在启动完毕之后是没有关系的。

startActivityForResult在启动了其他Activity之后是有回调的,也就是说启动者与被启动者在启动完毕之后依然是有关系的。

第8 步:前面我们已经在 HelloWorldActivity 中为 Button2 添加了事件并使用startActivityForResult来启动ActivityC,现在我们看看ActivityC又需要做些什么呢?关键代码如下所示:

//设置activityc.xml为布局文件

setContentView(R.layout.activityc);

//得到Button实例

Button button1 = (Button)findViewById(R.id.buttonc1);

button1.setOnClickListener(new OnClickListener() {

@Override

public void onClick(View v) {

// TODO Auto-generated method stub

//实例化一个intent对象

Intent data = new Intent();

//获取EditText实例

EditText editText = (EditText)findViewById(R.id.etActivityc);

//得到EditText的值

String val = editText.getText().toString();

//将EditText的值存到intent对象中(以键值对的形式)

data.putExtra("helloworld", val);

//调用setResult方法,将intent对象(data)传回父Activity

setResult(Activity.RESULT_OK, data);

//关闭当前Activity

finish();

}

});

代码解释

从上面的代码注释中也能了解到,先是获取了 EditText 这个控件对象,并使用editText.getText().toString()得到EditText中输入的值,再通过intent的putExtra方法将获取的值以键值对的形式存入Intent中,之后调用setResult方法将成功的状态码以及intent对象传到父Activity (HelloWorldActivity)中。

小知识1

Intent在传递数据时提供了putExtra和对应的getExtra方法来实现存值与取值。而这里的put和get方法其实和Bundle的put,get方法是一一对应的。在Intent类中有一个Bundle的mExtras成员变量,所有的putExtra和getExtra方法实际上都是调用mExtras对象的put和get方法进行存取。所以,在正常情况下,传递数据可以直接使用intent的putExtra和getExtra方法即可,无需再创建一个Bundle对象。

小知识2

Bundle类型。这里简单介绍一下,Bundle是一个类型安全的容器,它的实现就是对HashMap做了一层封装。对于HashMap来说,任何键值对都可以存进去,值可以是任何的Java对象。但是对于Bundle来说,同样是存键值对,但是这个值只能是基本类型,或者基本类型数组,比如int,byte,boolean,char等。

如果大家对Bundle的概念还是有点模糊,没关系,在以后的学习过程中会慢慢了解,这里只需要知道,我们可以使用Intent对象的putExtra和getExtra方法来存取数据就行了。

第9步:在完成了Activity C.java文件的代码编写后,我们接着再继续打开HelloWorldActivity文件,最开始,我们在HelloWorldActivity 中实现了Button2 的事件,这个事件启动了对Activity C的调用,而在Activity C中我们刚刚也得到了一个EditText对象的值并通过setResult 方法回传到了父 Activity(HelloworldActivity),那么现在我们就需要在 HelloworldActivity 中来实现我们的回调函数了。具体代码如下。

@Override

protected void onActivityResult(int requestCode,int resultCode,

Intent data) {

super.onActivityResult(requestCode, resultCode, data);

if(requestCode == 100 && resultCode == Activity.RESULT_OK){

String val = data.getExtras().getString("helloworld");

TextView textView = (TextView)findViewById(R.id.tvDisplay);

textView.setText("来自ActivityC的值 :"+ val);

}

}

代码解释

if(requestCode==100 &&resultCode==Activity.RESULT_OK)这句代码是判断requestCode是不是等于当初你在startActivityForResult方法中设置的requestCode,并且ActivityC返回的resultCode是不是等于RESULT_OK,如果是,则通过data.getExtras().getString("helloworld")获取ActivityC中通过putExtra方法存的值。得到值之后,再获取main.xml布局文件中的TextView控件,并将值赋给它显示出来。

第10步:这也是最容易被忽视的一步,我们所有的 Activity 都必须在 Androidmanifest.xml 文件中进行注册,如果不注册,程序将会出错。具体注册代码如下。

<activity android:name=".ActivityB"/>

<activity android:name=".ActivityC"/>

注册完毕之后,整个实例就完成了,赶紧运行试试看吧!

6.10 本章小结

本章主要对Android中最重要的组件之一Activity进行了基本的讲解。在本章的最开始就已经说明了Activity对整个应用程序的重要性,所以学好Activity可以说是开发Android应用程序必备基础技能之一,尤其是对Activity的生命周期以及基本状态的了解,掌握了这些,在开发应用时,你就能游刃有余地把握每个Activity不同时期的不同状态,从而做出最合理的操作。最后又补充了一个Activity的小实例,希望大家能跟着本书动手编写,因为只有多写才能真正学好Android。

后记

终于,完稿了~

可以在这里和大家分享一下本书的一些故事了,静静的~

关于第一版

清晰记得2008年10月笔者离开深圳只身来到北京,和靳岩策划并撰写本书第一版的情形。那个时间段Android还是移动互联网的新秀,并不为多少人熟知,国内关于Android技术的文档和资料很少,懂Android开发的更是少的可怜。我们觉得Android是个非常棒的系统,也非常确信Android之后会有不俗的表现,并深知开发者在整个Android生态圈应该扮演一个非常关键的角色。为了让更多的人学会Android 开发,我们基于当时最新的1.6 版Android SDK 撰写了国内第一本Android技术书籍。

2009年4月,在人民邮电出版社出版的时候正好Android被国内第一批嗅觉敏锐的开发者接受,越来越多Java阵营开发者开始转向Android开发者阵营,而本书确实让很多人快速了解了Android开发知识,成为Android第一批的开发者。

而后出版社的编辑和市场部人员时不时地告诉我那本书卖得很好,一次又一次加印,甚至一度成为IT界最畅销的图书之一,这些成绩都让我们觉得非常兴奋。而最欣慰的是Android果然不负众望站在了智能手机领军地位,而这批Android开发者也逐渐成为各个公司Android技术的带头人。

随着时间的推移,我们也一头扎进了移动互联网创业浪潮,创办的 eoeAndroid.com 社区成为国内Android开发者活动的大本营,然后接着创办了eoeMobile公司。随着公司的发展和规模的扩大,尤其是创业公司,时间都不是自己的,所以也一直没有找到机会更新这本书。

所以,后来出版社再次告诉我本书又加印的时候我反而慢慢有点愧疚的感觉,我甚至一度劝读者别买这本书,因为第一版真的有点老了,怕误导了读者。从2009年到2012年,差不多3年多的时间,Android SDK 也从那时的1.5、1.6版本发展到现在的4.x 版本,书内有不少内容真的有点旧了。但是出版社还是时常反映还有很多人在买这本书,也多次询问我们是否有可能找时间更新一版。

关于第二版

其实曾经动过几次想更新一版的念头,但都因为时间问题没能最终落实,直到2012年9月出版社再次反馈说书又再次加印了,我终于“狠下心”着手策划本书的第二版。欣慰的是现在终于可以落笔写下最后一些文字,也就是第二版顺利撰写完成,即将上市和各位见面了。

回顾这本书的策划和撰写过程还是很有意思的,2012年9月份我决定更新本书第二版后,开始想怎么组织和策划,最开始想在第一版的基础上稍微调整,后来打算按照第一版的框架完善,而最终决定的时候则仅仅参考了第一版的结构,从头到尾都重新策划和组织了,基本没有用第一版稿件中的文字和代码。第二版所有的大纲、章节、文字、图片和代码都是重新选择和撰写的。如果问这一版和第一版还有什么关联的话,那应该就是这两个版本都是我策划的,这两版都是为了帮助不了解Android开发的人快速而又系统地掌握Android开发技术。

策划完大纲,要开始撰写内容,如果仅仅靠我一个人来写的话估计到明年的这个时候都不一定能写完,幸运的是有 eoeAndroid 社区,在社区中认识了很多技术非常棒的朋友,于是我在社区召集了几名同伴一起撰写,先后加入写作的有 kris、hexer、haoliuyou、River、Vincent4J、huaxiannv和cailiang。

多人撰写的好处是可以发挥每个人的长处,将每个知识点都写得特别详细,也可以并行撰写而大大加快速度,但也可能存在每个人的写作风格不同的问题,每个人写的知识点涉及其他知识点的时候会有些许问题或断链。幸运的是这些我们之前都处理过,开始写作前通过很多规范来统一每个人的风格,也通过在线IM和Dropbox及时沟通保持信息的同步,最后还会进行统一的校审,总体下来,效果很不错,感谢所有参与写作的各位。

相比第一版,本次更新我们不仅完善了第一版出版后大家反馈的不足和问题,还从结构上做了非常大的调整,我们力求把本书写得让我们自己和大家都满意,让本书成为每一个进入Android开发阵营读者的标准教材,也希望有更多学校和培训学校能采取本书作为学习教材。

致谢

写书是很消耗精力的苦差事,除了要消耗脑力策划、组织和撰写有价值的内容外,还要选择案例,熬夜调试程序,力求把每一页都做到尽善尽美,不留遗憾,所以,认真写本书是非常辛苦的。

本书的撰写由eoe社区中的会员完成,首先要感谢eoeAnroid社区中的所有的朋友们,是你们的督促和努力,让Android的明天更美好,让我们大家对Android的未来充满信心。

再要感谢参与本书撰写的朋友们,他们是kris、hexer、haoliuyou、River、Vincent4J、huaxiannv和 cailiang,谢谢大家牺牲自己的休息时间为 Android 发展做出的贡献,是大家的努力让这本书如期而至,摆在了大家的面前。

接着要感谢为本书的出版做了辛苦工作的张涛编辑、李大微编辑、封面美术编辑等工作人员,是大家的努力保证了本书的顺利出版。

最后感谢我的太太Tina,你是我的动力!

本书写完是一个小小的里程碑,eoe社区藏龙卧虎,而我们这个图书撰写小team还是很有战斗力的,来eoe社区大家可以看到我们活跃的身影,以后我们还会给大家带来更多有价值的东西,敬请期待!

天亮了,早安。

By Iceskysl@eoe

于北京

图书在版编目(CIP)数据

Android 开发入门与实战/eoe移动开发者社区组编.--2版.--北京:人民邮电出版社,2013.6

ISBN 978-7-115-31464-2

Ⅰ.①A… Ⅱ.①e… Ⅲ.①移动终端—应用程序—程序设计 Ⅳ.①TN929.53

中国版本图书馆CIP数据核字(2013)第065975号

内容提要

本书遵循第一版的写作宗旨,通过本书的学习,让不懂Android开发的人系统地快速掌握Android开发的知识。本书主要内容为:Android 开发环境搭建、Android SDK 介绍、Android 应用程序结构剖析,并对Android 中最重要的组件Activity、Intents&Intent Filters&Broadcastreceivers、Intent、Service、ContentProviders 进行了详细的讲解;然后对线程&进程、数据存储、Widget、网络通信和XML解析、多设备适配、Android UI Design(设计规范)等核心技术和读者关心的流行技术结合实例进行了详细讲解;最后精选了6个真实的案例,如图书信息查询、eoeWiki客户端、广告查查看看、手机信息小助手、土地浏览器、地图跟踪,让读者把各种技术贯穿起来,达到学以致用的目的。

书中内容的安排循序渐进、由浅到深,跟随本书的步调,一定可以学会Android开发。本书除了理论知识的介绍外,还加入很多实战经验技巧和实战案例剖析,让大家在学习的时候能理论结合实战,融会贯通,真正掌握Android的开发技术。

◆组编 eoe移动开发者社区

编著 姚尚朗 靳岩 等

责任编辑 张涛

责任印制 焦志炜

◆人民邮电出版社出版发行  北京市崇文区夕照寺街14号

邮编 100061  电子邮件 315@ptpress.com.cn

网址 http://www.ptpress.com.cn

北京昌平百善印刷厂印刷

◆开本:800×1000 1/16

印张:24.75

字数:595千字  2013年6月第2版

印数:19001-22500册  2013年6月北京第1次印刷

定价:59.00元

读者服务热线:(010)67132692 印装质量热线:(010)67129223

反盗版热线:(010)67171154

广告经营许可证:京崇工商广字第0021号

相关图书

Android App开发入门与实战
Android App开发入门与实战
Kotlin入门与实战
Kotlin入门与实战
Android 并发开发
Android 并发开发
Android APP开发实战——从规划到上线全程详解
Android APP开发实战——从规划到上线全程详解
Android应用案例开发大全( 第4版)
Android应用案例开发大全( 第4版)
深入理解Android内核设计思想(第2版)(上下册)
深入理解Android内核设计思想(第2版)(上下册)

相关文章

相关课程