本章为移动程序开发——Android开发的期末复习专题,提供了一些简单的例子和图像。
这里是一些关于 Android 开发的基础知识。
线程与进程有什么关系
Activity的生命周期回调函数的顺序是怎么样的?
生命周期流程可以如下表示:
onCreate()
-> onStart()
-> onResume()
onPause()
-> onStop()
onRestart()
-> onStart()
-> onResume()
onPause()
-> onStop()
-> onDestroy()
onCreate()
方法只会在 Activity 第一次被创建时调用一次。onRestart()
方法只会在 Activity 从 onStop()
状态返回时调用。onPause()
它在 Activity 离开前台时被调用,一般在此保存一些资源和状态操作。onStop()
表示 Activity 完全不可见时调用。onDestroy()
是 Activity 被销毁前的最后一次机会进行清理工作。
AndroidManifest.xml
中,如何设置Activity的标题以及是否为首页?
在 AndroidManifest.xml
中,可以在AndroidManifest.xml
中为Activity指定一个标签(label)
<activity android:name=".MainActivity"
android:label="@string/app_name">
</activity>
要将某个Activity设置为应用的首页,需要在AndroidManifest.xml
文件中为这个Activity添加<intent-filter>
,并包含ACTION_MAIN
和CATEGORY_LAUNCHER
。
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
在 Android 项目结构中,存在哪些关键的文件夹和文件,它们的作用是什么?
我们的 Android 项目中,主要的项目文件存放于:app/src/main
,下面也以这个文件夹为根目录。
mipmap-mdpi
、mipmap-hdpi
等。
下面是一些常见组件,及其解释。
app/src/main/AndroidManifest.xml
: 是每个Android应用都必须包含的一个文件,它位于应用的根目录下。这个文件向Android系统描述了应用的基本信息,包括应用的组件(如Activity、Service、BroadcastReceiver和ContentProvider)、权限需求、使用的API级别等。Activity.java
: 是Android应用中的一个关键组件,它负责与用户进行交互,通常表现为屏幕上的一个界面。activity_layout.xml
: 样式文件,定义应用的用户界面。
startActivity.java 和 startActivity.class 之间有什么区别?
startActivity.java
和 startActivity.class
代表的是不同的阶段和不同类型的文件
特性 | startActivity.java |
startActivity.class |
---|---|---|
类型 | Java 源代码文件 | Java 字节码文件 |
内容 | 人类可读的 Java 代码 | JVM 可执行的字节码 |
生成方式 | 开发者编写 | 通过 Java 编译器编译 .java 文件生成 |
用途 | 编写程序逻辑 | JVM 执行程序 |
在 Android 中 | 存放 Activity、Service 等组件的源代码 | Android 运行时加载和执行的类文件 |
可以把 .java
文件比作菜谱,里面写着做菜的步骤(源代码)。而 .class
文件就像按照菜谱做出来的菜肴,可以直接食用(被 JVM 执行)。.java
是编译前的文件,.class
是编译后的文件。
Intent 是什么?
Intent
在 Android 中是一个消息传递对象,你可以使用它来请求执行某个操作。可以将 Intent
看作是不同组件之间进行通信的信使。通过 Intent
,你可以启动 Activity、启动 Service、传递广播消息等。
Intent
可以分为两种类型:
Intent intent = new Intent(this, TargetActivity.class);
startActivity(intent);
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse("https://www.example.com"));
startActivity(intent);
Adapter 是什么? 常见的 Adapter 有哪些,它们都有什么功能?
Adapter
在 Android 中是一个连接数据源和 AdapterView 的桥梁。它可以从数据源(例如数组、List、数据库查询结果等)中获取要显示的数据。
以下是一些常见的 Android Adapter
及其功能:
List
中的数据展示在 AdapterView
上。List
类型的、由 Map
对象组成的列表展示在 AdapterView
上。每个 Map
对象代表一行数据,键对应数据项的字段名,值对应字段值。 比 ArrayAdapter
更灵活,可以展示包含多个字段的数据。Cursor
对象中的数据展示在 AdapterView
上。Cursor
通常是数据库查询的结果集。RecyclerView
的 Adapter。RecyclerView
是一个更加灵活和高效的 AdapterView
,用于展示大量数据。
有哪些值得注意的Layout设计问题?
在 Android 开发中,Layout
是用于组织和排列屏幕上 UI 元素的容器。
以下是一些常见的布局类型:
在LinearLayout
中,你至少需要设置其宽度和高度以及排列方向。
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal/vertical">
</LinearLayout>
LinearLayout
的默认排列方向是 horizontal
wrap_content
和 match_parent
是 View
的两个非常重要的布局参数,用于控制 View
的宽度和高度。
wrap_content
: 表示 View
的大小会根据其内容的大小来调整。例如,如果一个 TextView
的文本内容很短,那么它的宽度就会很窄;如果文本内容很长,它的宽度就会相应地变宽。match_parent
: 表示 View
的大小会填充其父容器的剩余空间。对于宽度来说,它会填充父容器的宽度;对于高度来说,它会填充父容器的高度。
在 Android 中,id
是用于唯一标识一个 View
的标识符。你可以在 XML 布局文件中使用 android:id
属性来设置 View
的 ID。
android:id="@+id/your_view_id"
在 Java 代码中使用这个 ID 来获取 View
的实例,例如:
TextView userNameTextView = findViewById(R.id.user_name_text);
在EditText组件中,我们经常使用hint
用于提示用户的输入:
android:hint="默认提示文本"
在CheckBox控件中,可以设计默认选中:
android:checked="true"
CheckBox 提供了几个方法和事件用于设置或者获取自身是否选中状态:
if (box.isChecked()){
// 选中
}
box.setChecked(true); // 设置选中
为什么需要用SQLite数据库,它有什么优点?
在 Android 应用开发中,我们经常需要持久化存储数据。这意味着即使应用被关闭或者设备重启,数据仍然能够被保存下来,并在下次应用启动时可以被访问。SQLite 是一个轻量级的、基于文件的、开源的关系型数据库管理系统。它不需要独立的服务器进程,整个数据库都存储在一个单独的文件中,这使得它非常适合嵌入式系统
SQLite具有如下优点:
SQLiteDatabase
, SQLiteOpenHelper
) 来进行数据库操作。这使得在 Android 应用中使用 SQLite 非常方便。
在activity中,如何向 TextView 显示所选 Spinner 的值?
Show the value of choosen Spinner to TextView
public void onClickFindBeer(View view) {
// Get a reference to the TextView
TextView brands = (TextView) findViewById(R.id.brands);
// Get a reference to the Spinner
Spinner color = (Spinner) findViewById(R.id.color);
// Get the selected item in the Spinner
String beerType = String.valueOf(color.getSelectedItem());
// Display the selected item
brand.setText(beerType);
}
在当前 Activity 中,如何跳转到 ReceiveMessageActivity,并携带一些信息?
Start the other activity (ReceiveMessageActivity) and put some message in the intent.
import android.content.Intent
...
public void onSendMessage(View view) {
// Get a reference to the EditView
EditView messageView = (EditView) findViewById(R.id.message);
// Get the value from EditView
String message = messageView.getText().toString();
// Define Intent
Intent intent = new Intent(this, ReceiveMessageActivity.class);
// Put infomation
intent.putExtra(ReceiveMessageActivity.EXTRA_MESSAGE, message);
// Start Intent
startActivity(intent);
}
putExtra("var", value);
"var"
需要引号,表示hash table中的一个名称,是String
类的。
onSendMessage()
回应点击。它创建了一个Intent,并向ReceiveMessageActivity发送了信息,并打开它。在 ReceiveMessageActivity 中如何获取传递的信息?
Get infomation from intent
import android.content.Intent
public class ReceiveMessageActivity ... {
// Define a constant to pass the value in the Intent
public static final String EXTRA_MESSAGE = "message";
@Override
protected void onCreate(...) {
...
// Get the Intent
Intent intent = getIntent();
// Get the value from Intent
String messageText = intent.getStringExtra(EXTRA_MESSAGE);
// Set to a TextView
TextView textView = (TextView) findViewById (R.id.text);
textView.setText(messageText);
}
}
EXTRA_MESSAGE
用于 Intent 传递数据时的键(key),通常定义在接收 Intent 的 Activity 中。
如何使用 Intent 传递 Action?
Create an intent that specifies an action. (Call other APP)
import android.content.Intent
...
public void onSendMessage(View view) {
// Get a reference to the EditView and Get the value from EditView
EditView messageView = (EditView) findViewById(R.id.message);
String message = messageView.getText().toString();
// Create an Intent with Action
Intent intent = new Intent(Intent.ACTION_SEND);
// Set Extra infomation format and message
intent.setType("text/plain");
intent.putExtra(Intent.EXTRA_TEXT, message);
// Use a chooser and set title
String chooserText = getString (R.string.chooser);
Intent chooserIntent = Intent.createChooser(intent, chooserText);
// Start Intent
startActivity(intent);
}
在 Android 的生命周期中,onSaveInstanceState()
,可以保存任何需要保留的值,以免丢失。
Save the current state
@Override
public void onSaveInstanceState(Bundle savedInstanceState) {
super.onSaveInstanceState(savedInstanceState);
// Save values
savedInstanceState.putInt("sec", 1);
savedInstanceState.putString("message", "test");
savedInstanceState.putBoolean("running", true);
}
使用 savedInstanceState.put{type}(“var”, value);
在 "var"
中存储值。
Get stored state
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout...);
// Get values from savedInstanceState
if (savedInstanceState != null) {
int sec = savedInstanceState.getInt("sec");
String message = savedInstanceState.getString("message");
Boolean running = savedInstanceState.getBoolean("running");
}
}
下面是完整的项目生命周期回调函数的顺序:
onPause()
onResume()
onStop()
.onRestart()
.在 Android 布局中,Margin
(外边距)和 Padding
(内边距)是两个非常重要的概念,它们都用于控制 View 周围的空白空间,但作用的位置和影响的对象不同。
Margin
是指一个 View 自身边界之外 的空白区域,用于控制 View 与其相邻的 View 或父容器之间的距离。Margin
影响的是 当前 View 本身 在其父容器中的位置,以及与其他兄弟 View 之间的间隔。
Margin
is to add extra space to the top/bottom/left/right of the its own view. 仅能对自己当前view进行距离添加调整。
Padding
是指一个 View 自身边界之内 的空白区域,用于控制 View 的 内容 与其 自身边缘 之间的距离。Padding
影响的是 View 的内容 在其自身内部的布局,使其内容不会紧贴 View 的边缘。
Padding
specifies the distance between its contents and the boundary of the parent view. 注重于与父视图之间的距离。
特性 | Margin (外边距) | Padding (内边距) |
---|---|---|
位置 | View 边界之外 | View 边界之内 |
控制对象 | View 与相邻 View 或父容器的距离 | View 的内容与其自身边缘的距离 |
影响 | View 的外部位置和与其他 View 的间隔 | View 内部内容的布局和显示效果 |
属性前缀 | layout_margin... |
padding... |
android:gravity
属性作用于 View 自身,用于控制 View 内部内容 在 View 自身边界内的对齐方式。
Gravity
controls the alignment (对齐方式) of the content inside the view. 仅视图内部。
top
:内容靠顶部对齐。bottom
:内容靠底部对齐。left/start
:内容靠左侧对齐。right/end
:内容靠右侧对齐。center_vertical
:内容在垂直方向居中对齐。center_horizontal
:内容在水平方向居中对齐。center
:内容在水平和垂直方向都居中对齐。|
组合使用,例如 center|right
表示垂直居中并靠右对齐。
与它很相似的是layout_gravity
,android:layout_gravity
属性作用于 View 本身,但它是用来告诉 父容器 如何在父容器内 定位这个 View。它决定了 View 自身在其父容器所提供的空间内的对齐方式。
例如:
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center" <!-- 内容居中于整个页面 -->
android:text="Hello World!" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="right" <!-- 整个按钮右对齐 -->
android:text="Click Me" />
特性 | android:gravity |
android:layout_gravity |
---|---|---|
作用对象 | View 自身 | View 自身 (但作用于其父容器) |
控制目标 | View 内部内容 在自身边界内的对齐方式 | View 自身 在其父容器提供的空间内的对齐方式 |
影响范围 | View 内部 | View 在父容器中的位置 |
常用场景 | 控制 TextView 等文本的对齐,ImageView 图片的对齐 |
在 LinearLayout 等布局中控制子 View 的位置 |
当我们需要响应用户点击事件时,需要使用一个监听器(Listener) - OnClickListener()
,用于处理复杂的用户点击和响应场景。
例如在ListView中需要响应用户对列表项的点击:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ListView listView = findViewById(R.id.list_view);
// Define Listener
AdapterView.OnItemClickListener itemOnclickListener = new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> listView, View view, int position, long id) {
// Handle event
if (position == 0) {
Intent intent = new Intent(this, DrinkCategoryActivity.class);
startActivity(intent;
}
}
}
// Set OnItemClickListener
listView.setOnItemClickListener(itemOnclickListener);
}
}
onItemClick()
的参数如下:
AdapterView<?> listView
是触发事件的父视图,即ListView`。View view
是被点击的具体项的视图。int position
是被点击项在适配器中的位置。long id
是被点击项的行ID。例如这是一个应用了Listener的例子。
onItemClickListener
Adapter (适配器) 是用于连接后端数据和前端显示的适配器接口,是数据data和UI(View)之间一个重要的纽带。View需要通过Adapter加载数据,Adapter就像是转接头一下,它们的关系如下:
Adapter通过将数据项转换为视图(View)来工作。每当需要显示一个新的数据项时,Adapter会创建或重用一个视图,并将相应的数据绑定到该视图上。其中最常用的Adapter是ArrayAdapter
,用于将数组或列表中的数据绑定到视图(一般用于ListView)。
// Define an Adapter
ArrayAdapter<String> adapter = new ArrayAdapter<>(
this,
android.R.layout.simple_list_item_1,
data
);
// Set the Adapter for ListView
ListView listView = findViewById(R.id.my_list_view);
listView.setAdapter(adapter);
ArrayAdapter<{data_type}> adapter
: 需要转换数据项的类型。this
: 表示当前的activity。android.R.simple_list_item_1:
单行显示的内置样式。data
: 一个数组,表述数据。我们有这样的例子:
DrinkCategoryActivity
的layout有一个ListView;DrinkCategoryActivity
中定义并设置了一个Adapter,是自己定义的Drink类;我们还有这样的例子:
很容易得到如下答案:
String[] colors = new String[] {"Red", "Blue", "Green"};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Spinner spinner = (Spinner) findViewById(R.id.spinner);
ArrayAdapter<String> adapter = new ArrayAdapter<> (
this,
android.R.layout.simple_spinner_item,
colors
);
spinner.setAdapter(adapter);
}
Fragment 可以被视为一个 Activity 的子部分,它具有自己的生命周期和事件处理机制。Fragment 使得在同一 Activity 中组合多个 UI 组件成为可能,从而实现模块化设计。
我们有如下Fragment的例子:
新的Fragment类继承自 Fragment
类,并重写 onCreateView()
方法以加载布局。
public class ExampleFragment extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_example, container, false);
}
}
如果是ListFragment的话,则需要继承ListFrament类,它必须包含ListView,同时通过一些ArrayAdapter处理点击响应:
public class MyListFragment extends ListFragment {
private String[] cities = {"Shenzhen", "Beijing", "Shanghai"};
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
// 使用 ArrayAdapter 绑定数据
setListAdapter(new ArrayAdapter<>(getActivity(), android.R.layout.simple_list_item_1, cities));
}
@Override
public void onListItemClick(ListView l, View v, int position, long id) {
// 处理点击事件
String selectedCity = cities[position];
Toast.makeText(getActivity(), "You selected: " + selectedCity, Toast.LENGTH_SHORT).show();
}
}
我们创建好Fragment后,还需要将Fragment在需要使用的Activiy中载入,通常可以通过 FragmentManager
来实现:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (savedInstanceState == null) {
MyListFragment fragment = new MyListFragment();
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.replace(R.id.fragment_container, fragment);
ft.addToBackStack(null);
ft.commit();
}
}
}
这里定义的fragment是MyListFragment
类的,需要确保其实fragment定义的主类。加载的过程需要使用transaction用于reference to the activity’s fragment manager.
add(int containerViewId, Fragment fragment)
: 将一个新的 Fragment 添加到指定的容器中。replace(int containerViewId, Fragment fragment)
: 替换指定容器中的现有 Fragment。remove(Fragment fragment)
: 从 UI 中移除指定的 Fragment。addToBackStack(String name)
: 将当前事务添加到回退栈,以便用户可以通过后退按钮返回到之前的状态。可以提供一个名称以便于后续管理。commit()
: 提交事务,开始执行所有操作。此时还需要在activity对应的layout中,确保在 Activity 的布局文件中有一个容器来放置fragment
<!-- activity_main.xml -->
<FrameLayout
android:id="@+id/fragment_container"
android:layout_width="match_parent"
android:layout_height="match_parent" />
此外,当一个 Fragment 作为另一个 Fragment 的子 Fragment 时,使用 ChildFragmentManager
。例如在父Fragment中:
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
// 使用 ChildFragmentManager 添加子 Fragment
if (savedInstanceState == null) {
ChildFragment childFragment = new ChildFragment();
getChildFragmentManager().beginTransaction()
.replace(R.id.child_fragment_container, childFragment)
.commit();
}
}
此时是在Fragment中嵌套了一个Fragment,使用ChildFragmentManager
。
AppBar
是位于 Android 应用界面顶部的工具栏区域。下面是最常见的两种AppBar。
ActionBar
是早期版本的应用栏实现,通常由系统自动提供。Toolbar
是更现代、更灵活的应用栏实现,你需要手动添加到布局中并设置为 SupportActionBar
。我们可以通过修改 AndroidManifest.xml
去开启或关闭AppBar:
<activity android:name=".YourActivity"
android:theme="@style/Theme.AppCompat.Light">
</activity>
如果需要使用Toolbar, 为了避免冲突,你需要确保你的应用主题禁用了默认的 ActionBar
。
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
<!-- Customize your theme here. -->
</style>
下面是Toolbar的工作流程:
Toolbar
: 在你的 Activity
或 Fragment
的布局 XML 文件中添加 <androidx.appcompat.widget.Toolbar>
元素。此外,我们也可以通过include
引入自定义的样式。
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
android:elevation="4dp"
android:theme="@style/ThemeOverlay.AppCompat.ActionBar"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light"/>
Activity
中将其设置为 SupportActionBar
: 在你的 Activity
的 onCreate()
方法中,找到 Toolbar
的实例,并使用 setSupportActionBar()
方法将其设置为 Activity
的支持操作栏。
public class MyActivity extends AppCompatActivity {
private Toolbar toolbar;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_my);
toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
}
}
res/menu
目录下创建一个 XML 文件(例如 main_menu.xml
),定义你的菜单项。
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/action_settings"
android:orderInCategory="100"
android:title="设置"
app:showAsAction="never" />
<item
android:id="@+id/action_search"
android:icon="@drawable/ic_search"
android:orderInCategory="50"
android:title="搜索"
app:showAsAction="ifRoom" />
</menu>
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.main_menu, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.action_settings:
// 处理设置按钮的点击事件
Toast.makeText(this, "点击了设置", Toast.LENGTH_SHORT).show();
return true;
case R.id.action_search:
// 处理搜索按钮的点击事件
Toast.makeText(this, "点击了搜索", Toast.LENGTH_SHORT).show();
return true;
default:
return super.onOptionsItemSelected(item);
}
}
通过这样的设计,我们可以得到一个带有ACTION的Toolbar。
此外,我们还可以使用Fragment来添加标签(Tabs),以便它们可以随意折叠或滚动。
这是一个简单的 Fragment 示例,用于显示每个标签页的内容。
import ...
public class TabFragment extends Fragment {
private String content;
public TabFragment(String content) {
this.content = content;
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(android.R.layout.simple_textview, container, false);
TextView textView = view.findViewById(android.R.id.text1);
textView.setText(content);
return view;
}
}
我们需要在activity_main.xml
中设计:
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appBarLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:layout_scrollFlags="scroll|enterAlways|snap"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
android:text="我的应用" />
<com.google.android.material.tabs.TabLayout
android:id="@+id/tabLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:tabMode="fixed"
app:tabGravity="fill" />
</com.google.android.material.appbar.AppBarLayout>
<androidx.viewpager.widget.ViewPager
android:id="@+id/viewPager"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
app:layout_scrollFlags="scroll|enterAlways"
: 定义了 Toolbar
在滚动时的行为 scroll。 scroll
表示这个 Toolbar
应该跟随滚动事件一起滚动出屏幕。enterAlways
表示当内容向上滚动时,这个 Toolbar
会立即显示出来,即使内容还没有完全滚动到顶部。app:layout_behavior="@string/appbar_scrolling_view_behavior"
: 告诉 ViewPager
它的滚动应该与 AppBarLayout
协调。当 ViewPager
的内容向上滚动时,会触发 AppBarLayout
的折叠或滚动。
MainActivity.java
中,
private Toolbar toolbar;
private TabLayout tabLayout;
private ViewPager viewPager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
tabLayout = findViewById(R.id.tabLayout);
viewPager = findViewById(R.id.viewPager);
// 创建 Fragment 列表
List<Fragment> fragmentList = new ArrayList<>();
fragmentList.add(new TabFragment("标签页 1 的内容"));
fragmentList.add(new TabFragment("标签页 2 的内容"));
fragmentList.add(new TabFragment("标签页 3 的内容"));
// 创建 FragmentPagerAdapter 的适配器
PagerAdapter adapter = new PagerAdapter(getSupportFragmentManager(), fragmentList);
viewPager.setAdapter(adapter);
// 将 TabLayout 和 ViewPager 关联起来
tabLayout.setupWithViewPager(viewPager);
}
// FragmentPagerAdapter 的适配器
private static class PagerAdapter extends FragmentPagerAdapter {
private final List<Fragment> fragmentList;
public PagerAdapter(FragmentManager fm, List<Fragment> fragmentList) {
super(fm);
this.fragmentList = fragmentList;
}
@NonNull
@Override
public Fragment getItem(int position) {
return fragmentList.get(position);
}
@Override
public int getCount() {
return fragmentList.size();
}
@Nullable
@Override
public CharSequence getPageTitle(int position) {
switch (position) {
case 0:
return "标签一";
case 1:
return "标签二";
case 2:
return "标签三";
default:
return null;
}
}
}
我们创建了一个继承自 FragmentPagerAdapter
的内部类 PagerAdapter
。
FragmentPagerAdapter
的构造函数需要 FragmentManager
。 我们通常传递 getSupportFragmentManager()
。getItem(int position)
: 返回指定位置的 Fragment
实例。getCount()
: 返回标签页的总数。getPageTitle(int position)
: 对于 FragmentPagerAdapter
,你需要重写 getPageTitle()
方法来提供每个标签页的标题。
Snackbar
是 Android 中一种用于向用户提供操作反馈的控件,它会短暂地显示在屏幕底部,并且可以包含一个可选的操作按钮。与 Toast
类似,Snackbar
不会阻塞用户界面。 Snackbar
的一个主要优点是可以提供一个操作项,允许用户撤销或执行与先前操作相关的操作。
假设我们有一个场景,用户刚刚删除了一条数据。 我们希望显示一个 Snackbar
,提示用户数据已删除,并提供一个 "UNDO" 按钮,如果用户点击该按钮,则显示一个 Toast
来模拟撤销操作。
创建一个Snackbar,并将其显示:
int duration = Snackbar.LENGTH_SHORT
Snackbar snackbar = Snackbar.make(findViewById(R.id.coordinator), "数据已删除", duration);
snackbar.setAction("UNDO", new View.OnClickListener() {
@Override
public void onClick(View view) {
Toast.makeText(this, "UNDO", Toast.LENGTH_SHORT).show();
}
});
snackbar.show();
创建 Snackbar
: 使用 Snackbar.make(View view, CharSequence text, int duration)
方法创建一个 Snackbar
实例。
View view
: Snackbar
需要一个 View
来找到其父布局,并用于定位 Snackbar
。 通常,你可以传递触发 Snackbar
显示的 View
(例如这里的 findViewById(R.id.coordinator)
),或者任何当前 Activity 布局中的 View
。CharSequence text
: Snackbar
上要显示的消息文本,这里是 "数据已删除"。int duration
: Snackbar
的显示时长。 常用的值有:
Snackbar.LENGTH_SHORT
: 短暂显示。Snackbar.LENGTH_LONG
: 较长时间显示。Snackbar.LENGTH_INDEFINITE
: 无限期显示,直到用户执行操作或手动关闭。 注意:如果使用 LENGTH_INDEFINITE
,通常需要设置一个 Action,否则用户无法关闭 Snackbar
。snackbar.setAction(CharSequence text, View.OnClickListener listener)
方法添加一个操作按钮。
CharSequence text
: 操作按钮上显示的文本,这里是 "UNDO"。View.OnClickListener listener
: 一个 OnClickListener
接口的实现,定义了当操作按钮被点击时要执行的操作。 在这个例子中,我们创建了一个匿名内部类来实现 OnClickListener
,并在其 onClick()
方法中调用了 undoDelete()
方法。Toast
显示 "UNDO"。 在实际应用中,这里会执行撤销删除的逻辑,例如将删除的数据恢复。snackbar.show()
: 最后调用 show()
方法来显示 Snackbar
。
RecyclerView
是一个更加强大和灵活的滚动控件,专门用于高效地展示大型数据集。可以说是一个增强版的ListView。
它的主要功能是:视图回收 (View Recycling): RecyclerView
不会为每一个列表项都创建新的视图。当列表项滚动出屏幕时,RecyclerView
会回收这些视图,并将它们重新用于即将进入屏幕的新列表项。这样就避免了频繁创建和销毁视图带来的性能损耗。
RecyclerView
的工作流程可以大致分为以下几个步骤,我们通过Pizza的卡片列表展示为例。
我们的第一步就是:创建Pizza数据类。
//Pizza.java
public class Pizza {
private String name;
private String imageUrl;
public Pizza(String name, String imageUrl) {
this.name = name;
this.imageUrl = imageUrl;
}
public String getName() {
return name;
}
public String getImageUrl() {
return imageUrl;
}
}
接下来我们尝试创建ViewHolder,用于存放我们的每一个卡片对象。(这里假设了CardView中的id)
// PizzaViewHolder.java
import ...
public class PizzaViewHolder extends RecyclerView.ViewHolder {
ImageView pizzaImage;
TextView pizzaName;
public PizzaViewHolder(@NonNull View itemView) {
super(itemView);
pizzaImage = itemView.findViewById(R.id.pizza_image);
pizzaName = itemView.findViewById(R.id.pizza_name);
}
}
下一步是创建 Adapter,这个过程从你的数据源(Pizza
)中获取数据,并将这些数据转换成 RecyclerView
可以显示的视图。
// PizzaAdapter.java
import ...
public class PizzaAdapter extends RecyclerView.Adapter<PizzaViewHolder> {
private List<Pizza> pizzaList;
private Context context;
public PizzaAdapter(Context context, List<Pizza> pizzaList) {
this.pizzaList = pizzaList;
this.context = context;
}
@NonNull
@Override
public PizzaViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_pizza, parent, false); // item_pizza是存放CardView的样式的
return new PizzaViewHolder(itemView);
}
@Override
public void onBindViewHolder(@NonNull PizzaViewHolder holder, int position) {
Pizza currentPizza = pizzaList.get(position);
holder.pizzaName.setText(currentPizza.getName()); // 载入到每个ViewHolder的文本中
Picasso.get().load(currentPizza.getImageUrl()).into(holder.pizzaImage); // 载入到每个ViewHolder的图片中
}
@Override
public int getItemCount() {
return pizzaList.size();
}
}
onCreateViewHolder()
方法: 这个方法负责创建 ViewHolder
。ViewHolder
是一个用来保存列表中单个条目视图的引用的对象。你可以把它想象成一个“盒子”,ViewHolder是一个列表中的项的最小单位,用来存放一个 Pizza 的图片和文字描述的 ImageView
和 TextView
。 当 RecyclerView
需要显示一个新的条目时,它会调用这个方法来创建一个新的 ViewHolder
。onBindViewHolder()
方法: 这个方法负责将数据绑定到 ViewHolder
的视图上。 当需要显示特定位置的 Pizza 信息时,RecyclerView
会调用这个方法,并传入对应的 ViewHolder
和数据的位置。你需要在 onBindViewHolder()
中从你的数据源中取出对应位置的 Pizza 对象,然后将其图片和文字信息设置到 ViewHolder
中 ImageView
和 TextView
上。
最后是在PizzaFragment
中初始化我们的RecyclerView
并设置 GridLayoutManager
。
// PizzaFragment.java
import ...
public class PizzaFragment extends Fragment {
private RecyclerView recyclerView;
private PizzaAdapter adapter;
private List<Pizza> pizzaList;
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_pizza, container, false);
recyclerView = view.findViewById(R.id.pizza_recycler_view);
// 初始化数据
pizzaList = new ArrayList<>();
pizzaList.add(new Pizza("Pepperoni Pizza", "https://example.com/pepperoni.jpg"));
pizzaList.add(new Pizza("Margherita Pizza", "https://example.com/margherita.jpg"));
pizzaList.add(new Pizza("Vegetarian Pizza", "https://example.com/vegetarian.jpg"));
// 添加更多 Pizza ...
// 创建 GridLayoutManager,设置列数为 2
GridLayoutManager layoutManager = new GridLayoutManager(getContext(), 2);
recyclerView.setLayoutManager(layoutManager);
// 创建 Adapter 实例
adapter = new PizzaAdapter(getContext(), pizzaList);
recyclerView.setAdapter(adapter);
return view;
}
}
总结一下我们的过程:
PizzaFragment
被创建时,onCreateView
方法会被调用。LayoutInflater
会加载 fragment_pizza.xml
布局文件,这个布局文件中包含了 RecyclerView
。RecyclerView
的初始化和 LayoutManager
的设置:
onCreateView
中,我们找到 RecyclerView
的实例。GridLayoutManager
的实例,并指定了网格的列数为 2。GridLayoutManager
负责以网格的形式排列 RecyclerView
的列表项。recyclerView.setLayoutManager(layoutManager)
,我们将 GridLayoutManager
设置给 RecyclerView
,告诉它使用网格布局。Adapter
的创建和设置:
PizzaAdapter
的实例,并将 Pizza 数据列表传递给它。recyclerView.setAdapter(adapter)
,我们将 Adapter
设置给 RecyclerView
。RecyclerView
会使用这个 Adapter
来获取需要展示的视图。ViewHolder
的创建 (onCreateViewHolder
):
RecyclerView
需要显示新的列表项时(最初显示在屏幕上或滚动进入屏幕时),它会调用 Adapter
的 onCreateViewHolder
方法。onCreateViewHolder
中,我们使用 LayoutInflater
加载 item_pizza.xml
布局文件。由于 item_pizza.xml
的根布局是 CardView
,所以这里创建的 ViewHolder
持有的 itemView
就是一个 CardView
。onBindViewHolder
):
RecyclerView
会调用 Adapter
的 onBindViewHolder
方法,并传入对应的 ViewHolder
实例和数据的位置。onBindViewHolder
中,我们从 pizzaList
中取出对应位置的 Pizza
对象,然后将其 name
和 imageUrl
设置到 ViewHolder
中 CardView
内部的 TextView
和 ImageView
上。RecyclerView
使用 GridLayoutManager
来确定每个列表项(即 CardView
)在屏幕上的位置和大小,从而实现两列的网格布局。Adapter
负责提供每个位置上需要显示的 CardView
,并将数据填充到 CardView
内部的视图中。
下面是另一个例子,为PastaFragment应用RecyclerView。
根据之前学到的RecyclerView的工作过程,我们可以得到如下的过程:
View view = inflater.inflate(R.layout.fragment_pasta, container, false);
recyclerView = view.findViewById(R.id.pizza_recycler_view);
LayoutInflater 是一个builder,负责根据 XML 蓝图创建实际的 UI 组件。
inflate()
方法就像是“建造”的过程。
你需要告诉它:
R.layout.xxx
)parent
或 container
)attachToRoot
参数)当你通过 inflate()
获得了 View
对象后,你就可以使用 findViewById()
方法在这个 View
对象中查找特定的子视图。例如,在 Fragment
中,view.findViewById(R.id.pizza_recycler_view)
会在 fragment_pizza.xml
布局中查找 id
为 pizza_recycler_view
的 RecyclerView
。
那看到题目这里只有一句,推测Layout文件中根元素就是<androidx.recyclerview.widget.RecyclerView>
。
最后得到的是RecyclerView
,那我们这里就是填入PasteFragment的蓝图,所以这里是:
RecyclerView pasteRecycler = (RecyclerView) inflater.inflate(R.layout.fragment_paste, container, false);
Adapter
设置给 RecyclerView
,于是我们可以回答:
CapionedImageAdapter adapter = new CapionedImageAdapter(pastaNames, pastaImages);
pasteRecycler.setAdapter(adapter);
这里的CapionedImageAdapter
是题目定义的Adapter,而它的构造函数中,设计了对数组数据的处理。
RecyclerView
,并将其应用到 RecyclerView
上。
GridLayoutManager layoutManager = new GridLayoutManager(getActivity(), 2);
pasteRecycler.setLayoutManager(layoutManager);
Android使用SQLite数据库来持久化数据。(Android uses SQLite databases to persist data)
原因有三:
在 Android 中,SQLite 是一个轻量级的、嵌入式的关系型数据库。这意味着你的应用程序可以直接在设备上存储和管理结构化的数据,而无需单独的数据库服务器。
我们一般使用 SQLiteOpenHelper
类来辅助创建和管理数据库。 SQLiteOpenHelper
提供了一种标准的方式来处理数据库的创建和版本升级。
我们需要创建一个继承自 SQLiteOpenHelper
的子类,并重写以下两个重要的方法:
onCreate(SQLiteDatabase db)
: 当数据库第一次被创建时调用。你可以在这个方法中执行创建表结构的 SQL 语句。onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)
: 当数据库版本升级时调用。你可以在这个方法中执行修改表结构、迁移数据等的 SQL 语句。以如下作为例子我们来了解Android中如何使用数据库。
假设我们要创建一个名为 "mydatabase.db" 的数据库,并在其中创建一个名为 "users" 的表,包含 "id" (整型,主键,自增长) 和 "name" (文本类型) 两个字段。
import ...
public class DatabaseHelper extends SQLiteOpenHelper {
private static final String DATABASE_NAME = "mydatabase.db";
private static final int DATABASE_VERSION = 1;
public static final String TABLE_USERS = "users";
public static final String COLUMN_ID = "id";
public static final String COLUMN_NAME = "name";
private static final String CREATE_TABLE_USERS = "CREATE TABLE IF NOT EXISTS" + TABLE_USERS + "("
+ COLUMN_ID + " INTEGER PRIMARY KEY AUTOINCREMENT,"
+ COLUMN_NAME + " TEXT"
+ ")";
public DatabaseHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(CREATE_TABLE_USERS);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
db.execSQL("DROP TABLE IF EXISTS " + TABLE_USERS);
onCreate(db);
}
}
在创建数据库的时候,使用SQL创建了数据表,而在更新版本的时候将表清除。
在数据库中,使用 SQLiteDatabase
的 update()
方法更新已有的数据行。
ContentValues updateValues = new ContentValues();
updateValues.put(DatabaseHelper.COLUMN_NAME, "Charlie");
String whereClause = DatabaseHelper.COLUMN_ID + " = ?";
String[] whereArgs = {"1"}; // 更新 ID 为 1 的行
db.update(DatabaseHelper.TABLE_USERS, updateValues, whereClause, whereArgs);
要操作数据库,需要获取 SQLiteDatabase
的实例。 可以通过 DatabaseHelper
的 getWritableDatabase()
(用于写入数据) 或 getReadableDatabase()
(用于读取数据) 方法来获取。
DatabaseHelper dbHelper = new DatabaseHelper(this);
SQLiteDatabase db = dbHelper.getWritableDatabase(); // 获取可写数据库实例
SQLiteDatabase readableDb = dbHelper.getReadableDatabase(); // 获取只读数据库实例
要从数据库中查询数据,可以使用 SQLiteDatabase
的 query()
方法。 query()
方法提供了多种参数来指定查询条件。
Cursor cursor = db.query("mydatabase.db",
new String[]{"id", "name"}, // 要查询的列
selection, // WHERE 子句
selectionArgs, // WHERE 子句的参数
groupBy, // GROUP BY 子句
having, // HAVING 子句
orderBy); // ORDER BY 子句
Cursor
是一个接口,它提供了访问数据库查询结果集的能力。你可以将 Cursor
想象成一个指向查询结果集的指针,你可以移动这个指针来逐行访问数据。
你需要使用 Cursor
的方法来移动到每一行并获取数据。
if (cursor != null && cursor.moveToFirst()) {
do {
int id = cursor.getInt(cursor.getColumnIndex(DatabaseHelper.COLUMN_ID));
String name = cursor.getString(cursor.getColumnIndex(DatabaseHelper.COLUMN_NAME));
// 处理获取到的数据,例如打印到日志
Log.d("Database", "ID: " + id + ", Name: " + name);
} while (cursor.moveToNext()); // 移动到下一行
}
// 始终记得关闭 Cursor 和数据库
if (cursor != null) {
cursor.close();
}
db.close();
cursor.moveToFirst()
: 将 Cursor
移动到结果集的第一行。如果结果集为空,则返回 false
。cursor.moveToNext()
: 将 Cursor
移动到结果集的下一行。如果已经到达末尾,则返回 false
。cursor.getColumnIndex(String columnName)
: 获取指定列名在结果集中的索引位置。cursor.getInt(int columnIndex)
: 获取指定索引位置的整型数据。cursor.getString(int columnIndex)
: 获取指定索引位置的字符串数据。cursor.close()
: 释放 Cursor
占用的资源.
假设我们已经有了一个从数据库查询用户数据的 Cursor
,我们想要将其直接显示在一个 ListView
中。此时我们就需要使用一个 CursorAdapter
将 Cursor
直接绑定到 ListView
或 Spinner
等 AdapterView 上。
import ...
public class UserListActivity extends AppCompatActivity {
private DatabaseHelper dbHelper;
private Cursor userCursor;
private SimpleCursorAdapter userAdapter;
private ListView userListView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_user_list);
userListView = findViewById(R.id.user_list_view);
dbHelper = new DatabaseHelper(this);
}
@Override
protected void onResume() {
super.onResume();
populateListView();
}
private void populateListView() {
SQLiteDatabase db = dbHelper.getReadableDatabase();
String[] projection = {
DatabaseHelper.COLUMN_ID,
DatabaseHelper.COLUMN_NAME
};
userCursor = db.query(
DatabaseHelper.TABLE_USERS,
projection,
null,
null,
null,
null,
null
);
// 定义 Cursor 中要读取的列和 ListView 中要显示这些列的 TextView 的 ID
String[] fromColumns = {DatabaseHelper.COLUMN_NAME};
int[] toViews = {android.R.id.text1}; // 使用 Android 提供的简单 TextView
// 创建 SimpleCursorAdapter
userAdapter = new SimpleCursorAdapter(
this,
android.R.layout.simple_list_item_1, // 使用 Android 提供的简单列表项布局
userCursor,
fromColumns,
toViews,
0 // flag (通常为 0)
);
// 将 Adapter 设置到 ListView
userListView.setAdapter(userAdapter);
}
@Override
protected void onDestroy() {
super.onDestroy();
if (userCursor != null) {
userCursor.close();
}
if (dbHelper != null) {
dbHelper.close();
}
}
}
Cursor
对象 userCursor
。SimpleCursorAdapter
。 SimpleCursorAdapter
是 CursorAdapter
的一个简单实现,适用于将 Cursor
中的数据直接映射到 TextView
上。SimpleCursorAdapter
的构造函数需要以下参数:
context
: 上下文对象。layout
: 用于显示每一行的布局文件的 ID。 android.R.layout.simple_list_item_1
是 Android 提供的包含一个 TextView
的简单布局。cursor
: 要绑定的 Cursor
对象。fromColumns
: 一个字符串数组,包含 Cursor
中要读取的列名。toViews
: 一个整型数组,包含布局文件中用于显示这些列数据的 TextView
的 ID。 android.R.id.text1
是 simple_list_item_1
布局中 TextView
的 ID。flags
: 指定适配器行为的标志,通常为 0。userAdapter
设置到 ListView
上。当然如果我们先将数据库查询结果转换为一个 List<String>
(假设我们只显示用户名),那么我们就不能使用 CursorAdapter
了。
import ...
public class UserListActivity extends AppCompatActivity {
private DatabaseHelper dbHelper;
private ListView userListView;
private ArrayAdapter<String> userAdapter;
private List<String> userNames;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_user_list);
userListView = findViewById(R.id.user_list_view);
dbHelper = new DatabaseHelper(this);
}
@Override
protected void onResume() {
super.onResume();
populateListView();
}
private void populateListView() {
SQLiteDatabase db = dbHelper.getReadableDatabase();
Cursor cursor = db.query(
DatabaseHelper.TABLE_USERS,
new String[]{DatabaseHelper.COLUMN_NAME}, // 只查询用户名
null,
null,
null,
null,
null
);
userNames = new ArrayList<>();
if (cursor != null && cursor.moveToFirst()) {
do {
String name = cursor.getString(cursor.getColumnIndex(DatabaseHelper.COLUMN_NAME));
userNames.add(name);
} while (cursor.moveToNext());
cursor.close();
}
// 创建 ArrayAdapter
userAdapter = new ArrayAdapter<>(
this,
android.R.layout.simple_list_item_1,
userNames
);
// 将 Adapter 设置到 ListView
userListView.setAdapter(userAdapter);
}
@Override
protected void onDestroy() {
super.onDestroy();
if (dbHelper != null) {
dbHelper.close();
}
}
}
总的来说:
Cursor
时,并且想要直接将 Cursor
的数据展示在 AdapterView
上,CursorAdapter
(或其子类如 SimpleCursorAdapter
) 是一个非常方便且高效的选择。 CursorAdapter
可以高效地处理 Cursor
的数据,并且当 Cursor
的数据发生变化时,它可以自动更新 AdapterView
的显示。List
,然后使用 ArrayAdapter
、BaseAdapter
或 RecyclerView.Adapter
会更加灵活。 这种方式下,你需要手动管理数据的更新。
在我们实际应用中,经常遇到我们更新了数据库后,之前显示的数据需要重新获取的问题。
Cursor
可以被看作是一个指向数据库查询结果集的指针。 当你执行数据库查询时,Cursor
会保存查询结果的一个快照。 即使数据库中的数据发生了改变,原有的 Cursor
对象仍然指向的是旧的数据集。 因此,你需要执行一个新的查询来获取最新的数据,并用新的 Cursor
来更新适配器。
以下是替换 Cursor
的一般步骤:
Cursor
: 在数据库更新操作完成后,你需要执行一个新的查询来获取最新的数据。 这个查询应该与之前用于生成旧 Cursor
的查询条件相同,以获取更新后的结果集。// 假设 dbHelper 是你的 DatabaseHelper 实例
SQLiteDatabase db = dbHelper.getReadableDatabase();
Cursor newCursor = db.query(
DatabaseHelper.TABLE_USERS, // 表名
null, // 返回所有列
null, // WHERE 子句,这里假设没有条件
null, // WHERE 子句的参数
null, // GROUP BY 子句
null, // HAVING 子句
null // ORDER BY 子句
);
CursorAdapter
: 对于 CursorAdapter
(包括 SimpleCursorAdapter
),你可以使用 swapCursor()
方法来替换底层的 Cursor
。 swapCursor()
方法会关闭旧的 Cursor
(如果存在),并通知观察者数据已经改变,从而触发 ListView
或 RecyclerView
的更新。我们还可以使用changeCursor()
方法,但是需要自己关闭旧的 Cursor
。// 假设 userAdapter 是你的 SimpleCursorAdapter 实例
Cursor oldCursor = userAdapter.swapCursor(newCursor);
// 使用changeCursor,并手动关闭旧的 Cursor
userAdapter.changeCursor(newCursor);
if (oldCursor != null && oldCursor != newCursor) {
oldCursor.close();
}
服务是一种在后台执行长时间运行操作而不提供用户界面的应用程序组件。
Android 中的服务主要分为两种类型:
startService()
方法时,服务将被启动。启动后,服务可以在后台无限期地运行,即使启动它的组件已被销毁。通常,启动服务会执行一个单一的操作,并在完成后自行停止。bindService()
方法绑定到服务时,服务是绑定的。绑定服务提供了一个客户端-服务器接口,允许组件与服务进行交互、发送请求和接收结果,甚至是跨进程的。绑定服务只有在至少有一个组件绑定到它时才会运行,当所有客户端都取消绑定后,服务就会被销毁。一个服务可以同时是启动的和绑定的。
以下是服务生命周期中的主要回调方法:
onCreate()
: 这是服务第一次创建时调用的。onStartCommand(Intent intent, int flags, int startId)
: 当另一个组件(如 Activity)通过调用 startService()
请求启动服务时,系统会调用此方法。这是启动服务特有的回调。onBind(Intent intent)
: 当另一个组件想通过调用 bindService()
与服务绑定时,系统会调用此方法。这是绑定服务特有的回调。这个接口通常是一个 IBinder
对象。onUnbind(Intent intent)
: 当所有客户端都与服务取消绑定时调用。返回值布尔值表示服务在有新的连接到达时是否希望调用 onRebind()
。onDestroy()
: 当服务不再被使用且即将被销毁时,系统会调用此方法。这是服务生命周期的最后一个回调。你应该在这里清理所有资源,例如取消注册监听器、释放网络连接等。
简单来说,对于启动服务: onCreate()
-> onStartCommand()
-> onDestroy()
对于绑定服务: onCreate()
-> onBind()
-> onUnbind()
-> onDestroy()
让我们创建一个使用 GPS 服务的示例,该服务将在后台获取位置更新并在有新的位置信息时通知我们。
首先,在 AndroidManifest.xml
文件中添加访问 GPS 的权限,并声明 Service(IDE自动):
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<service android:name=".GPSService" />
接下来是在创建好的server里面使用GPS位置信息:
import ...
public class GPSService extends Service {
private static final String TAG = "GPSService";
private LocationManager locationManager;
private LocationListener locationListener;
@Override
public void onCreate() {
super.onCreate();
Log.d(TAG, "onCreate");
locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
locationListener = new MyLocationListener();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d(TAG, "onStartCommand");
startLocationUpdates();
}
private void startLocationUpdates() {
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
Log.e(TAG, "Location permission not granted");
return;
}
locationManager.requestLocationUpdates(
LocationManager.GPS_PROVIDER,
5000, // 最小时间间隔 (毫秒)
10, // 最小距离间隔 (米)
locationListener
);
}
private void stopLocationUpdates() {
locationManager.removeUpdates(locationListener);
}
@Override
public void onDestroy() {
super.onDestroy();
Log.d(TAG, "onDestroy");
stopLocationUpdates();
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null; // 这个服务不提供绑定
}
private class MyLocationListener implements LocationListener {
@Override
public void onLocationChanged(Location location) {
Log.d(TAG, "Location changed: " + location.getLatitude() + ", " + location.getLongitude());
// 在这里处理新的位置信息,例如发送广播或更新 UI
}
@Override
public void onStatusChanged(String provider, int status, Bundle extras) {
Log.d(TAG, "Status changed: " + provider + " - " + status);
}
@Override
public void onProviderEnabled(String provider) {
Log.d(TAG, "Provider enabled: " + provider);
}
@Override
public void onProviderDisabled(String provider) {
Log.d(TAG, "Provider disabled: " + provider);
}
}
}
在 MainActivity.java
中,可以使用 startService()
和 stopService()
方法来启动和停止 GPSService
:
import ...
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button startButton = findViewById(R.id.startButton);
Button stopButton = findViewById(R.id.stopButton);
startButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
startGPSService();
}
});
stopButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
stopGPSService();
}
});
}
private void startGPSService() {
Intent serviceIntent = new Intent(this, GPSService.class);
startService(serviceIntent);
}
private void stopGPSService() {
Intent serviceIntent = new Intent(this, GPSService.class);
stopService(serviceIntent);
}
}
GPSService
的 onCreate()
方法(如果服务尚未创建)。GPSService
的 onStartCommand()
方法。GPSService
开始请求位置更新。MyLocationListener
的 onLocationChanged()
方法会被调用,你可以在这里处理新的位置信息。MainActivity
中点击 "停止服务" 按钮。GPSService
的 onDestroy()
方法,停止位置更新。
AsyncTask
允许Android在后台执行耗时的操作,同时不会阻塞主线程(UI 线程),并在后台任务完成后更新 UI。
AsyncTask
提供了一种简单的实现异步操作的方式。 它将异步任务的执行过程分解为几个步骤,并在不同的线程上执行这些步骤。 下面是按照顺序执行的几种常见方法:
onPreExecute()
(UI 线程执行):
doInBackground(Params... params)
(后台线程执行):
onPreExecute()
执行完毕后立即被调用,运行在 后台线程。publishProgress(Progress...)
方法来发布进度更新。onPostExecute()
方法。onProgressUpdate(Progress... values)
(UI 线程执行):
publishProgress()
后被调用,运行在 UI 线程。ProgressBar
的进度。onPostExecute(Result result)
(UI 线程执行):
doInBackground()
方法执行完成后被调用,运行在 UI 线程。doInBackground()
方法的返回值作为参数。假设我们想要模拟从网络下载一张图片并在 ImageView
中显示。
import ...
public class DownloadImageActivity extends AppCompatActivity {
private ImageView imageView;
private ProgressBar progressBar;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_download_image);
imageView = findViewById(R.id.imageView);
progressBar = findViewById(R.id.progressBar);
// 启动异步任务下载图片
new DownloadImageTask().execute("sample.gif");
}
private class DownloadImageTask extends AsyncTask<String, Void, Bitmap> {
@Override
protected void onPreExecute() {
progressBar.setVisibility(View.VISIBLE); // 显示进度条
}
@Override
protected Bitmap doInBackground(String... urls) {
String imageUrl = urls[0];
Bitmap bitmap = null;
try {
URL url = new URL(imageUrl);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setDoInput(true);
connection.connect();
InputStream input = connection.getInputStream();
bitmap = BitmapFactory.decodeStream(input);
} catch (IOException e) {
e.printStackTrace();
}
return bitmap;
}
@Override
protected void onPostExecute(Bitmap result) {
progressBar.setVisibility(View.GONE); // 隐藏进度条
if (result != null) {
imageView.setImageBitmap(result); // 将下载的图片设置到 ImageView
} else {
Toast.makeText(DownloadImageActivity.this, "下载图片失败", Toast.LENGTH_SHORT).show();
}
}
}
}
onPreExecute()
: 在后台任务开始前,我们将 ProgressBar
设置为可见。doInBackground()
: 在后台下载并解码为 Bitmap
对象并返回。这里是在后台线程执行,不能直接操作 ImageView
onPostExecute()
: 接收 doInBackground()
返回的 Bitmap
对象。首先隐藏 ProgressBar。如果 Bitmap 不为空,则将其设置到 ImageView 上,从而更新 UI。如果下载失败,则显示一个 Toast 消息。
Note: 需要注意的是,AsyncTask
已经被标记为 deprecated(不推荐使用),Google 推荐使用更现代的并发工具,如 Coroutines
或 RxJava
。
Navigation Drawer(导航抽屉)是一个从屏幕边缘滑出的面板,其中包含应用程序的主要导航选项。
除去各种Fragment和样式,我们来看如何应用一个Drawer到Activity中:
import ...
public class MainActivity extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener {
private DrawerLayout drawerLayout;
private NavigationView navigationView;
private Toolbar toolbar;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
drawerLayout = findViewById(R.id.drawer_layout);
navigationView = findViewById(R.id.nav_view);
navigationView.setNavigationItemSelectedListener(this);
ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(this, drawerLayout, toolbar,
R.string.navigation_drawer_open, R.string.navigation_drawer_close);
drawerLayout.addDrawerListener(toggle);
toggle.syncState();
// 设置默认选中项 (可选)
navigationView.setCheckedItem(R.id.nav_item1);
// 初始化Fragment (省略)
}
@Override
public void onBackPressed() {
if (drawerLayout.isDrawerOpen(GravityCompat.START)) {
drawerLayout.closeDrawer(GravityCompat.START);
} else {
super.onBackPressed();
}
}
@Override
public boolean onNavigationItemSelected(@NonNull MenuItem item) {
int id = item.getItemId();
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction transaction = fragmentManager.beginTransaction();
if (id == R.id.nav_item1) {
transaction.replace(R.id.fragment_container, new ItemOneFragment());
// 处理选项一的点击事件,转换Fragment
} else if (id == R.id.nav_item2) {
startActivity(new Intent(this, ItemTwoFragment.class));
// 处理选项二的点击事件,跳转
}
transaction.commit();
drawerLayout.closeDrawer(GravityCompat.START);
return true;
}
}
onCreate()
方法中,我们获取了 DrawerLayout
、NavigationView
和 Toolbar
的实例。setSupportActionBar(toolbar)
将 Toolbar
设置为 Activity 的 ActionBar。NavigationView.OnNavigationItemSelectedListener
接口,并将其设置为 NavigationView
的监听器,以便在菜单项被点击时接收回调。ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(...)
创建了一个 ActionBarDrawerToggle
的实例,它需要以下参数:
Activity
: 当前的 Activity。DrawerLayout
: 我们的 DrawerLayout
实例。Toolbar
: 我们的 Toolbar
实例。openDrawerContentDescRes
: 用于辅助功能的字符串资源,描述打开抽屉的操作。closeDrawerContentDescRes
: 用于辅助功能的字符串资源,描述关闭抽屉的操作。drawerLayout.addDrawerListener(toggle)
: 将 toggle
添加为 DrawerLayout
的监听器,以便它可以监听抽屉的状态变化。toggle.syncState()
: 同步抽屉的状态和菜单图标。这会在首次创建 Activity 或配置更改后更新菜单图标的状态。onBackPressed()
方法,以便在抽屉打开时先关闭抽屉,而不是直接退出 Activity。onNavigationItemSelected(@NonNull MenuItem item)
方法在导航抽屉中的菜单项被点击时调用。
item.getItemId()
获取被点击菜单项的 ID。if-else if
语句来判断点击了哪个菜单项,并执行相应的操作(这里只是简单地显示一个 Toast)。drawerLayout.closeDrawer(GravityCompat.START)
: 在处理完点击事件后关闭导航抽屉。true
表示我们已经处理了点击事件。
在处理中,我们采用了两种方法:
transaction.replace(R.id.fragment_container, new YourFragment());
方法来替换 fragment_container
中的内容。最后,我们调用 transaction.commit();
来提交事务,使 Fragment 的替换操作生效。startActivity(new Intent(this, YourFragment.class));
方法使用Intent
跳转到下一页面。