简介:《Android疯狂讲义》是为Android初学者量身打造的教程,李刚先生所著。本书的源码包包含了书中每个章节的示例程序源代码,目的是协助读者更深入地理解并掌握Android开发技能。书中内容涵盖了从基础知识、UI设计、数据存储、网络编程、服务与广播、多媒体处理、权限管理、通知与推送、动画效果,到高级主题如组件间通信、多进程应用以及应用性能优化等多个方面。通过源码的分析与实践,读者能够获得实战经验,并解决实际开发中的问题。
1. Android系统架构与基础
在深入探讨Android应用开发的高级特性之前,理解Android系统架构是至关重要的。本章将带您回顾Android的基础知识,确保您对平台的框架有一个坚实的理解。
1.1 Android操作系统的架构概述
Android操作系统基于Linux内核,它采用分层架构设计,由以下几个关键组件构成:
- Linux内核层: 为Android设备提供基本的服务,如安全、内存管理、进程管理等。
- 硬件抽象层(HAL): 提供标准化接口,使Android能够运行在不同的硬件平台上。
- 运行时库层: 包括核心库和Android运行时。核心库为Android应用提供Java语言核心库的功能;Android运行时则包括核心Java类库和Dalvik虚拟机或Android Runtime(ART)。
- 系统库与Android API: 提供开发Android应用所需的API,如图形处理库、数据库访问库等。
- 应用框架层: 提供构建应用所需的高级API,包括活动管理、视图系统、通知管理、包管理等。
- 应用层: 包括所有安装在设备上的Android应用,如拨号器、联系人等。
1.2 Android基础组件与开发环境搭建
Android应用由一个或多个组件构成,核心组件包括:
- Activity: 应用的用户界面,通常对应一个屏幕。
- Service: 在后台运行,不提供用户界面,可以执行长时间运行的操作或跨应用数据传输。
- BroadcastReceiver: 监听系统发出的广播,如电量低警告或接收到短信。
- ContentProvider: 管理访问和共享应用数据的接口,如联系人数据。
为了开始Android应用开发,您需要搭建开发环境:
- 安装JDK: 确保安装最新版本的Java开发工具包。
- 下载Android Studio: 官方推荐的开发工具,集成了代码编辑器、调试器和模拟器等。
- 配置Android SDK: 安装并配置所需的SDK版本。
开发环境搭建完成后,您就可以开始构建您的第一个Android应用,并深入学习后续章节中介绍的各种高级特性了。
2. Activity生命周期与管理
2.1 Activity生命周期解析
2.1.1 Activity状态转换
Activity在Android系统中扮演着至关重要的角色,它是所有用户界面的容器和交互的起点。而Activity的生命周期则是其运行的根本。Activity在运行过程中可以处于不同的状态,状态的转换是Activity生命周期的核心。
在Android系统中,Activity共有5种状态:
- Resumed(运行状态) :这是Activity的活跃状态,它在前台运行并且拥有用户输入焦点。
- Paused(暂停状态) :当一个透明或非全屏的Activity覆盖在当前Activity上时,当前Activity会进入暂停状态,但仍然保持其状态和成员信息。
- Stopped(停止状态) :如果Activity被另一个Activity完全覆盖,它将进入停止状态。它仍然留在内存中,但如果系统资源紧张,则可能会被系统销毁。
- Destroyed(销毁状态) :当Activity被系统销毁或Activity调用finish()方法后,它将进入销毁状态。
- Non-existence(不存在状态) :这是一种特殊状态,指Activity对象被系统销毁或Activity从未被创建。
从一个Activity状态转换到另一个状态,是由Android系统根据用户的操作或系统状态的变化来决定的。理解这些状态转换对于编写出稳定的Android应用是十分重要的。
2.1.2 生命周期回调方法
为了管理Activity状态的转换,Android框架提供了多个生命周期回调方法,开发者需要根据需求在相应的方法内编写代码以处理不同的状态转换事件。
主要生命周期回调方法如下:
-
onCreate()
: 当Activity被创建时调用,这是Activity生命周期的开始,通常用于初始化设置,如设置布局和初始化数据等。 -
onStart()
: 当Activity对用户可见时调用,表示Activity即将进入运行状态。 -
onResume()
: 在Activity即将进入Resumed状态,并且可以与用户进行交互时调用。 -
onPause()
: 当另一个Activity启动时调用,当前Activity进入Paused状态,通常用于暂停或保存持久数据。 -
onStop()
: 当Activity不再对用户可见时调用,此时Activity可以进入Stopped状态。 -
onDestroy()
: 当Activity即将被销毁时调用,可以执行清理工作。 -
onRestart()
: 当Activity从停止状态变为运行状态之前调用,即Activity重新启动。
通过这些生命周期回调方法,开发者可以控制Activity在不同生命周期的适当行为,确保应用的稳定性和流畅性。
2.2 Activity的管理机制
2.2.1 任务栈与Activity关系
在Android系统中,Activity并不是孤立存在的,它们是通过任务栈来管理的。任务栈是一种后进先出(LIFO)的数据结构,它记录了Activity的历史堆叠顺序。系统为每个应用运行实例维护一个任务栈,当一个新的Activity启动时,它会被推入当前任务栈的顶部。
这个机制有几个重要的特点和作用:
- 任务栈管理 :通过任务栈,系统可以方便地管理Activity的状态转换,如当用户按下返回键时,系统会移除栈顶的Activity,并将其状态从运行状态切换到暂停状态。
- 前后台管理 :当用户按下Home键,当前任务栈中的Activity不会被销毁,而是保持在后台,当用户重新进入应用时,可以快速从栈顶恢复。
- 任务恢复 :当系统因为资源不足而销毁任务栈中的Activity时,可以通过任务栈的记录来恢复它们的状态,例如通过
onSaveInstanceState()
保存必要的状态。
2.2.2 Activity的配置更改管理
当设备配置更改(如屏幕旋转、键盘可用性更改等)发生时,Activity会被系统销毁并重新创建。这种行为可以确保Activity能够适应新的配置,但它也可能会导致数据丢失或状态不一致的问题。
为了处理这种情况,Android提供了 onSaveInstanceState()
和 onRestoreInstanceState()
两个方法:
-
onSaveInstanceState()
: 在Activity被系统销毁前调用,可以保存Activity的状态,比如用户输入的文本、滚动位置等。 -
onRestoreInstanceState()
: 在Activity重新创建后恢复之前保存的状态。
通过这两个方法,开发者可以确保Activity在配置更改后能够恢复到之前的状态,提升用户体验。
通过本章的介绍,您应该已经对Android中的Activity生命周期有了一个深入的了解。这些基础知识对于开发稳定和高效的Android应用是不可或缺的。在下一章中,我们将深入探讨Intent的使用方法,进一步了解Android组件间的数据交互。
3. Intent的使用方法
3.1 Intent基础
3.1.1 Intent的类型和作用
Intent在Android中是一个非常核心的概念,它是一个消息传递对象,可以用来启动活动(Activity)、服务(Service)以及发送广播(BroadcastReceiver)。Intent通过指定组件名称来启动一个组件,或者通过指定动词、数据和额外信息来描述一个通用的操作来执行。
-
显式Intent:通过指定组件名直接启动目标组件。这种方式目标明确,不会发生歧义。
java Intent intent = new Intent(this, TargetActivity.class); startActivity(intent);
-
隐式Intent:不指定组件名称,而是声明一个操作,系统会根据操作查找能够响应该操作的组件并启动它。这种方式灵活但需要系统有明确的意图过滤器配置。
3.1.2 数据携带和结果返回
Intent可以携带数据,这是组件间通信的一种简单方式。携带数据需要使用 putExtra()
方法,并且可以携带多种类型的数据。数据通过键值对的形式附加到Intent上。
Intent intent = new Intent(this, TargetActivity.class);
intent.putExtra("key", "value");
startActivity(intent);
接收数据的组件可以使用 getIntent()
方法获取启动它的Intent,然后使用 getStringExtra()
等方法按照键值对的方式检索数据。
结果返回可以通过 startActivityForResult()
方法启动目标组件,目标组件使用 setResult()
方法返回结果,启动它的组件通过覆写 onActivityResult()
方法来接收结果。
// 启动目标组件
startActivityForResult(new Intent(this, TargetActivity.class), REQUEST_CODE);
// 在目标组件中返回结果
setResult(RESULT_OK, new Intent().putExtra("result", "Data returned"));
finish();
3.2 Intent的高级应用
3.2.1 隐式Intent的使用场景
隐式Intent常用于多种不同的应用场景,例如:
-
发起一个网页浏览:当应用需要打开一个网址时,可以通过定义一个带有
ACTION_VIEW
动词和网址数据的隐式Intent来实现。java Intent intent = new Intent(Intent.ACTION_VIEW); intent.setData(Uri.parse("***")); startActivity(intent);
-
分享文本信息:用户希望分享一些文本时,应用可以通过定义一个带有
ACTION_SEND
动词和文本数据的隐式Intent来实现。java Intent shareIntent = new Intent(); shareIntent.setAction(Intent.ACTION_SEND); shareIntent.putExtra(Intent.EXTRA_TEXT, "This is a share text."); shareIntent.setType("text/plain"); startActivity(Intent.createChooser(shareIntent, "Share using"));
3.2.2 Intent Filter的配置
为了使应用能够响应隐式Intent,需要在应用的 AndroidManifest.xml
文件中配置相应的Intent Filter。这告诉Android系统哪些组件能够响应哪些动作、数据类型等。
<activity android:name=".ShareActivity">
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="text/plain" />
</intent-filter>
</activity>
以上配置指明 ShareActivity
能够响应 ACTION_SEND
动作,并且处理数据类型为纯文本的Intent。当有其他应用发起这样的隐式Intent时,用户可以选择使用 ShareActivity
来分享内容。
Intent是Android应用间及应用内部组件间进行交互的基础。理解Intent的工作原理、类型和应用场景,有助于开发者设计出更灵活、可复用的组件架构。
4. UI布局和视图组件
4.1 布局管理器使用
4.1.1 常用布局的特点和适用场景
布局管理器是Android开发中非常重要的一个部分,它负责在有限的屏幕区域内,合理地安排各种视图组件。Android提供多种布局管理器,每种都有其独特的特点和使用场景。
LinearLayout(线性布局) :这是一种按顺序排列子视图的布局,可以垂直或水平排列。它的特点是简单易用,适用于创建单一方向的连续视图序列。例如,创建一个简单的登录界面,可以将用户名、密码输入框以及登录按钮按顺序排列。
RelativeLayout(相对布局) :这个布局允许视图组件相对于彼此或者相对于父布局定位。它提供了灵活的布局方式,适合创建复杂的界面,因为它可以减少嵌套的层次,从而提高界面的渲染效率。
FrameLayout(帧布局) :这是一种简单却强大的布局方式,其子视图通常是重叠的,第一个子视图位于最底层,最后一个位于顶层。它适用于需要重叠视图的场景,如悬浮按钮或者加载动画。
GridLayout(网格布局) :作为Android 4.0引入的新布局,GridLayout能够以网格的形式排列子视图,非常适合创建具有行列数据的表格布局,例如计算器界面。
ConstraintLayout(约束布局) :从Android 7.0开始,ConstraintLayout提供了一种灵活的方式来创建复杂的布局结构,类似于RelativeLayout但提供了更多的布局控制。它通过约束和边距来确定视图的位置,非常适合复杂的布局需求。
4.1.2 布局参数和嵌套使用
布局参数定义了子视图相对于父布局或其他子视图的关系。对于每种布局,都有自己特定的布局参数类,例如LinearLayout的 LinearLayout.LayoutParams
或RelativeLayout的 RelativeLayout.LayoutParams
。
在使用布局时,布局参数非常关键,因为它们定义了视图的宽度、高度、对齐方式以及与其他视图的关系。例如,在LinearLayout中,可以设置 weight
属性来分配空间,从而使得视图按比例扩展或收缩。
嵌套布局是布局管理中常见的用法,通过将一个布局嵌入到另一个布局中,可以创建更复杂的用户界面。但是,过多的嵌套会增加渲染的复杂性和界面的渲染时间,因此应该尽量避免不必要的嵌套,或考虑使用如ConstraintLayout这样的扁平化布局。
示例代码展示了如何在LinearLayout中使用布局参数来定义子视图:
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Welcome to Android Development"
android:gravity="center_horizontal"
android:padding="16dp"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Click Me"
android:layout_gravity="center_horizontal"/>
</LinearLayout>
在上述代码中,LinearLayout作为父布局,其中包含一个TextView和一个Button。通过设置 layout_width
为 match_parent
,TextView会尝试匹配父布局的宽度。Button则使用 wrap_content
,它只会根据其内容来决定大小。 layout_gravity
属性用于控制子视图在父布局中的位置。
4.2 视图组件实践
4.2.1 常见视图组件的属性和事件监听
Android平台提供了各种视图组件,用于显示数据或与用户进行交互。一些常见的视图组件包括TextView、Button、ImageView、EditText等。
TextView :用于显示文本信息。TextView有很多有用的属性,如 text
、 textColor
、 textSize
、 gravity
等,可以用来控制文本的显示内容、颜色、大小、对齐方式等。
Button :用户点击触发事件。它可以通过 android:onClick
属性指定点击事件的处理函数。可以设置 text
属性定义按钮上的显示文字。
ImageView :用于显示图片。通过 android:src
属性来设置图片资源。ImageView还支持多种图像格式和一些基本的图片操作,如缩放、裁剪等。
EditText :用于用户输入文本。它提供了丰富的属性和方法来管理用户输入,例如 inputType
、 hint
、 maxLines
、 textWatcher
等。 inputType
可以设置为密码、数字、电话号码等类型, textWatcher
可以监听文本变化的事件。
事件监听器是视图组件中不可分割的一部分,它们用于响应用户的操作。例如:
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 处理点击事件
}
});
imageView.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
// 处理触摸事件
return true;
}
});
在上述代码中, setOnClickListener
是为按钮添加点击事件监听器,而 setOnTouchListener
则用于添加触摸事件监听器。
4.2.2 自定义视图组件开发
在Android开发中,可能会遇到一些需求,标准视图组件无法满足。此时,可以通过继承已有的视图类或直接继承 View
类来自定义视图组件。
自定义视图组件需要覆盖 onDraw(Canvas canvas)
方法来绘制内容,同时还可以覆盖 onMeasure(int widthMeasureSpec, int heightMeasureSpec)
来确定自己的尺寸。自定义视图可以通过组合已有的视图或绘制自定义图形来实现独特界面。
public class CustomView extends View {
private Paint paint;
public CustomView(Context context, AttributeSet attrs) {
super(context, attrs);
paint = new Paint();
paint.setColor(Color.BLACK);
paint.setTextSize(50);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawText("Custom View", 10, 50, paint);
}
}
在上面的代码中, CustomView
继承自 View
类,并重写了 onDraw
方法来绘制文本。通过这种方式,可以实现各种自定义视图组件的需求。
自定义组件开发涉及到的技术点较多,包括视图的尺寸测量、绘制优化、触摸事件处理等,需要开发者对Android绘图机制有较深的理解。
5. RecyclerView与Adapter的使用
5.1 RecyclerView基础
5.1.1 RecyclerView的工作原理
RecyclerView
是 Android 开发中用于高效显示大量数据列表的组件。它可以显示任何类型的视图,并且提供了高度的可定制性和性能优化。 RecyclerView
的核心是一个 RecyclerView.Adapter
,这个适配器负责将数据集合绑定到列表的每个条目上,以及回收和重用视图以优化滚动性能。
RecyclerView
的工作原理基于以下几个关键组件:
- Adapter :负责数据和视图的绑定,将数据集合中的内容转换成视图展示给用户。当列表滚动时,它可以重用视图,以减少不必要的视图创建操作。
-
LayoutManager :管理每个子视图的位置和大小,并且控制列表的滚动方向。常见的
LayoutManager
有LinearLayoutManager
(线性布局),GridLayoutManager
(网格布局),StaggeredGridLayoutManager
(瀑布流布局)等。 -
ViewHolder :每个
RecyclerView
的条目都通过一个视图持有者(ViewHolder
)来管理,它是RecyclerView.Adapter
的内部类,用于缓存视图信息,提高列表滚动的性能。
5.1.2 LayoutManager的选择与应用
选择合适的 LayoutManager
对于优化 RecyclerView
的性能和用户体验至关重要。不同的 LayoutManager
适用于不同的场景:
- LinearLayoutManager :它按照单列或单行来展示数据,适用于简单的列表展示。通过设置方向为
VERTICAL
或HORIZONTAL
,可以实现垂直或水平滚动。
LinearLayoutManager layoutManager = new LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false);
recyclerView.setLayoutManager(layoutManager);
- GridLayoutManager :它将数据分为多列展示,适用于展示网格形式的数据,比如图片画廊。
GridLayoutManager gridLayoutManager = new GridLayoutManager(context, 2);
gridLayoutManager.setOrientation(GridLayoutManager.VERTICAL);
recyclerView.setLayoutManager(gridLayoutManager);
- StaggeredGridLayoutManager :这个布局管理器可以创建不规则的网格布局,其中每个条目的高度或宽度可以不同,适用于复杂的布局设计。
StaggeredGridLayoutManager staggeredGridLayoutManager = new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL);
recyclerView.setLayoutManager(staggeredGridLayoutManager);
5.2 Adapter高级技巧
5.2.1 ViewHolder模式优化
为了进一步提高性能, RecyclerView.Adapter
中推荐使用 ViewHolder
模式。这种方式通过将每个条目的视图缓存到 ViewHolder
中,避免了在 onBindViewHolder
方法中重复查找视图元素,从而加快了视图的绑定速度。
下面是一个使用 ViewHolder
模式的典型实现:
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyViewHolder> {
private List<String> productList;
public MyAdapter(List<String> productList) {
this.productList = productList;
}
@Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
// 创建视图并包装成ViewHolder
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.product_item, parent, false);
return new MyViewHolder(view);
}
@Override
public void onBindViewHolder(MyViewHolder holder, int position) {
// 直接通过ViewHolder绑定数据
holder.productName.setText(productList.get(position));
}
@Override
public int getItemCount() {
return productList.size();
}
public static class MyViewHolder extends RecyclerView.ViewHolder {
TextView productName;
public MyViewHolder(View itemView) {
super(itemView);
productName = itemView.findViewById(R.id.product_name);
}
}
}
5.2.2 DiffUtil提升列表性能
当数据集合发生变化时, RecyclerView
需要更新屏幕上的视图以反映数据的变更。 DiffUtil
类可以帮助开发者计算两个列表之间的差异,并且智能地进行最小限度的更新,这样可以大大提高列表更新的性能。
下面是如何使用 DiffUtil
来计算和应用数据变更的示例:
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyViewHolder> {
private List<String> oldProductList;
private List<String> newProductList;
@Override
public int getItemCount() {
return newProductList.size();
}
@Override
public void setList(List<String> newList) {
oldProductList = new ArrayList<>(newList);
newProductList = newList;
// 重新计算差异并更新列表
DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new MyDiffCallback(oldProductList, newProductList));
diffResult.dispatchUpdatesTo(this);
}
private class MyDiffCallback extends DiffUtil.Callback {
private List<String> oldList;
private List<String> newList;
MyDiffCallback(List<String> oldList, List<String> newList) {
this.oldList = oldList;
this.newList = newList;
}
@Override
public int getOldListSize() {
return oldList.size();
}
@Override
public int getNewListSize() {
return newList.size();
}
@Override
public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
// 根据项目位置判断两个对象是否是同一项
return oldList.get(oldItemPosition).equals(newList.get(newItemPosition));
}
@Override
public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
// 判断两个条目的内容是否相同
return oldList.get(oldItemPosition).equals(newList.get(newItemPosition));
}
}
}
通过以上示例,可以看出 DiffUtil
不仅优化了更新列表的过程,还通过抽象出的 DiffCallback
来自定义判断数据项相等性的逻辑,增强了代码的可读性和可维护性。
6. SharedPreferences与SQLite数据库操作
6.1 SharedPreferences使用指南
6.1.1 持久化存储与读取数据
在Android开发中, SharedPreferences
是一个轻量级的存储类,它提供了一种方便的机制来存储和检索持久的键值对数据。这些数据可以是用户的设置或者应用的状态信息,使用 SharedPreferences
可以在不同的Activity或者应用程序组件之间共享数据。
通常情况下,使用 SharedPreferences
来存储少量数据,如应用的配置信息或者用户的一些小偏好设置。
代码示例
以下是一个简单的使用示例,它演示了如何存储和读取一个字符串类型的值:
// 获取SharedPreferences实例
SharedPreferences sharedPreferences = getSharedPreferences("MyPrefs", MODE_PRIVATE);
// 存储数据
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putString("username", "AndroidDev");
editor.apply(); // 使用apply()异步保存数据
// 读取数据
String username = sharedPreferences.getString("username", "DefaultName");
在这个示例中, getSharedPreferences
方法用于获取一个 SharedPreferences
实例,其中 "MyPrefs"
是这个文件的名称, MODE_PRIVATE
指明了文件模式(只有本应用才可以读写这个文件)。 SharedPreferences.Editor
对象允许我们进行数据的写入操作, putString
方法是用于存储字符串类型数据的函数。使用 apply()
方法来异步保存数据,这是一种更高效的方式,因为它不会阻塞主线程。
参数说明
-
getSharedPreferences
: 第一个参数是文件名(不是文件路径),它唯一标识了这个偏好设置文件。第二个参数是操作模式,MODE_PRIVATE
是最常见的,表示只有你的应用可以访问这个文件。 -
SharedPreferences.Editor
: 通过edit()
方法获得,允许对值进行修改。 -
apply()
: 提交对偏好设置的更改,不需要等待操作完成。
6.1.2 数据加密与安全存储
在某些情况下,存储在 SharedPreferences
中的数据可能包含敏感信息,因此对其进行加密存储是很重要的。Android提供了一个内置的加密机制,可以帮助开发者保护这些数据。
数据加密示例
SharedPreferences
本身不提供加密功能,但是可以利用系统服务和一些额外的步骤来实现。例如,使用 SharedPreferences.Editor
的 putStringSet
方法存储加密后的数据:
// 加密数据
String secretData = "Secret info";
String encryptedData = new String(encryptString(secretData));
// 存储加密数据
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putString("secret", encryptedData);
editor.apply();
在这个例子中, encryptString
方法是一个假设的加密函数,你需要根据需要实现它。它会将你的数据转换为一个加密字符串,然后这个字符串就可以像普通字符串一样存储在 SharedPreferences
中。
参数说明
-
encryptString
: 这个函数需要开发者自己实现,可以使用javax.crypto
类库来进行加密操作。 -
putString
: 存储字符串值,将加密后的数据转换为字符串格式保存。
请注意,存储加密数据虽然可以增加安全性,但也需要确保安全密钥的管理以及数据的解密流程,避免数据的永久性丢失。此外,如果数据的敏感性非常高,可能需要考虑使用更安全的存储方案,如使用 SQLCipher
对数据库进行加密。
6.2 SQLite数据库操作
6.2.1 数据库创建与版本管理
SQLite 是 Android 平台内置的轻量级关系数据库,非常适合用于管理结构化数据。由于它嵌入在应用程序中,因此不需要单独的服务器进程。
在创建和管理数据库时,通常在 SQLiteOpenHelper
类中进行。这个类提供了创建和版本管理数据库的能力,并为数据库的升级提供了一个接口。
数据库版本管理
每次需要更改数据库结构时,都需要更新数据库版本号,并且提供一个 SQLiteOpenHelper
的子类来处理升级逻辑。
public class DatabaseHelper extends SQLiteOpenHelper {
private static final String DATABASE_NAME = "MyDatabase.db";
private static final int DATABASE_VERSION = 2;
public DatabaseHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
// 创建表
db.execSQL("CREATE TABLE users (_id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, age INTEGER)");
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
// 如果旧版本是1并且新版本是2,那么进行升级操作
if (oldVersion < 2) {
db.execSQL("ALTER TABLE users ADD COLUMN email TEXT");
}
}
}
在这个例子中, onCreate
方法定义了首次创建数据库时应当执行的SQL命令。 onUpgrade
方法则定义了数据库的升级逻辑。通过比较 oldVersion
和 newVersion
参数,你可以判断数据库的版本,并执行相应的升级SQL命令。
参数说明
-
DATABASE_NAME
: 指定数据库文件的名称。 -
DATABASE_VERSION
: 数据库版本号,每次需要修改数据库结构时都应增加这个版本号。 -
onCreate
: 当数据库被首次创建时,SQLiteOpenHelper
会调用此方法。 -
onUpgrade
: 当数据库的版本发生改变时,SQLiteOpenHelper
会调用此方法进行数据库的升级操作。
6.2.2 CRUD操作及事务处理
一旦数据库创建完成,接下来的操作通常包括数据的增删改查(CRUD)。 SQLiteDatabase
类提供了进行这些操作的方法。
CRUD操作
以下是进行数据操作的一些基本示例:
// 插入数据
ContentValues values = new ContentValues();
values.put("name", "John");
values.put("age", 24);
SQLiteDatabase db = dbHelper.getWritableDatabase();
long newRowId = db.insert("users", null, values);
// 查询数据
Cursor cursor = db.query("users", new String[] { "name", "age" },
"age > ?", new String[] { "21" }, null, null, null);
if (cursor.moveToFirst()) {
do {
String name = cursor.getString(cursor.getColumnIndex("name"));
int age = cursor.getInt(cursor.getColumnIndex("age"));
// 处理数据...
} while (cursor.moveToNext());
}
// 更新数据
ContentValues valuesUpdate = new ContentValues();
valuesUpdate.put("age", 25);
db.update("users", valuesUpdate, "name = ?", new String[] { "John" });
// 删除数据
db.delete("users", "name = ?", new String[] { "John" });
这些方法分别使用 ContentValues
对象来存储要插入或更新的列值,使用SQL语句执行查询和删除操作。
事务处理
数据库事务可以确保一系列操作要么全部成功,要么全部失败,以保持数据的一致性。可以通过 SQLiteDatabase
的 beginTransaction()
, setTransactionSuccessful()
, 和 endTransaction()
方法来执行事务。
db.beginTransaction();
try {
// 进行一系列的数据库操作...
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
在这个例子中, beginTransaction
启动一个事务,然后在 try
代码块中执行所有需要的数据库操作。如果操作成功完成,调用 setTransactionSuccessful()
方法来标记事务成功。无论成功与否, finally
代码块确保 endTransaction()
方法被调用,这将提交或回滚事务。
参数说明
-
ContentValues
: 用于存储一组键值对,它表示行中的列数据。 -
insert
: 将新的行插入到数据库表中。 -
query
: 执行一个查询操作,返回一个包含结果集的Cursor
对象。 -
update
: 更新数据库中的行,需要指定表、新的列值以及条件。 -
delete
: 删除数据库中的行,需要指定要删除的表和条件。 -
SQLiteDatabase
: 提供了对数据库的操作方法。 -
ContentValues
: 用于表示一组列和值的映射,通常用在插入和更新操作中。
请注意,实际操作时可能需要处理 SQLiteException
异常,例如在进行数据操作时数据库文件不可用或SQL语句有误等情况。
7. 网络编程与JSON解析
7.1 网络编程基础
网络编程是移动应用开发中不可或缺的一部分,特别是在Android平台上,它允许应用进行数据交换、服务访问和其他远程操作。在深入网络编程之前,了解基础的网络知识和Android中网络请求的处理方式至关重要。
7.1.1 HTTP协议与请求/响应模型
HTTP协议全称超文本传输协议(Hypertext Transfer Protocol),是一种用于分布式、协作式和超媒体信息系统的应用层协议。HTTP协议是无状态的,意味着它不会保存任何数据状态,这对于网络编程来说,状态管理需要开发者自行实现。
在Android中,网络请求通常由HTTP客户端发起,发起一个HTTP请求包括以下步骤:
- 创建一个HTTP客户端实例。
- 构建请求对象(例如GET、POST、PUT、DELETE等)。
- 设置请求头信息,如内容类型、认证信息等。
- 发送请求并获取响应对象。
- 读取响应内容,并根据需要进行处理。
// 示例代码:发起一个简单的GET请求
HttpClient httpClient = new DefaultHttpClient();
HttpGet httpGet = new HttpGet("***");
try {
HttpResponse response = httpClient.execute(httpGet);
// 处理响应
} catch (Exception e) {
e.printStackTrace();
}
7.1.2 网络权限和线程处理
在Android应用中执行网络请求需要声明网络权限,在 AndroidManifest.xml
中添加以下权限:
<uses-permission android:name="android.permission.INTERNET" />
网络请求不应该在主线程(UI线程)中执行,否则会阻塞用户界面,导致应用无响应(ANR)。在Android中,通常会用 AsyncTask
或 ExecutorService
等机制来在后台线程处理网络请求。
// 使用AsyncTask发起网络请求
class DownloadTask extends AsyncTask<String, Void, String> {
@Override
protected String doInBackground(String... urls) {
try {
URL url = new URL(urls[0]);
HttpURLConnection httpConn = (HttpURLConnection) url.openConnection();
// 配置HTTP请求参数
InputStream inputStream = new BufferedInputStream(httpConn.getInputStream());
// 处理输入流
return "请求成功";
} catch (Exception e) {
return "请求失败:" + e.getMessage();
}
}
@Override
protected void onPostExecute(String result) {
// 更新UI
super.onPostExecute(result);
}
}
new DownloadTask().execute("***");
7.2 JSON数据解析
JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,易于人阅读和编写,同时也易于机器解析和生成。在网络编程中,JSON常作为数据交换格式,用于前后端的数据传输。
7.2.1 JSON结构与解析方式
JSON数据通常包含以下结构:
- 对象:一组无序的键值对集合,用大括号
{}
包围,键值对之间用逗号,
分隔。键用双引号"
包围,值可以是字符串、数字、数组、布尔值或null。 - 数组:用方括号
[]
包围,元素之间用逗号,
分隔。 - 值:可以是字符串、数字、布尔值、null、对象或数组。
在Android中,JSON解析通常使用 org.json
包中的类,或者使用第三方库如GSON或Moshi。
// 使用org.json解析JSON数据
JSONObject jsonObject = new JSONObject("{\"name\":\"John\", \"age\":30}");
String name = jsonObject.getString("name");
int age = jsonObject.getInt("age");
7.2.2 GSON和Moshi库的使用比较
GSON和Moshi是两个流行的JSON解析库,它们提供更加简洁的API来进行Java对象和JSON之间的转换。
GSON:
- GSON是Google提供的一个库,能够将Java对象序列化为JSON字符串,或者将JSON字符串反序列化为Java对象。
- GSON支持泛型。
- GSON在Android中广泛使用,但需要注意的是,它不是Android官方的库,可能有潜在的安全问题。
// 使用GSON库序列化和反序列化
Gson gson = new Gson();
MyClass myObject = new MyClass("John", 30);
String json = gson.toJson(myObject);
MyClass newObject = gson.fromJson(json, MyClass.class);
Moshi:
- Moshi是由Square开发的一个替代GSON的库,它更轻量且速度更快。
- Moshi只支持JSON到Java的反序列化,不支持Java到JSON的序列化。
- Moshi支持Kotlin,并且能够与协程很好地集成。
// 使用Moshi库进行反序列化
Moshi moshi = new Moshi.Builder().build();
JsonAdapter<MyClass> adapter = moshi.adapter(MyClass.class);
String json = "{name:'John',age:30}";
MyClass myObject = adapter.fromJson(json);
在实际应用中,选择GSON还是Moshi取决于你的项目需求、团队偏好以及是否需要与Kotlin或协程集成。
简介:《Android疯狂讲义》是为Android初学者量身打造的教程,李刚先生所著。本书的源码包包含了书中每个章节的示例程序源代码,目的是协助读者更深入地理解并掌握Android开发技能。书中内容涵盖了从基础知识、UI设计、数据存储、网络编程、服务与广播、多媒体处理、权限管理、通知与推送、动画效果,到高级主题如组件间通信、多进程应用以及应用性能优化等多个方面。通过源码的分析与实践,读者能够获得实战经验,并解决实际开发中的问题。