第一行代码:Android(第2版)

郭霖

1.1 了解全貌——Android王国简介

  • Linux内核层、系统运行库层、应用框架层和应用层。
  • Android系统是基于Linux内核的,这一层为Android设备的各种硬件提供了底层的驱动,如显示驱动、音频驱动、照相机驱动、蓝牙驱动、Wi-Fi驱动、电源管理等。
  • 2014年Google I/O大会上,谷歌推出了号称史上版本改动最大的Android 5.0系统,其中使用ART运行环境替代了Dalvik虚拟机,大大提升了应用的运行速度,还提出了Material Design的概念来优化应用的界面设计。
  • Android系统四大组件分别是活动(Activity)、服务(Service)、广播接收器(Broadcast Receiver)和内容提供器(Content Provider)。

1.3 创建你的第一个Android项目

  • 如果你的项目中使用到了第三方jar包,就需要把这些jar包都放在libs目录下,放在这个目录下的jar包都会被自动添加到构建路径里去。
  • 这是你整个Android项目的配置文件,你在程序中定义的所有四大组件都需要在这个文件里注册,另外还可以在这个文件中给应用程序添加权限声明。
  • 这个文件用于指定项目代码的混淆规则,当代码开发完成后打成安装包文件,如果不希望代码被别人破解,通常会将代码进行混淆,从而让破解者难以阅读。
  • 表示HelloWorldActivity是这个项目的主活动,在手机上点击应用图标,首先启动的就是这个活动。
  • Activity是Android系统提供的一个活动基类,我们项目中所有的活动都必须继承它或者它的子类才能拥有活动的特性(AppCompatActivity是Activity的子类)
  • 所有以drawable开头的文件夹都是用来放图片的,所有以mipmap开头的文件夹都是用来放应用图标的,所有以values开头的文件夹都是用来放字符串、样式、颜色等配置的,layout文件夹是用来放布局文件的。
  • 在制作程序的时候最好能够给同一张图片提供几个不同分辨率的版本,分别放在这些文件夹下,然后当程序运行的时候,会自动根据当前运行设备分辨率的高低选择加载哪个文件夹下的图片
  • ❑ 在代码中通过R.string.app_name可以获得该字符串的引用。❑ 在XML中通过@string/app_name可以获得该字符串的引用。
  • Gradle是一个非常先进的项目构建工具,它使用了一种基于Groovy的领域特定语言(DSL)来声明项目设置,摒弃了传统基于XML(如Ant和Maven)的各种烦琐配置。
  • 那么这个jcenter是什么意思呢?其实它是一个代码托管仓库,很多Android开源项目都会选择将代码托管到jcenter上,声明了这行配置之后,我们就可以在项目中轻松引用任何jcenter上的开源项目了。
  • 一般有两种值可选:com.android.application表示这是一个应用程序模块,com.android.library表示这是一个库模块。
  • applicationId用于指定项目的包名
  • buildTypes闭包中用于指定生成安装文件的相关配置,通常只会有两个子闭包,一个是debug,一个是release。
  • minifyEnabled用于指定是否对项目的代码进行混淆,true表示混淆,false表示不混淆。
  • Android Studio项目一共有3种依赖方式:本地依赖、库依赖和远程依赖

1.4 前行必备——掌握日志工具的使用

  • 第一个参数是tag,一般传入当前的类名就好,主要用于对打印信息进行过滤;第二个参数是msg,即想要打印的具体的内容。
  • 比如日志打印不可控制、打印时间无法确定、不能添加过滤器、日志没有级别区分……
  • 我们在onCreate()方法的外面输入logt,然后按下Tab键,这时就会以当前的类名作为值自动生成一个TAG常量

2.1 活动是什么

  • 活动(Activity)是最容易吸引用户的地方,它是一种可以包含用户界面的组件,主要用于和用户进行交互。

2.2 活动的基本用法

  • Generate Layout File表示会自动为FirstActivity创建一个对应的布局文件
  • 勾选Backwards Compatibility表示会为项目启用向下兼容的模式
  • 项目中的任何活动都应该重写Activity的onCreate()方法
  • Android程序的设计讲究逻辑和视图分离,最好每一个活动都能对应一个布局,布局就是用来显示界面内容的,
  • 如果你需要在XML中引用一个id,就使用@id/id_name这种语法,而如果你需要在XML中定义一个id,则要使用@+id/id_name这种语法
  • wrap_content表示当前元素的高度只要能刚好包含里面的内容就行
  • 调用了setContentView()方法来给当前的活动加载一个布局
  • 项目中添加的任何资源都会在R文件中生成一个相应的资源id
  • 所有的活动都要在AndroidManifest.xml中进行注册才能生效
  • 活动的注册声明要放在标签内,这里是通过标签来对活动进行注册的。
  • 配置主活动的方法其实在第1章中已经介绍过了,就是在标签的内部加入标签,并在这个标签里添加这两句声明即可。
  • 如果你的应用程序中没有声明任何一个活动作为主活动,这个程序仍然是可以正常安装的,只是你无法在启动器中看到或者打开这个程序。这种程序一般都是作为第三方服务供其他应用在内部进行调用的,如支付宝快捷支付服务。
  • Toast是Android系统提供的一种非常好的提醒方式
  • findViewById()方法获取到在布局文件中定义的元素
  • findViewById()方法返回的是一个View对象,我们需要向下转型将它转成Button对象
  • 通过getMenuInflater()方法能够得到MenuInflater对象,再调用它的inflate()方法就可以给当前活动创建菜单了。
  • Activity类提供了一个finish()方法,我们在活动中调用一下这个方法就可以销毁当前活动了。

2.3 使用Intent在活动之间穿梭

  • Intent是Android程序中各组件之间进行交互的一种重要方式,它不仅可以指明当前组件想要执行的动作,还可以在不同组件之间传递数据。Intent一般可被用于启动活动、启动服务以及发送广播等场景
  • 显式Intent和隐式Intent
  • 第一个参数Context要求提供一个启动活动的上下文,第二个参数Class则是指定想要启动的目标活动,通过这个构造函数就可以构建出Intent的“意图
  • 指定了一系列更为抽象的action和category等信息,然后交由系统去分析这个Intent,并帮我们找出合适的活动去启动。
  • 通过在标签下配置的内容,可以指定当前活动能够响应的action和category
  • 只有中的内容同时能够匹配上Intent中指定的action和category时,这个活动才能响应该Intent。
  • android.intent.category.DEFAULT是一种默认的category,在调用startActivity()方法的时候会自动将这个category添加到Intent中。
  • 每个Intent中只能指定一个action,但却能指定多个category
  • 使用隐式Intent,我们不仅可以启动自己程序内的活动,还可以启动其他程序的活动,这使得Android多个应用程序之间的功能共享成为了可能。
  • 然后通过Uri.parse()方法,将一个网址字符串解析成一个Uri对象,再调用Intent的setData()方法将这个Uri对象传递进去。
  • 它接收一个Uri对象,主要用于指定当前Intent正在操作的数据,而这些数据通常都是以字符串的形式传入到Uri.parse()方法中解析产生的。
  • 只有标签中指定的内容和Intent中携带的Data完全一致时,当前活动才能够响应该Intent。
  • Intent中提供了一系列putExtra()方法的重载,可以把我们想要传递的数据暂存在Intent中,启动了另一个活动后,只需要把这些数据再从Intent中取出就可以了
  • 首先可以通过getIntent()方法获取到用于启动SecondActivity的Intent
  • startActivityForResult()方法也是用于启动活动的,但这个方法期望在活动销毁的时候能够返回一个结果给上一个活动
  • 在SecondActivity被销毁之后会回调上一个活动的onActivityResult()方法
  • 由于在一个活动中有可能调用startActivityForResult()方法去启动很多不同的活动,每一个活动返回的数据都会回调到onActivityResult()这个方法中,因此我们首先要做的就是通过检查requestCode的值来判断数据来源

2.4 活动的生命周期

  • 其实Android是使用任务(Task)来管理活动的,一个任务就是一组存放在栈里的活动的集合,这个栈也被称作返回栈(Back Stack)。
  • 当一个活动不再处于栈顶位置,但仍然可见时,这时活动就进入了暂停状态
  • 当一个活动不再处于栈顶位置,并且完全不可见的时候,就进入了停止状态
  • 当一个活动从返回栈中移除后就变成了销毁状态。
  • onStart()。这个方法在活动由不可见变为可见的时候调用。
  • 前台生存期。活动在onResume()方法和onPause()方法之间所经历的就是前台生存期。
  • 而这里@style/Theme.AppCompat.Dialog则毫无疑问是让DialogActivity使用对话框式的主题。
  • Activity中还提供了一个onSaveInstanceState()回调方法,这个方法可以保证在活动被回收之前一定会被调用,因此我们可以通过这个方法来解决活动被回收时临时数据得不到保存的问题。
  • Intent还可以结合Bundle一起用于传递数据,首先可以把需要传递的数据都保存在Bundle对象中,然后再将Bundle对象存放在Intent里

2.5 活动的启动模式

  • 启动模式一共有4种,分别是standard、singleTop、singleTask和singleInstance,可以在AndroidManifest.xml中通过给标签指定android:launchMode属性来选择启动模式。
  • 对于使用standard模式的活动,系统不会在乎这个活动是否已经在返回栈中存在,每次启动都会创建该活动的一个新的实例。
  • 在启动活动时如果发现返回栈的栈顶已经是该活动,则认为可以直接使用它,不会再创建新的活动实例。
  • 不过当FirstActivity并未处于栈顶位置时,这时再启动FirstActivity,还是会创建新的实例的。
  • 当活动的启动模式指定为singleTask,每次启动该活动时系统首先会在返回栈中检查是否存在该活动的实例,如果发现已经存在则直接使用该实例,并把在这个活动之上的所有活动统统出栈,如果没有发现就会创建一个新的活动实例。
  • 指定为singleInstance模式的活动会启用一个新的返回栈来管理这个活动
  • 使用singleInstance模式就可以解决这个问题,在这种模式下会有一个单独的返回栈来管理这个活动,不管是哪个应用程序来访问这个活动,都共用的同一个返回栈,也就解决了共享活动实例的问题。

2.6 活动的最佳实践

  • killProcess()方法用于杀掉一个进程,它接收一个进程id参数,我们可以通过myPid()方法来获得当前程序的进程id。需要注意的是,killProcess()方法只能用于杀掉当前程序的进程,我们不能使用这个方法去杀掉其他程序。

3.2 常用控件的使用方法

  • match_parent表示让当前控件的大小和父布局的大小一样,也就是由父布局来决定当前控件的大小。wrap_content表示让当前控件的大小能够刚好包含住里面的内容,也就是由控件内容决定当前控件的大小。
  • 我们使用android:gravity来指定文字的对齐方式,可选值有top、bottom、left、right、center等,可以用“|”来同时指定多个值
  • 如果你不喜欢使用匿名类的方式来注册监听器,也可以使用实现接口的方式来进行注册
  • Android控件的可见属性。所有的Android控件都具有这个属性,可以通过android:visibility进行指定,可选值有3种:visible、invisible和gone。

3.3 详解4种基本布局

  • android:gravity用于指定文字在控件中的对齐方式
  • 当LinearLayout的排列方向是horizontal时,只有垂直方向上的对齐方式才会生效
  • android:layout_weight。这个属性允许我们使用比例的方式来指定控件的大小,
  • dp是Android中用于指定控件大小、间距等属性的单位
  • 系统会先把LinearLayout下所有控件指定的layout_weight值相加,得到一个总值,然后每个控件所占大小的比例就是用该控件的layout_weight值除以刚才算出的总值。
  • android:layout_alignLeft表示让一个控件的左边缘和另一个控件的左边缘对齐

3.4 系统控件不够用?创建自定义控件

  • 我们所用的所有控件都是直接或间接继承自View的,所用的所有布局都是直接或间接继承自ViewGroup的
  • 通过LayoutInflater的from()方法可以构建出一个LayoutInflater对象,然后调用inflate()方法就可以动态加载一个布局文件

3.5 最常用和最难用的控件——ListView

  • 数组中的数据是无法直接传递给ListView的,我们还需要借助适配器来完成
  • 我们使用了android.R.layout.simple_list_item_1作为ListView子项布局的id,这是一个Android内置的布局文件,里面只有一个TextView,可用于简单地显示一段文本。
  • 最后,还需要调用ListView的setAdapter()方法,将构建好的适配器对象传递进去,这样ListView和数据之间的关联就建立完成了。
  • 在getView()方法中,首先通过getItem()方法得到当前项的Fruit实例,然后使用LayoutInflater来为这个子项加载我们传入的布局。
  • 第三个参数指定成false,表示只让我们在父布局中声明的layout属性生效,但不会为这个View添加父布局,因为一旦View有了父布局之后,它就不能再添加到ListView中了
  • getView()方法中还有一个convertView参数,这个参数用于将之前加载好的布局进行缓存,以便之后可以进行重用

3.6 更强大的滚动控件——RecyclerView

  • ListView的布局排列是由自身去管理的,而RecyclerView则将这个工作交给了LayoutManager, LayoutManager中制定了一套可扩展的布局排列接口,子类只要按照接口的规范来实现,就能定制出各种不同排列方式的布局了。
  • GridLayoutManager可以用于实现网格布局,StaggeredGridLayoutManager可以用于实现瀑布流布局

4.1 碎片是什么

  • 碎片(Fragment)是一种可以嵌入在活动当中的UI片段

4.2 碎片的使用方式

  • 动态添加碎片主要分为5步。(1) 创建待添加的碎片实例。(2) 获取FragmentManager,在活动中可以直接通过调用getSupportFragmentManager()方法得到。(3) 开启一个事务,通过调用beginTransaction()方法开启。(4) 向容器内添加或替换碎片,一般使用replace()方法实现,需要传入容器的id和待添加的碎片实例。(5) 提交事务,调用commit()方法来完成。
  • 在每个碎片中都可以通过调用getActivity()方法来得到和当前碎片相关联的活动实例

5.1 广播机制简介

  • Android中的每个应用程序都可以对自己感兴趣的广播进行注册,这样该程序就只会接收到自己所关心的广播内容,这些广播可能是来自于系统的,也可能是来自于其他应用程序的
  • 标准广播(Normal broadcasts)是一种完全异步执行的广播,在广播发出之后,所有的广播接收器几乎都会在同一时刻接收到这条广播消息,因此它们之间没有任何先后顺序可言。这种广播的效率
  • 有序广播(Ordered broadcasts)则是一种同步执行的广播,在广播发出之后,同一时刻只会有一个广播接收器能够收到这条广播消息,当这个广播接收器中的逻辑执行完毕后,广播才会继续传递

5.2 接收系统广播

  • 注册广播的方式一般有两种,在代码中注册和在AndroidManifest.xml中注册,其中前者也被称为动态注册,后者也被称为静态注册。
  • 不要在onReceive()方法中添加过多的逻辑或者进行任何的耗时操作,因为在广播接收器中是不允许开启线程的,当onReceive()方法运行了较长时间而没有结束时,程序就会报错。

5.3 发送自定义广播

  • abortBroadcast()方法,就表示将这条广播截断

5.4 使用本地广播

  • 本地广播是无法通过静态注册的方式来接收的

5.5 广播的最佳实践——实现强制下线功能

  • 这是因为我们始终需要保证只有处于栈顶的活动才能接收到这条强制下线广播,非栈顶的活动不应该也没有必要去接收这条广播,所以写在onResume()和onPause()方法里就可以很好地解决这个问题,当一个活动失去栈顶位置时就会自动取消广播接收器的注册。

6.1 持久化技术简介

  • 文件存储、SharedPreferences存储以及数据库存储

6.2 文件存储

  • Context类中提供了一个openFileOutput()方法,可以用于将数据存储到指定的文件中

6.3 SharedPreferences存储

  • 2.Activity类中的getPreferences()方法
  • 3.PreferenceManager类中的getDefaultSharedPreferences()方法

6.4 SQLite数据库存储

  • integer表示整型,real表示浮点型,text表示文本类型,blob表示二进制类型。另外,上述建

7.1 内容提供器简介

  • 内容提供器(Content Provider)主要用于在不同的应用程序之间实现数据共享的功能,它提供了一套完整的机制,允许一个程序访问另一个程序中的数据,同时还能保证被访数据的安全性。

7.2 运行时权限

  • 如果是属于这张表中的权限,那么就需要进行运行时权限处理,如果不在这张表中,那么只需要在AndroidManifest.xml文件中添加一下权限声明就可以了。

10.1 服务是什么

  • 服务(Service)是Android中实现程序后台运行的解决方案,它非常适合去执行那些不需要和用户交互而且还要求长期运行的任务
  • 服务并不是运行在一个独立的进程当中的,而是依赖于创建服务时所在的应用程序进程

10.2 Android多线程编程

  • 如果想要更新应用程序里的UI元素,则必须在主线程中进行,否则就会出现异常。
  • Android确实是不允许在子线程中进行UI操作的
  • 对于这种情况,Android提供了一套异步消息处理机制,完美地解决了在子线程中进行UI操作的问题。
  • Message、Handler、MessageQueue和Looper
  • Message是在线程之间传递的消息,它可以在内部携带少量的信息,用于在不同线程之间交换数据
  • 每个线程中只会有一个MessageQueue对象。
  • 简单来说,使用AsyncTask的诀窍就是,在doInBackground()方法中执行具体的耗时任务,在onProgressUpdate()方法中进行UI操作,在onPostExecute()方法中执行一些任务的收尾工作。

10.4 服务的生命周期

  • 这种情况下要同时调用stopService()和unbindService()方法,onDestroy()方法才会执行。

12.5 卡片式布局

  • scroll表示当RecyclerView向上滚动的时候,Toolbar会跟着一起向上滚动并实现隐藏;enterAlways表示当RecyclerView向下滚动的时候,Toolbar会跟着一起向下滚动并重新显示。snap表示当Toolbar还没有完全隐藏或显示的时候,会根据当前滚动的距离,自动选择是隐藏还是显示。

13.2 使用Intent传递对象

  • 使用Intent来传递对象通常有两种实现方式:Serializable和Parcelable