文章目录
前言
一、View 和 Activity
1. View
(1)常见的 View 组件分类
(2)常用的UI组件
2. Activity 和 Fragment
(1)Activity
(2)Fragment
3. 拓展:监听(Listener)、回调(CallBack)和 Handler
(1)监听器
(2)回调
(3)Handler
二、Intent
1. Inntent的基本概述
(1)定义
(2)Intent 的关键属性(七大属性)
(3)Intent 的分类
拓展:隐式intent启动activity,如果有多个符合的activity,会都启动吗?
(4)Intent 传值方式
(5)Intent 标志位(Flags)
(6)Intent 返回结果(StartActivityForResult)
(7)拓展:Intent构造方法详解
2. Intent的基本用法
(1)启动 Activity、Service、Broadcast
(2)配置清单文件
三、Service
1. Service 的基础知识
(1)定义
(2)Service 的生命周期方法
(3)Service 的两种启动方式
(4)Service 的分类
2. Service 的基础用法
(1)Service 传参
(2)普通 Service 容易出现的错误
(3)Service 使用注意事项
(4)在 AndroidManifest.xml 中注册 Service
(5)Service 的应用场景
四、BroadcastReceiver
1. BroadcastReceiver 的基础知识
(1)Broadcast 的基本概念
(2)BroadcastReceiver 的生命周期
(3)广播类型
2.BroadcastReceiver 的基本用法
(1)广播接收器的注册方式
(2)发送广播的方式
(3)LocalBroadcastManager(本地广播)
(4)注意事项
五、ContentProvider
1. ContentProvider 的基础知识
(1)定义
(2)ContentProvider 的基本概念
(3)ContentProvider 的核心组件
2. ContentProvider 的基础用法
(1)自定义 ContentProvider 步骤
(2)ContentProvider 的 Uri 匹配机制
(3)ContentProvider 的应用场景
(4)ContentProvider 的优缺点
(5)ContentProvider 的权限控制
(6)ContentProvider 与数据库的关系
(7)ContentProvider 与 ContentResolver 的关系
(8)注意事项
六、AIDL
1. AIDL 的基础知识
(1)定义
(2)AIDL 的基本概念
(3)AIDL 的工作原理
(4)AIDL 的核心组件
(5)AIDL 的数据类型支持
2. AIDL 的基础用法
(1)AIDL 自定义对象示例
(2)AIDL 的同步与异步调用
(3)AIDL 的注册与权限控制
(4)AIDL 的应用场景
(5)AIDL 与 Messenger 的区别
(6)AIDL 开发注意事项
(7)AIDL 的优点与缺点
(8)AIDL 的回调机制(远程回调)
(9)AIDL 的生命周期管理
(10)AIDL 的常见错误与解决办法
(11)总结表格
总结
前言
本文皆在总结安卓基础学习过程中的基础知识、易有困惑。
本人2025应届毕业生,因工作原因学习安卓知识。
本文一为总结,二为分享,如有错误,还望各位大佬指正!
(持续更新中…)
一、View 和 Activity
1. View
在 Android 开发中,视图(View)组件 是构建用户界面的基本元素。它们用于展示 UI 内容、接收用户输入和交互事件,是 Android 应用中最基础也是最重要的组成部分。
(1)常见的 View 组件分类
基础视图组件(Basic Views)
控件 | 描述 |
---|---|
TextView | 显示文本内容 |
EditText | 可编辑的文本输入框 |
Button | 按钮,常用于响应点击事件 |
ImageView | 显示图片资源 |
ProgressBar | 显示进度条 |
CheckBox | 多选框 |
RadioButton | 单选按钮(通常写于 RadioGroup 内) |
ToggleButton / Switch | 切换开关按钮 |
布局容器(ViewGroup)
布局 | 描述 |
---|---|
LinearLayout | 线性布局,线性排列子视图(水平或垂直) |
TableLayout | 表格布局,可控制行数列数(TableRow代表行) |
FrameLayout | 帧布局(常用于 Fragment 容器) |
RelativeLayout | 相对布局,相对定位子视图 |
GridLayout | 网格布局(类似表格) |
ConstraintLayout | 强大的约束布局,支持复杂布局 |
(2)常用的UI组件
TextView及其子类
ImageView及其子类
AdapterView及其子类
ProgressBar及其子类
待补充
2. Activity 和 Fragment
(1)Activity
定义
Activity 是 Android 四大组件之一,代表一个单独的屏幕,是用户与应用交互的基本单元。
每个 Activity 都有一个独立的生命周期,并可以启动另一个 Activity 或被其他组件调用。
核心作用
提供用户操作的界面(通常绑定一个布局文件 .xml)。
管理用户的输入事件、数据展示和页面跳转。
生命周期
生命周期方法 | 调用时机 |
---|---|
onCreate() | 创建 Activity,初始化视图和数据 |
onStart() | Activity 即将可见 |
onResume() | Activity 获取焦点,开始与用户交互 |
onPause() | Activity 失去焦点,但仍然可见(如弹窗) |
onStop() | Activity 不再可见 |
onDestroy() | Activity 销毁前调用 |
onRestart() | Activity 从停止状态重新启动 |
只使用Java代码开发UI界面
// 只使用编程Java代码的方式开发UI界面
public class CodeViewActivity extends Activity {
@Override // 第一次创建该 Activity 时回调该方法
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 创建一个线性布局管理器
LinearLayout layout = new LinearLayout(this);
// 设置该组件显示 layout
super.setContentView(layout);
layout.setOrientation(LinearLayout.VERTICAL); // 设置线性布局方向为垂直排列
// 创建一个TextView
final TextView show = new TextView(this);
// 创建一个按钮
Button button = new Button(this);
button.setText("单击我");
button.setLayoutParams(new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT
));
// 向layout容器中添加TextView、Button
layout.addView(show);
layout.addView(button);
// 为按钮绑定一个事件监听器
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
button.setText("已点击");
show.setText("Hello,Android," + new java.util.Date());
}
});
}
}
XML布局文件 + Java代码
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<TextView
android:id="@+id/show"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:layout_alignParentTop="true"
android:text="hello_world" />
<Button
android:id="@+id/button0"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/show"
android:text="单击我"
android:onClick="clickHandler"/>
</RelativeLayout>
public class MainActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 使用activity_main.xml 文件定义的界面布局
setContentView(R.layout.activity_main);
}
public void clickHandler(View source) {
// 获取UI界面中的 R.id.show 的文本框
TextView tv = (TextView) findViewById(R.id.show);
Button b = findViewById(R.id.button0);
b.setText("已点击");
// 改变文本框的文本内容
tv.setText("Hello Android-" + new java.util.Date());
}
}
以上两种方式得到的结果是相同的,至此明确了两种界面开发方式。
下面是清单文件对应配置信息:AndroidManifest.xml
<!-- android:name指定组件类名 -->
<activity android:name=".code_ui.CodeViewActivity"
android:exported="true">
<intent-filter>
<!-- 指定该Activity是程序的入口 -->
<action android:name="android.intent.action.MAIN" />
<!-- 指定加载该应用时运行该activity -->
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
(2)Fragment
定义
Fragment 是附属于 Activity 的 UI 模块,代表 Activity 中的一部分界面。
一个 Activity 可以包含多个 Fragment,适用于复杂 UI(如双-pane 布局)。
核心作用
实现模块化 UI 设计,提高代码复用性。
支持动态添加、移除、替换等操作,适合响应式布局。
在平板和手机上可以灵活组合不同的 Fragment。
生命周期
生命周期方法 | 调用时机 |
---|---|
onCreate() | 初始化 Fragment |
onCreateView() | 创建并返回 View 层级结构 |
onDestroyView() | 销毁 Fragment 的视图 |
onDetach() | Fragment 与 Activity 解除关联 |
Fragment 具体示例:写一个简单的图书详情展示
图书实体类
public class BookContent {
// 定义一个内部类,作为系统的业务对象
public static class Book {
public Integer id;
public String title;
public String desc;
public Book(Integer id, String title, String desc) {
this.id = id;
this.title = title;
this.desc = desc;
}
@Override
public String toString() {
return title;
}
}
// 使用List集合记录系统所包含的Book对象
public static List<Book> ITEMS = new ArrayList<Book>();
// 使用Map集合记录系统所包含的Book对象
public static Map<Integer, Book> ITEM_MAP = new HashMap<Integer, Book>();
static {
// 使用静态初始化代码,将Book对象添加到List集合、Map集合中
addItem(new Book(1, "三国演义", "由罗贯中编写,基于东汉末年至三国时期的历史事件改编而成。此书以刘备、关羽、张飞三人结义开始,叙述了魏蜀吴三国之间错综复杂的军事斗争与政治权谋。"));
addItem(new Book(2, "水浒传", "一般认为作者为施耐庵(元末明初),描述了宋江领导的一百零八位好汉反抗腐败官府最后被招安的故事。该书是中国历史上第一部用白话文写成的长篇小说之一。"));
addItem(new Book(3, "西游记", "明代吴承恩所著,讲述了唐僧师徒四人取经路上历经九九八十一难的故事。这部作品融合了佛教、道教和民间信仰元素,充满了丰富的想象力和幽默感。"));
}
private static void addItem(Book book) {
ITEMS.add(book);
ITEM_MAP.put(book.id, book);
}
}
activity_book_list.xml 布局文件
<?xml version="1.0" encoding="utf-8"?>
<fragment xmlns:android="http://schemas.android.com/apk/res/android"
android:name="com.example.musicapp.fragment.BookListFragment"
android:id="@+id/book_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp">
</fragment>
activity_book_detail.xml 布局文件
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/book_detail_container"
android:layout_width="match_parent"
android:layout_height="match_parent">
</FrameLayout>
图书列表的 Fragment 组件
public class BookListFragment extends ListFragment {
private Callbacks mCallbacks;
public interface Callbacks {
/**
* 定义一个回调接口,该 Fragment所在的 Activity 需要实现该接口
* 该 Fragment 将通过该接口与它所在的 Activity 交互
* @param id
*/
public void onItemSelected(Integer id);
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 为该 ListFragment 设置 Adapter
setListAdapter(new ArrayAdapter<BookContent.Book>(getActivity(), android.R.layout.simple_list_item_activated_1, android.R.id.text1, BookContent.ITEMS));
}
// 当该Fragment被添加、显示到Activity时,回调该方法
@Override
public void onAttach(@NonNull Context activity) {
super.onAttach(activity);
// 如果Activity没有实现Callbacks接口,抛出异常
if (!(activity instanceof Callbacks)) {
throw new IllegalStateException("BookListFragment 所在的Activity必须实现Callbacks接口!");
}
// 把该Activity当成Callbacks对象
mCallbacks = (Callbacks) activity;
}
// 当该Fragment从它所属的Activity中被删除时回调该方法
@Override
public void onDetach() {
super.onDetach();
//将mCallbacks赋为null。
mCallbacks = null;
}
// 当用户单击某列表项时激发该回调方法
@Override
public void onListItemClick(ListView listView, View view, int position, long id) {
super.onListItemClick(listView, view, position, id);
//激发mCallbacks的onItemSelected方法
mCallbacks.onItemSelected(BookContent.ITEMS.get(position).id);
}
/**
* 设置列表项是否可点击激活
* @param activateOnItemClick true 表示列表项可点击激活
*/
public void setActivateOnItemClick(boolean activateOnItemClick) {
getListView().setChoiceMode(activateOnItemClick ? ListView.CHOICE_MODE_SINGLE : ListView.CHOICE_MODE_NONE);
}
}
下面这个Activity是主程序:
public class BookListActivity extends FragmentActivity implements BookListFragment.Callbacks {
// 定义一个旗标,用于标识该应用是否支持大屏幕
private boolean mTwoPane;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//指定加载R.layout.activity_book_list对应的界面布局文件
//但实际上该应用会根据屏幕分辨率加载不同的界面布局文件
setContentView(R.layout.activity_book_list);
//如果加载的界面布局文件中包含ID为book_detail_container的组件
if (findViewById(R.id.book_detail_container) != null) {
mTwoPane = true;
// ((BookListFragment) getFragmentManager().findFragmentById(R.id.book_list)).setActivate0nItemClick(true);
((BookListFragment) getSupportFragmentManager().findFragmentById(R.id.book_list)).setActivateOnItemClick(true);
}
}
// 实现了回调方法:如果是大屏则用Fragment替换,否则启动新的Activity
@Override
public void onItemSelected(Integer id) {
if (mTwoPane) {
// 创建Bundle,准备向Fragment传入参数
Bundle arguments = new Bundle();
arguments.putInt(BookDetailFragment.ITEM_ID, id);
//创建BookDetailFragment对象
BookDetailFragment fragment = new BookDetailFragment();
//向Fragment传入参数
fragment.setArguments(arguments);
//使用 fragment 替换book_detail_container 容器当前显示的Fragment
getSupportFragmentManager().beginTransaction()
.replace(R.id.book_detail_container, fragment).commit();
} else {
//创建启动 BookDetailActivity的Intent
Intent detailIntent = new Intent(this, BookDetailActivity.class);
//设置传给BookDetailActivity的参数
detailIntent.putExtra(BookDetailFragment.ITEM_ID, id);
//启动Activity
startActivity(detailIntent);
}
}
}
替换book_detail_container 容器的图书详情Fragment
public class BookDetailFragment extends Fragment {
public static final String ITEM_ID = "item_id";
// 保存该Fragment显示的Book对象
BookContent.Book book;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 如果启动该Fragment时包含了ITEM_ID参数
if (getArguments().containsKey(ITEM_ID)) {
book = BookContent.ITEM_MAP.get(getArguments().getInt(ITEM_ID));
}
}
// 重写该方法,该方法返回的View将作为Fragment显示的组件
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
// 加载/res/layout/目录下的fragment_book_detail.xml布局文件
View rootView = inflater.inflate(R.layout.fragment_book_detail, container, false);
if (book != null) {
// 让book_title 文本框显示book对象的title属性
((TextView) rootView.findViewById(R.id.book_title))
.setText(book.title);
// 让book_desc文本框显示book对象的desc属性
((TextView) rootView.findViewById(R.id.book_desc))
.setText(book.desc);
}
return rootView;
}
}
通过Intent启动的图书详情Activity
public class BookDetailActivity extends FragmentActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//指定加载/res/layout目录下的activity_book_detail.xml布局文件
//该界面布局文件内只定义了一个名为book_detail_container的 FrameLayout
setContentView(R.layout.activity_book_detail);
//将ActionBar上的应用图标转换成可点击的按钮
getActionBar().setDisplayHomeAsUpEnabled(true);
if (savedInstanceState == null) {
//创建BookDetailFragment对象
BookDetailFragment fragment = new BookDetailFragment();
//创建Bundle对象,
Bundle arguments = new Bundle();
arguments.putInt(BookDetailFragment.ITEM_ID, getIntent()
.getIntExtra(BookDetailFragment.ITEM_ID, 0));
//向Fragment传入参数
fragment.setArguments(arguments);
//将指定fragment添加到book_detail_container容器中
getSupportFragmentManager().beginTransaction()
.add(R.id.book_detail_container, fragment).commit();
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == android.R.id.home) {
//创建启动BookListActivity的Intent
Intent intent = new Intent(this, BookListActivity.class);
//添加额外的Flag,将Activity栈中处于FirstActivity之上的Activity弹出
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
//启动intent对应的Activity
startActivity(intent);
return true;
}
return super.onOptionsItemSelected(item);
}
}
上述代码可运行,可以试试。
如果看有些概念(比如Intent、CallBack)看不明白,没关系,继续...
3. 拓展:监听(Listener)、回调(CallBack)和 Handler
(1)监听器
举例:点击事件监听器 => 看上面两种UI界面开发模式的示例代码
(2)回调
举例:图书详情展示 => 看上面图书详情展示的示例代码
回调的定义
在编程中,回调是一个通过函数指针或引用传递给其他代码,当某个特定操作完成或某个事件触发时,这段代码会反过来“调用”传入的函数。
简单来说,就是你告诉别人:“你做完某件事之后,记得通知我一声,并执行我的任务。”
注:上述点击事件监听方法也是一种回调。
讲解上述图书详情展示示例的回调(看 BookListFragment类)
通过函数指针或引用传递给其他代码:
// 把该Activity当成Callbacks对象
mCallbacks = (Callbacks) activity;
这段代码会反过来“调用”传入的函数:
//激发mCallbacks的onItemSelected方法
mCallbacks.onItemSelected(BookContent.ITEMS.get(position).id);
读者结合回调的定义反复理解最佳…若还未理解看看这篇:
深刻理解“别人做完事来通知你”
(3)Handler
自动播放图片(1200毫秒修改一次)
public class MainActivity extends Activity {
// 定义周期性显示的图片 ID
int[] imageIds = new int[] {
R.drawable.bg,
R.drawable.bg2,
R.drawable.bg3,
R.drawable.bg4,
R.drawable.bg5
};
int currentImageId = 0;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main_handler);
final ImageView show = (ImageView) findViewById(R.id.show);
final Handler myHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
// 如果该消息是本程序所发送的
if (msg.what == 0x1233) {
// 动态地修改所显示的图片
show.setImageResource(imageIds[++currentImageId % imageIds.length]);
}
}
};
// 定义一个计时器,让该计时器周期性地执行该任务
new Timer().schedule(new TimerTask() {
@Override
public void run() {
// 发送空消息
myHandler.sendEmptyMessage(0x1233);
}
},0,1200);
}
}
二、Intent
1. Inntent的基本概述
(1)定义
Intent 是 Android 中用于组件间通信的核心机制之一,一般理解为“意图”或“动作请求”。通过 Intent,你可以启动 Activity、Service、BroadcastReceiver,甚至可以传递数据。
(2)Intent 的关键属性(七大属性)
属性 | 说明 |
---|---|
Action | 意图的动作,比如 ACTION_VIEW(可自定义) |
Data | 要操作的数据 URI,比如一个图片路径、网页链接 |
Category | 对 Action 进一步描述,比如 CATEGORY_LAUNCHER(可自定义) |
Type | 数据的 MIME 类型,比如 image/*, text/plain |
Component | 指定目标组件名称(包名 + 类名),用于显式 Intent |
Extras | 附加信息 Bundle,用于传值 |
Flags | 控制 Activity 启动模式等行为标志位 |
(3)Intent 的分类
显式 Intent(Explicit Intent)
明确指定目标组件的类名,适用于应用内部调用。
public class MainActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.intent_main);
Button button = (Button) findViewById(R.id.bn);
//为按钮绑定事件监听器
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View source) {
// 创建一个 ComponentName,SecondActivity.class要启动的Activity
ComponentName componentName = new ComponentName(MainActivity.this, SecondActivity.class);
Intent intent = new Intent();
// 为 Intent 设置 ComponentName 属性
intent.setComponent(componentName);
startActivity(intent);
/* 可简化成以下形式:
Intent intent = new Intent(MainActivity.this, SecondActivity.class);
startActivity(intent);
*/
}
});
}
}
隐式 Intent(Implicit Intent)
不指定具体组件,而是通过 Action、Category、Data 等信息让系统匹配合适的组件。适用于跨应用调用。
public class MainActivity extends Activity {
public final static String SHUAI_ACTION = "com.example.musicapp.intent3.SHUAI_ACTION";
public final static String SHUAI_CATEGORY = "com.example.musicapp.intent3.SHUAI_CATEGORY";
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.intent_main);
Button button = (Button) findViewById(R.id.bn);
//为按钮绑定事件监听器
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View source) {
// 一个 Intent 的 action 只能设置一个;category 可以设置多个
Intent intent = new Intent();
// 设置 Action 属性
intent.setAction(MainActivity.SHUAI_ACTION);
// 设置 Category 属性
intent.addCategory(MainActivity.SHUAI_CATEGORY);
startActivity(intent);
}
});
}
}
拓展:隐式intent启动activity,如果有多个符合的activity,会都启动吗?
隐式intent启动activity,如果有多个符合的activity,会都启动吗?
(4)Intent 传值方式
使用 putExtra() 方法(推荐)
支持基本类型、String、Bundle、Parcelable、Serializable 等。
Intent intent = new Intent(this, DetailActivity.class);
intent.putExtra("name", "张三");
intent.putExtra("age", 25);
startActivity(intent);
// 在 DetailActivity类中接收
Intent intent = getIntent();
String name = intent.getStringExtra("name");
int age = intent.getIntExtra("age", 0);
使用 Bundle 对象传值
适合一次性传递多个参数。
Bundle bundle = new Bundle();
bundle.putString("city", "北京");
bundle.putInt("population", 2154);
Intent intent = new Intent(this, CityInfoActivity.class);
intent.putExtras(bundle);
startActivity(intent);
// 在 CityInfoActivity类中接收
Bundle bundle = getIntent().getExtras();
String city = bundle.getString("city");
int population = bundle.getInt("population");
(5)Intent 标志位(Flags)
Flag | 说明 |
---|---|
FLAG_ACTIVITY_NEW_TASK | 在新任务栈中启动 Activity |
FLAG_ACTIVITY_CLEAR_TOP | 如果目标 Activity 已存在,则清除其上的所有 Activity |
FLAG_ACTIVITY_SINGLE_TOP | 如果目标 Activity 在栈顶,则不会新建实例 |
FLAG_ACTIVITY_CLEAR_TASK | 清除当前任务栈的所有 Activity |
FLAG_ACTIVITY_REORDER_TO_FRONT | 将已存在的 Activity 移到前台 |
// 示例
Intent intent = new Intent(this, MainActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
(6)Intent 返回结果(StartActivityForResult)
从 A Activity 启动 B Activity,并期望返回结果。
启动方(A Activity):
public class MainActivity extends Activity {
final int REQUEST_CODE_EDIT= 110;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.intent4_mian);
Button bn = findViewById(R.id.bn);
bn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 创建 Intent
Intent intent = new Intent(MainActivity.this, EditActivity.class);
// 启动指定 Activity 并等待返回的结果
startActivityForResult(intent, REQUEST_CODE_EDIT);
}
});
}
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_CODE_EDIT && resultCode == RESULT_OK) {
String result = data.getStringExtra("result");
Toast.makeText(this, "返回结果:" + result, Toast.LENGTH_SHORT).show();
}
}
}
接收方(B Activity)设置返回结果:
public class EditActivity extends Activity {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.intent4_mian);
Intent resultIntent = new Intent();
resultIntent.putExtra("result", "成功");
setResult(RESULT_OK, resultIntent);
finish(); // 关闭当前 Activity
}
}
具体后续详解...
(7)拓展:Intent构造方法详解
Intent常见构造方法
2. Intent的基本用法
(1)启动 Activity、Service、Broadcast
启动 Activity(活动)
Intent intent = new Intent(MainActivity.this, SecondActivity.class);
startActivity(intent);
启动 Service(服务)
Intent intent = new Intent("com.example.MY_CUSTOM_ACTION");
intent.putExtra("message", "Hello World");
sendBroadcast(intent);
启动 Broadcast(广播)
Intent intent = new Intent(this, MyService.class);
startService(intent); // 启动服务
(2)配置清单文件
当我们的 SecondActivity 想通过隐式 intent 启动时,需要配置清单文件。
// 一个 Intent 的 action 只能设置一个;category 可以设置多个
Intent intent = new Intent();
// 设置 Action 属性
intent.setAction(MainActivity.SHUAI_ACTION);
// 设置 Category 属性
intent.addCategory(MainActivity.SHUAI_CATEGORY);
startActivity(intent);
<activity android:name=".intent3.SecondActivity"
android:theme="@android:style/Theme.Holo"
android:label="第二页面"
android:exported="true">
<intent-filter>
<!-- 隐式 Intent:设置 Action 条件 -->
<action android:name="com.example.musicapp.intent3.SHUAI_ACTION" />
<!-- 多余的 Action 条件没有影响 -->
<action android:name="helloWorld" />
<!-- 隐式 Intent:设置 Category 条件 -->
<category android:name="com.example.musicapp.intent3.SHUAI_CATEGORY" />
<!-- 多余的 Category 条件没有影响 -->
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
三、Service
1. Service 的基础知识
(1)定义
Service 是 Android 四大组件之一,用于在后台执行长时间运行的操作(如播放音乐、下载文件、上传数据等),与 Activity 不同的是,它没有用户界面。
(2)Service 的生命周期方法
方法 | 调用时机 |
---|---|
onCreate() | 第一次创建服务时调用,只调用一次 |
onStartCommand(Intent intent, int flags, int startId) | 每次调用 startService() 都会触发此方法 |
onBind(Intent intent) | 当调用 bindService() 时调用,返回 IBinder 对象 |
onUnbind(Intent intent) | 当所有客户端解除绑定时调用 |
onDestroy() | 服务被销毁时调用 |
(3)Service 的两种启动方式
通过 startService() 启动
适用于执行一次性或长时间运行的任务。
服务独立运行,即使启动它的组件被销毁,服务仍可继续运行。
停止服务必须调用 stopSelf() 或 stopService()。
Intent intent = new Intent(this, MyService.class);
startService(intent); // 启动服务
Intent intent = new Intent(this, MyService.class);
stopService(intent); // 停止服务
通过 bindService() 绑定(常用)
适用于组件与服务之间需要交互的情况(如获取数据、控制播放)。
服务依附于绑定它的组件,当最后一个组件解绑后,服务会被销毁(除非也通过 startService() 启动过)。
Step 1:在 BindService 中定义 Binder 接口
Step 2:在 MainActivity 中绑定服务
Step 3:调用服务的方法 getCount();
public class MainActivity extends Activity {
Button bind,unbind,getServiceStatus;
// 保持所启动的 Service 的 IBinder 对象
BindService.MyBinder binder;
// 定义一个 ServiceConnection 对象
private ServiceConnection conn = new ServiceConnection() {
/**
* 当该 Activity 与 Service 连接成功时回调该方法
* @param name 已连接的服务的具体组件名称。
* @param service 服务的通信通道的 IBinder,您现在可以对其进行调用。
*/
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
new ComponentName(MainActivity.this, SecondActivity.class);
System.out.println("--Service Connected--");
// 获取 Service 的 onBind 方法所返回的 MyBinder 对象
binder = (BindService.MyBinder) service;
}
/**
* 当该 Activity 与 Service 断开连接时回调该方法(属异常情况时)
* @param name The concrete component name of the service whose
* connection has been lost.
*/
@Override
public void onServiceDisconnected(ComponentName name) {
System.out.println("--Service Disconnected--");
}
};
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.service2_bind_main);
// 获取程序界面中的 start、stop、getServiceStatus 按钮
bind = (Button) findViewById(R.id.bind);
unbind = (Button) findViewById(R.id.unbind);
getServiceStatus = (Button) findViewById(R.id.getServiceStatus);
// 创建启动 Service 的 Intent
final Intent intent = new Intent(MainActivity.this,BindService.class);
bind.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 绑定指定的 Service
bindService(intent,conn, Service.BIND_AUTO_CREATE);
}
});
unbind.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 解除绑定的 Service
unbindService(conn);
}
});
getServiceStatus.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 获取并显示 Service 的 count 值
int count = binder.getCount();
Toast.makeText(MainActivity.this,"获取count值为:" + count,Toast.LENGTH_LONG).show();
}
});
}
}
public class BindService extends Service {
private int count;
private boolean quit;
// 定义 onBinder 方法所返回的对象
private MyBinder binder = new MyBinder();
// 通过继承 Binder 来实现 IBinder 类
public class MyBinder extends Binder {
public int getCount() {
// 获取 Service 的运行状态:count
return count;
}
}
/**
* 必须实现的方法,绑定该 Service 时回调该方法
*/
@Override
public IBinder onBind(Intent intent) {
System.out.println("Service is Binded");
// 返回 IBinder 对象
return binder;
}
/**
* Service 被创建时回调该方法
*/
@Override
public void onCreate() {
System.out.println("Service is onCreate");
super.onCreate();
// 启动一条线程,动态地修改 count 状态值
new Thread() {
@Override
public void run() {
while (!quit) {
try {
Thread.sleep(1000);
}catch (InterruptedException e) {
e.printStackTrace();
}
count++;
}
}
}.start();
}
/**
* Service 被断开连接时回调该方法
*/
@Override
public boolean onUnbind(Intent intent) {
System.out.println("Service is Unbinded");
return true; // 如果希望之后绑定该 Service 则设为 true
}
/**
* Service 被关闭之前回调该方法
*/
@Override
public void onDestroy() {
super.onDestroy();
this.quit = true;
System.out.println("Service is Destroyed");
}
}
(4)Service 的分类
类型 | 描述 |
---|---|
普通 Service | 通过 startService() 启动,无绑定机制 |
绑定 Service | 通过 bindService() 启动,支持组件与服务交互 |
前台 Service | 显示通知,优先级高,不易被系统杀死 |
远程 Service | 运行在其他进程中,通常使用 AIDL 实现跨进程通信 |
2. Service 的基础用法
(1)Service 传参
可以通过 Intent.putExtra() 向 Service 传递参数。
启动时传值:
Intent intent = new Intent(this, MyService.class);
intent.putExtra("musicUri", "http://example.com/song.mp3");
startService(intent);
在 Service 中接收:
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
String musicUri = intent.getStringExtra("musicUri");
Log.d("MyService", "即将播放:" + musicUri);
return START_STICKY;
}
(2)普通 Service 容易出现的错误
Service 不会专门启动一个单独的进程,Service 与它所在的应用位于同一个进程中。
Service 不是一条新的线程,因此不应该在 Service 中直接处理耗时任务。
启动 Service:
public class MainActivity extends Activity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.service3_main);
// 获取界面的两个按钮
Button startCommonService = (Button) findViewById(R.id.startCommonService);
Button startIntentService = (Button) findViewById(R.id.startIntentService);
startCommonService.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
runCommonService(v);
}
});
startIntentService.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
runIntentService(v);
}
});
}
private void runCommonService(View view) {
// 创建启动 Service 的 Intent
final Intent intent = new Intent(MainActivity.this, MyCommonService.class);
// 启动 Service
startService(intent);
}
private void runIntentService(View view) {
// 创建启动 JobIntentService 的 Intent
final Intent intent = new Intent(MainActivity.this, MyIntentService.class);
// 启动 Service
startService(intent);
}
}
在 MyCommonService 中执行耗时任务:可能导致 ANR(Application Not Responding)
public class MyCommonService extends Service {
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
// 该方法内执行耗时任务可能导致 ANR(Application Not Responding)
long endTime = System.currentTimeMillis() + 20 * 1000;
System.out.println("onStartCommand");
while (System.currentTimeMillis() < endTime) {
synchronized (this) {
try {
wait(endTime - System.currentTimeMillis());
}catch (Exception e){
e.printStackTrace();
}
}
}
System.out.println("---MyCommonService 耗时任务执行完成---");
return START_STICKY;
}
}
在 MyCommonService 中执行耗时任务:使用单独的线程来执行 onHandleWork() 方法的代码
public class MyIntentService extends JobIntentService {
/**
* JobIntentService 会使用单独的线程来执行该方法的代码
* @param intent
*/
@Override
protected void onHandleWork(@NonNull Intent intent) {
// 该方法内可以执行任何耗时任务,比如下载文件等,此处只是让线程暂停 20 秒
long endTime = System.currentTimeMillis() + 20 * 1000;
System.out.println("onHandleIntent");
while (System.currentTimeMillis() < endTime) {
synchronized (this) {
try {
wait(endTime - System.currentTimeMillis());
}catch (Exception e){
e.printStackTrace();
}
}
}
System.out.println("---MyIntentService 耗时任务执行完成---");
}
}
(3)Service 使用注意事项
注意事项 | 说明 |
---|---|
不要在主线程执行耗时操作 | 应使用子线程或 HandlerThread (也容易因低优先级而被杀掉,上述方法解决) |
避免内存泄漏 | 使用完服务后应及时解绑或停止 |
前台服务需显示通知 | 否则可能被系统视为低优先级而杀掉 |
权限声明 | 如果服务暴露给其他应用,需在 AndroidManifest.xml 中配置权限 |
(4)在 AndroidManifest.xml 中注册 Service
<service android:name=".MyService" />
如果是远程服务,可以指定 process 属性:
<service android:name=".MyRemoteService" android:process=":remote" />
(5)Service 的应用场景
场景 | 示例 |
---|---|
播放音乐 | 即使离开界面也能继续播放 |
下载/上传文件 | 在后台持续进行 |
定时任务 | 使用 AlarmManager 或 WorkManager 结合 Service 执行定时操作 |
数据同步 | 与服务器保持同步更新 |
前台服务 | 如导航 App 显示定位通知 |
四、BroadcastReceiver
1. BroadcastReceiver 的基础知识
(1)Broadcast 的基本概念
特性 | 描述 |
---|---|
作用 | 实现组件间通信、监听系统事件(如网络变化、电量变化等) |
发送方式 | 可以通过 sendBroadcast()、sendOrderedBroadcast() 等方法发送 |
接收方式 | 静态注册(在 AndroidManifest.xml 中声明)或动态注册(在代码中 registerReceiver()) |
生命周期 | BroadcastReceiver 生命周期短,接收到广播后立即执行 onReceive() 方法 |
(2)BroadcastReceiver 的生命周期
onReceive(Context context, Intent intent)
:当接收到广播时调用。
注意:不能在此方法中执行耗时操作(超过10秒可能引发 ANR),建议启动 Service 或使用 WorkManager 处理复杂任务。
(3)广播类型
类型 | 描述 | 是否有序 | 是否粘滞 |
---|---|---|---|
普通广播(Normal Broadcast) | 同时发送给所有接收者,无顺序 | ❌ | ❌ |
有序广播(Ordered Broadcast) | 按优先级依次传递,前一个接收者可修改数据或终止广播 | ✅ | ❌ |
粘滞广播(Sticky Broadcast) | 发送后注册也能接收到(已过时,不推荐使用) | ❌ | ✅ |
本地广播(Local Broadcast) | 只在本应用内传播,更安全高效 | ✅ | ❌ |
2.BroadcastReceiver 的基本用法
(1)广播接收器的注册方式
动态注册(代码中注册)
举例:超简单的音乐播放器
先准备下资源和布局文件:
① 放在 drawable 目录下
play.xml、pause.xml、stop.xml
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="200dp"
android:height="200dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path
android:pathData="M510.6,64.9C263.4,64.9 63,265.3 63,512.5S263.4,960 510.6,960s447.6,-200.4 447.6,-447.6S757.7,64.9 510.6,64.9zM685.5,529.2L374.9,708.6c-12.9,7.4 -29,-1.9 -29,-16.7L345.9,333.1c0,-14.9 16.1,-24.2 29,-16.7l310.6,179.3c12.9,7.5 12.9,26.1 0,33.5z"
android:fillColor="#1296db"/>
</vector>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="200dp"
android:height="200dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path
android:pathData="M512,85.33c235.63,0 426.67,191.04 426.67,426.67s-191.04,426.67 -426.67,426.67S85.33,747.63 85.33,512 276.37,85.33 512,85.33zM426.67,362.67h-85.33v298.67h85.33L426.67,362.67zM682.67,362.67h-85.33v298.67h85.33L682.67,362.67z"
android:fillColor="#1296db"/>
</vector>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="200dp"
android:height="200dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path
android:pathData="M512,42.67C252.79,42.67 42.67,252.79 42.67,512s210.13,469.33 469.33,469.33 469.33,-210.13 469.33,-469.33S771.21,42.67 512,42.67zM725.33,688a37.37,37.37 0,0 1,-37.33 37.33L336,725.33a37.37,37.37 0,0 1,-37.33 -37.33L298.67,336a37.37,37.37 0,0 1,37.33 -37.33h352a37.37,37.37 0,0 1,37.33 37.33z"
android:fillColor="#1296db"/>
</vector>
② 放在 layout 目录下
broadcast3_music_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageButton android:id="@+id/play"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/play"/>
<ImageButton android:id="@+id/stop"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/stop"/>
<TextView android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView android:id="@+id/author"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>
开写:Activity 和 Service
public class MainActivity extends Activity implements View.OnClickListener {
// 获取界面中显示的歌曲标题、作者的文本框
TextView title, author;
// 播放、暂停按钮
ImageButton play, stop;
ActivityReceiver activityReceiver;
public static final String CTL_ACTION = "org.crazyit.action.CTL_ACTION";
public static final String UPDATE_ACTION = "org.crazyit.action.UPDATE_ACTION";
// 定义音乐的播放状态,0x11代表没有播放,0x12代表正在播放,0x13代表暂停播放
int status = 0x11;
String[] titleStrs = new String[]{
"心愿", "约定", "美丽新世界"};
String[] authorStrs = new String[]{
"未知艺术家", "周蕙", "伍佰"};
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.broadcast3_music_main);
// 获取程序界面中的两个按钮
play = (ImageButton) this.findViewById(R.id.play);
stop = (ImageButton) this.findViewById(R.id.stop);
title = (TextView) findViewById(R.id.title);
author = (TextView) findViewById(R.id.author);
// 为两个按钮的单击事件添加监听器
play.setOnClickListener(this);
stop.setOnClickListener(this);
activityReceiver = new ActivityReceiver();
//创建IntentFilter
IntentFilter filter = new IntentFilter();
//指定BroadcastReceiver监听的Action
filter.addAction(UPDATE_ACTION);
//注册 BroadcastReceiver
registerReceiver(activityReceiver, filter);
Intent intent = new Intent(this, MusicService.class);
//启动后台Service
startService(intent);
}
// 自定义的BroadcastReceiver,负责监听从Service传回来的广播
public class ActivityReceiver extends BroadcastReceiver {
/**
* 设置音乐播放器的状态
* @param context
* @param intent
*/
@Override
public void onReceive(Context context, Intent intent) {
//获取Intent中的update消息,update代表播放状态
int update = intent.getIntExtra("update", -1);
//获取Intent中的current消息,current代表当前正在播放的歌曲
int current = intent.getIntExtra("current", -1);
if (current >= 0) {
title.setText(titleStrs[current]);
author.setText(authorStrs[current]);
}
switch (update) {
case 0x11:
play.setImageResource(R.drawable.play);
status = 0x11;
break;
//控制系统进入播放状态
case 0x12:
//在播放状态下设置使用暂停图标
play.setImageResource(R.drawable.pause);
//设置当前状态
status = 0x12;
break;
// 控制系统进入暂停状态
case 0x13:
// 在暂停状态下设置使用播放图标
play.setImageResource(R.drawable.play);
// 设置当前状态
status = 0x13;
break;
}
}
}
@Override
public void onClick(View source) {
//创建Intent
Intent intent = new Intent("org.crazyit.action.CTL_ACTION");
switch (source.getId()) {
//按下播放/暂停按钮
case R.id.play:
intent.putExtra("control", 1);
break;
//按下停止按钮
case R.id.stop:
intent.putExtra("control", 2);
break;
}
//发送广播,将被Service组件中的BroadcastReceiver接收到
sendBroadcast(intent);
}
}
public class MusicService extends Service {
MyReceiver serviceReceiver;
AssetManager am;
String[] musics = new String[]{
"wish.mp3", "promise.mp3", "beautiful.mp3"};
MediaPlayer mPlayer;
// 当前的状态,0x11代表没有播放;0x12代表正在播放;0x13代表暂停
int status = 0x11;
// 记录当前正在播放的音乐
int current = 0;
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onCreate() {
super.onCreate();
am = getAssets();
//创建BroadcastReceiver
serviceReceiver = new MyReceiver();
//创建IntentFilter
IntentFilter filter = new IntentFilter();
filter.addAction(MainActivity.CTL_ACTION);
registerReceiver(serviceReceiver, filter);
//创建MediaPlayer
mPlayer = new MediaPlayer();
//为MediaPlayer播放完成事件绑定监听器
mPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener()// ①
{
@Override
public void onCompletion(MediaPlayer mp) {
current++;
if (current >= 3) {
current = 0;
}
// 发送广播通知Activity更改文本框
Intent sendIntent = new Intent(MainActivity.UPDATE_ACTION);
sendIntent.putExtra("current", current);
//发送广播,将被Activity组件中的BroadcastReceiver接收到
sendBroadcast(sendIntent);
//准备并播放音乐
prepareAndPlay(musics[current]);
}
});
}
public class MyReceiver extends BroadcastReceiver {
/**
* 更新音乐播放器的状态:播放、暂停、停止三种状态(但未设置)
* @param context
* @param intent
*/
@Override
public void onReceive(final Context context, Intent intent) {
int control = intent.getIntExtra("control", -1);
switch (control) {
//播放或暂停
case 1:
//原来处于没有播放状态
if (status == 0x11) {
//准备并播放音乐
prepareAndPlay(musics[current]);
status = 0x12;
}
//原来处于播放状态
else if (status == 0x12) {
//暂停
mPlayer.pause();
//改变为暂停状态
status = 0x13;
}
//原来处于暂停状态
else if (status == 0x13) {
//播放
mPlayer.start();
//改变状态
status = 0x12;
}
break;
//停止声音
case 2:
//如果原来正在播放或暂停
if (status == 0x12 || status == 0x13) {
//停止播放
mPlayer.stop();
status = 0x11;
}
}
//广播通知Activity更改图标、文本框
Intent sendIntent = new Intent(MainActivity.UPDATE_ACTION);
sendIntent.putExtra("update", status);
sendIntent.putExtra("current", current);
//发送广播,将被Activity组件中的BroadcastReceiver接收到
sendBroadcast(sendIntent);
}
}
private void prepareAndPlay(String music) {
try {
//打开指定音乐文件
AssetFileDescriptor afd = am.openFd(music);
mPlayer.reset();
//使用MediaPlayer加载指定的声音文件,
mPlayer.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
//准备声音
mPlayer.prepare();
//播放
mPlayer.start();
} catch (IOException e) {
e.printStackTrace();
}
}
}
清单文件
<activity android:name=".broadcast_music.MainActivity"
android:theme="@android:style/Theme.Holo"
android:label="主界面"
android:exported="true">
<intent-filter>
<!-- 指定该Activity是程序的入口 -->
<action android:name="android.intent.action.MAIN" />
<!-- 指定加载该应用时运行该activity -->
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service android:name=".broadcast_music.MusicService"
android:exported="true">
<intent-filter android:priority="20">
<action android:name="org.crazyit.action.CTL_ACTION"/>
</intent-filter>
</service>
其中动态注册代码是:
activityReceiver = new ActivityReceiver();
//创建IntentFilter
IntentFilter filter = new IntentFilter();
//指定BroadcastReceiver监听的Action
filter.addAction(UPDATE_ACTION);
//注册 BroadcastReceiver
registerReceiver(activityReceiver, filter);
解除注册可通过代码:
unregisterReceiver(activityReceiver );
⚠️ 注意:应在 Activity/Service 销毁时解绑,避免内存泄漏。
静态注册(在 AndroidManifest.xml 中声明)
<receiver android:name=".broadcast_music.ActivityReceiver"
android:exported="true">
<intent-filter>
<action android:name="org.crazyit.action.UPDATE_ACTION"/>
</intent-filter>
</receiver>
⚠️ Android 8.0+ 对隐式广播限制较多,部分广播必须动态注册才能生效。
(2)发送广播的方式
普通广播
public class MainActivity extends Activity {
Button send;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.broadcast_main);
// 获取界面按钮
send = (Button) findViewById(R.id.send);
send.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 创建 Intent,设置 Action 条件,放入消息
Intent intent = new Intent();
intent.setAction("com.shuai.action.SHUAI_BROADCAST");
intent.putExtra("msg","简单的消息");
// 发送广播
sendBroadcast(intent);
}
});
}
}
public class MyReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Toast.makeText(context,"接收到的 Intent 的 Action 为:"
+ intent.getAction() + "
消息的内容是:"
+ intent.getStringExtra("msg")
,Toast.LENGTH_LONG).show();
}
}
<activity android:name=".broadcast.MainActivity"
android:theme="@android:style/Theme.Holo"
android:label="主界面"
android:exported="true">
<intent-filter>
<!-- 指定该Activity是程序的入口 -->
<action android:name="android.intent.action.MAIN" />
<!-- 指定加载该应用时运行该activity -->
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<receiver android:name=".broadcast.MyReceiver"
android:exported="true">
<intent-filter>
<action android:name="com.shuai.action.SHUAI_BROADCAST"/>
</intent-filter>
</receiver>
有序广播(广播会一级一级传递,优先级高的 Receiver 先处理)
public class MainActivity extends Activity {
Button send;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.broadcast2_main);
// 获取界面按钮
send = (Button) findViewById(R.id.send);
send.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 创建 Intent,设置 Action 条件,放入消息
Intent intent = new Intent();
intent.setAction("com.shuai.action.SHUAI_BROADCAST");
intent.putExtra("msg","有序的消息");
// 发送广播
// receiverPermission 表示设置:必须拥有才能接收您的广播的权限
// 为 null 表示不需要权限
sendOrderedBroadcast(intent, null);
}
});
}
}
public class MyReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Toast.makeText(context,"接收到的 Intent 的 Action 为:"
+ intent.getAction() + "
消息的内容是:"
+ intent.getStringExtra("msg")
,Toast.LENGTH_LONG).show();
// 创建一个 Bundle 对象,并存入数据
Bundle bundle = new Bundle();
bundle.putString("first","第一个 BroadcastReceiver 存入的消息");
// 将 bundle 放入结果中
setResultExtras(bundle);
// 取消 Broadcast 继续传播
// abortBroadcast();
}
}
public class MyReceiver2 extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
// 创建一个 Bundle 对象,true表示即使为 null也会创建对应类接收
Bundle bundle = getResultExtras(true);
// 解析前一个 BroadcastReceiver 所存入的 key 为 first 的消息
String first = bundle.getString("first");
Toast.makeText(context,"接收前一个 Broadcast 存入的消息 为:" + first,Toast.LENGTH_LONG).show();
}
}
<activity android:name=".broadcast2.MainActivity"
android:theme="@android:style/Theme.Holo"
android:label="主界面"
android:exported="true">
<intent-filter>
<!-- 指定该Activity是程序的入口 -->
<action android:name="android.intent.action.MAIN" />
<!-- 指定加载该应用时运行该activity -->
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<receiver android:name=".broadcast2.MyReceiver"
android:exported="true">
<intent-filter android:priority="20">
<action android:name="com.shuai.action.SHUAI_BROADCAST"/>
</intent-filter>
</receiver>
<receiver android:name=".broadcast2.MyReceiver2"
android:exported="true">
<intent-filter android:priority="0">
<action android:name="com.shuai.action.SHUAI_BROADCAST"/>
</intent-filter>
</receiver>
优先级通过清单文件中配置信息体现
android:priority="20"
android:priority="0"
(越高优先级越高)
广播可截断:abortBroadcast();
(3)LocalBroadcastManager(本地广播)
适用于应用内部通信,效率高且不会被外部应用接收到。
注册本地广播:
LocalBroadcastManager broadcastManager = LocalBroadcastManager.getInstance(this);
broadcastManager.registerReceiver(myReceiver, new IntentFilter("com.example.LOCAL_ACTION"));
发送本地广播:
Intent intent = new Intent("com.example.LOCAL_ACTION");
intent.putExtra("msg", "来自本地的消息");
LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
解绑:
LocalBroadcastManager.getInstance(this).unregisterReceiver(myReceiver);
(4)注意事项
注意事项 | 说明 |
---|---|
不要执行耗时操作 | onReceive() 中禁止执行长时间任务 |
尽量使用本地广播 | 提升性能与安全性 |
动态注册记得解绑 | 避免内存泄漏 |
系统广播兼容性问题 | Android 8.0+ 对隐式广播有诸多限制 |
权限申请 | 接收某些系统广播需要在 Manifest 中声明权限 |
五、ContentProvider
1. ContentProvider 的基础知识
(1)定义
ContentProvider 是 Android 四大组件之一,用于在不同应用之间共享数据。它提供了一套标准接口,使得一个应用可以安全地访问另一个应用的数据(如数据库、文件等),是 Android 实现跨应用数据共享的核心机制。
(2)ContentProvider 的基本概念
特性 | 描述 |
---|---|
作用 | 跨应用共享数据,如访问通讯录、短信、媒体库等 |
数据模型 | 类似数据库表结构,使用 Uri 定位数据 |
接口方法 | 提供 query()、insert()、update()、delete()、getType() 等操作方法 |
权限控制 | 可通过 <permission> 标签设置读写权限 |
(3)ContentProvider 的核心组件
Uri(统一资源标识符)
用于唯一标识一组数据。
格式:content://authority/path/id
Uri uri = Uri.parse("content://com.example.provider.userprovider/users/1");
content://
:固定前缀(类比https://)
com.example.provider.userprovider
:Authority,对应 AndroidManifest.xml 中声明的 authorities(类比域名:editor.csdn.net/)
users
:数据路径(表名)
1
:具体数据 ID
ContentResolver(内容解析器)
用于在客户端访问 ContentProvider 提供的数据。
通过 Context 获取: ContentResolver resolver = getContentResolver();
常用方法:query()、insert()、update()、delete()
ContentProvider(内容提供者)
需继承 ContentProvider
类并实现其抽象方法。
在 AndroidManifest.xml 配置:
<provider
android:authorities="com.shuai.providers.firstprovider"
android:name=".contentprovider.FirstProvider"
android:exported="true" />
2. ContentProvider 的基础用法
(1)自定义 ContentProvider 步骤
Step 1:创建 ContentProvider 子类
public class FirstProvider extends ContentProvider {
/**
* 第一次创建该 ContentProvider 时调用该方法
* @return 是否创建成功
*/
@Override
public boolean onCreate() {
System.out.println("=== onCreate 方法被调用 ===");
return true;
}
/**
*
* @param uri
* @return 代表了该 ContentProvider 所提供数据的 MIME 类型
*/
@Nullable
@Override
public String getType(@NonNull Uri uri) {
return null;
}
/**
* 实现查询方法
* @param uri
* @param projection
* @param selection
* @param selectionArgs
* @param sortOrder
* @return 返回查询得到的 Cursor(相当于 JavaSwing 中的 ResultSet)
*/
@Nullable
@Override
public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
System.out.println(uri + "=== query 方法被调用 ===");
System.out.println("selection 参数为:" + selection);
return null;
}
/**
* 实现插入方法
* @param uri
* @param values
* @return 返回新插入的记录的 Uri
*/
@Nullable
@Override
public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
System.out.println(uri + "=== insert 方法被调用 ===");
System.out.println("values 参数为:" + values);
return null;
}
/**
* 实现更新方法
* @param uri
* @param values
* @param selection
* @param selectionArgs
* @return 返回被更新记录的条数
*/
@Override
public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) {
System.out.println(uri + "=== update 方法被调用 ===");
System.out.println("selection 参数为:" + selection + ", values参数为:" + values);
return 0;
}
/**
* 实现删除方法
* @param uri
* @param selection
* @param selectionArgs
* @return 返回被删除记录的条数
*/
@Override
public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
System.out.println(uri + "=== delete 方法被调用 ===");
System.out.println("selection 参数为:" + selection);
return 0;
}
}
Step 2:在 AndroidManifest.xml 中注册
<provider
android:authorities="com.shuai.providers.firstprovider"
android:name=".contentprovider.FirstProvider"
android:exported="true" />
<activity android:name=".contentprovider.MainActivity"
android:theme="@android:style/Theme.Holo"
android:label="主界面"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
⚠️
android:authorities
:必须与代码中匹配
⚠️android:exported
:是否允许其他应用访问
Step 3:客户端访问 ContentProvider
public class MainActivity extends Activity {
ContentResolver contentResolver;
Uri uri = Uri.parse("content://com.shuai.providers.firstprovider/");
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.contentprovider_main);
// 获取 ContentResolver 对象
contentResolver = getContentResolver();
}
public void query(View v) {
// 调用 contentResolver 的 query 方法
// 实际返回的是该 Uri 对应 ContentProvider 的 query() 方法的返回值
Cursor c = contentResolver.query(uri, null, "query_where", null, null);
Toast.makeText(this,"远程ContentProvider返回的 Cursor 为:" + c,Toast.LENGTH_LONG).show();
c.close();
}
public void insert(View v) {
// 调用 contentResolver 的 insert 方法
// 实际返回的是该 Uri 对应 ContentProvider 的 insert() 方法的返回值
ContentValues values = new ContentValues();
values.put("name","张三");
Uri newUri = contentResolver.insert(uri, values);
Toast.makeText(this,"远程ContentProvider新插入记录的 Uri 为:" + newUri,Toast.LENGTH_LONG).show();
}
public void update(View v) {
// 调用 contentResolver 的 update 方法
// 实际返回的是该 Uri 对应 ContentProvider 的 update() 方法的返回值
ContentValues values = new ContentValues();
values.put("name","李四");
int count = contentResolver.update(uri, values, "update_where", null);
Toast.makeText(this,"远程ContentProvider 更新的记录数为:" + count,Toast.LENGTH_LONG).show();
}
public void delete(View v) {
// 调用 contentResolver 的 delete 方法
// 实际返回的是该 Uri 对应 ContentProvider 的 delete() 方法的返回值
int count = contentResolver.delete(uri, "delete_where", null);
Toast.makeText(this,"远程ContentProvider 删除的记录数为:" + count,Toast.LENGTH_LONG).show();
}
}
(2)ContentProvider 的 Uri 匹配机制
使用 UriMatcher
或 match(Uri)
来识别不同的 Uri 请求。
private static final UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH);
static {
matcher.addURI("com.example.provider.userprovider", "users", USERS);
matcher.addURI("com.example.provider.userprovider", "users/#", USER_ID);
}
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
switch (matcher.match(uri)) {
case USERS: // 查询所有用户 break;
case USER_ID: // 查询单个用户
String id = uri.getLastPathSegment();
selection = "_id=" + id; break;
}
return null;
}
举例:超简单的单词本
准备布局文件:contentprovider2_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<EditText android:id="@+id/word"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<EditText android:id="@+id/detail"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<Button android:id="@+id/insert"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="添加生词"/>
<EditText android:id="@+id/key"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<Button android:id="@+id/search"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="查找"/>
</LinearLayout>
单词实体类
public final class Words {
// 定义该 ContentProvider 的 Authorities
public static final String AUTHORITY = "com.shuai.providers.dictprovider";
public static final class Word implements BaseColumns {
// 定义 Content 所允许操作的三个数据列
public final static String _ID = "_id";
public final static String WORD = "word";
public final static String DETAIL = "detail";
// 定义该 Content 提供服务的两个 Uri
public final static Uri DICT_CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/words");
public final static Uri WORD_CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/word");
}
}
数据库访问助手
public class MyDatabaseHelper extends SQLiteOpenHelper {
final String CREATE_TABLE_SQL = "create table dict(" +
"_id integer primary key autoincrement, word, detail)";
public MyDatabaseHelper(@Nullable Context context, @Nullable String name, int version) {
super(context, name, null, version);
}
@Override
public void onCreate(SQLiteDatabase db) {
// 第一次使用数据库时自动建表
db.execSQL(CREATE_TABLE_SQL);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
System.out.println("----------onUpgrade Called----------"
+ oldVersion + "--->" + newVersion);
}
}
DictProvider 提供者
public class DictProvider extends ContentProvider {
private static UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH);
private static final int WORDS = 1;
private static final int WORD = 2;
private MyDatabaseHelper databaseHelper;
static {
// 为 UriMatcher 注册两个 Uri
matcher.addURI(Words.AUTHORITY, "words", WORDS);
matcher.addURI(Words.AUTHORITY, "word/#", WORD);
}
/**
* 第一次创建该 ContentProvider 时调用该方法
* @return 是否创建成功
*/
@Override
public boolean onCreate() {
System.out.println("=== onCreate 方法被调用 ===");
databaseHelper = new MyDatabaseHelper(this.getContext(), "myDict.db3", 1);
return true;
}
/**
* @param uri
* @return 代表了该 ContentProvider 所提供数据的 MIME 类型
*/
@Nullable
@Override
public String getType(@NonNull Uri uri) {
switch (matcher.match(uri)) {
// 如果操作的数据是多项数据
case WORDS:
return "vnd.android.cursor.dir/com.shuai.dict";
// 如果操作的数据是单项数据
case WORD:
return "vnd.android.cursor.item/com.shuai.dict";
default:
throw new IllegalArgumentException("未知 Uri:" + uri);
}
}
/**
* 实现查询方法
* @param uri
* @param projection
* @param selection
* @param selectionArgs
* @param sortOrder
* @return 返回查询得到的 Cursor(相当于 JavaSwing 中的 ResultSet)
*/
@Nullable
@Override
public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
System.out.println(uri + "=== query 方法被调用 ===");
SQLiteDatabase db = databaseHelper.getReadableDatabase();
switch (matcher.match(uri)) {
// 如果 Uri 参数代表操作全部数据项
case WORDS:
return db.query("dict", projection, selection, selectionArgs, null, null, sortOrder);
// 如果 Uri 参数代表操作指定数据项
case WORD:
long id = ContentUris.parseId(uri);
String whereClause = Words.Word._ID + "=" + id;
// 如果原来的 where 子句存在,拼接 where 子句
whereClause = whereClause + " and " + selection;
return db.query("dict", projection, whereClause, selectionArgs, null, null, sortOrder);
default:
throw new IllegalArgumentException("未知 Uri:" + uri);
}
}
/**
* 实现插入方法
* @param uri
* @param values
* @return 返回新插入的记录的 Uri
*/
@Nullable
@Override
public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
System.out.println(uri + "=== insert 方法被调用 ===");
// 获取数据库实例
SQLiteDatabase db = databaseHelper.getReadableDatabase();
switch (matcher.match(uri)) {
// 如果 Uri 参数代表操作全部数据项
case WORDS:
// 插入数据,返回插入记录的 ID
long rowId = db.insert("dict", Words.Word._ID, values);
// 如果插入成功返回 Uri
if (rowId > 0) {
// 在已有的 Uri 的后面追加 ID
Uri wordUri = ContentUris.withAppendedId(uri, rowId);
// 通知数据已经改变
getContext().getContentResolver().notifyChange(wordUri, null);
return wordUri;
}
break;
default:
throw new IllegalArgumentException("未知 Uri:" + uri);
}
return null;
}
/**
* 实现更新方法
* @param uri
* @param values
* @param selection
* @param selectionArgs
* @return 返回被更新记录的条数
*/
@Override
public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) {
System.out.println(uri + "=== update 方法被调用 ===");
SQLiteDatabase db = databaseHelper.getReadableDatabase();
// 记录所修改的记录数
int num = 0;
switch (matcher.match(uri)) {
// 如果 Uri 参数代表操作全部数据项
case WORDS:
num = db.update("dict", values, selection, selectionArgs);
break;
// 如果 Uri 参数代表操作指定数据项
case WORD:
// 解析出想修改的记录 ID
long id = ContentUris.parseId(uri);
String whereClause = Words.Word._ID + "=" + id;
// 如果原来的 where 子句存在,拼接 where 子句
if (selection != null && !"".equals(selection)) {
whereClause = whereClause + " and " + selection;
}
num = db.update("dict", values, selection, selectionArgs);
break;
default:
throw new IllegalArgumentException("未知 Uri:" + uri);
}
// 通知数据已经改变
getContext().getContentResolver().notifyChange(uri, null);
return num;
}
/**
* 实现删除方法
* @param uri
* @param selection
* @param selectionArgs
* @return 返回被删除记录的条数
*/
@Override
public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
System.out.println(uri + "=== delete 方法被调用 ===");
SQLiteDatabase db = databaseHelper.getReadableDatabase();
// 记录所修改的记录数
int num = 0;
switch (matcher.match(uri)) {
// 如果 Uri 参数代表操作全部数据项
case WORDS:
num = db.delete("dict", selection, selectionArgs);
break;
// 如果 Uri 参数代表操作指定数据项
case WORD:
// 解析出想删除的记录 ID
long id = ContentUris.parseId(uri);
String whereClause = Words.Word._ID + "=" + id;
// 如果原来的 where 子句存在,拼接 where 子句
if (selection != null && !"".equals(selection)) {
whereClause = whereClause + " and " + selection;
}
num = db.delete("dict", selection, selectionArgs);
break;
default:
throw new IllegalArgumentException("未知 Uri:" + uri);
}
// 通知数据已经改变
getContext().getContentResolver().notifyChange(uri, null);
return num;
}
}
MainActivity
public class MainActivity extends Activity {
ContentResolver contentResolver;
Button insert = null;
Button search = null;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.contentprovider2_main);
// 获取系统的 ContentResolver 对象
contentResolver = getContentResolver();
insert = (Button) findViewById(R.id.insert);
search = (Button) findViewById(R.id.search);
insert.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 获取用户的输入
String word = ((EditText) findViewById(R.id.word)).getText().toString();
String detail = ((EditText) findViewById(R.id.detail)).getText().toString();
// 插入生词记录
ContentValues values = new ContentValues();
values.put(Words.Word.WORD,word);
values.put(Words.Word.DETAIL,detail);
contentResolver.insert(Words.Word.DICT_CONTENT_URI,values);
// 显示提示信息
Toast.makeText(MainActivity.this,"添加生词成功!",Toast.LENGTH_LONG).show();
}
});
search.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 获取用户的输入
String key = ((EditText) findViewById(R.id.key)).getText().toString();
// 执行查询
Cursor cursor = contentResolver.query(
Words.Word.DICT_CONTENT_URI, null,
"word like ? or detail like ?", new String[] {
"%" + key + "%", "%" + key + "%"
} ,null);
// 创建 Bundle,放入数据
Bundle data = new Bundle();
data.putSerializable("data",converCursorToList(cursor));
// 创建 Intent,放入 Bundle
Intent intent = new Intent(MainActivity.this, ResultActivity.class);
intent.putExtras(data);
// 通过 Intent 启动第二个 Activity
startActivity(intent);
}
});
}
private ArrayList<Map<String,String>> converCursorToList(Cursor cursor) {
ArrayList<Map<String, String>> result = new ArrayList<>();
while (cursor.moveToNext()) {
// 将结果中的数据存入 ArrayList
Map<String, String> map = new HashMap<>();
// 取出查询记录的第二列、第三列
map.put("word",cursor.getString(1));
map.put("detail",cursor.getString(2));
result.add(map);
}
return result;
}
}
配置清单文件
<provider
android:authorities="com.shuai.providers.dictprovider"
android:name=".contentprovider2.DictProvider"
android:exported="true" />
<activity android:name=".contentprovider.MainActivity"
android:theme="@android:style/Theme.Holo"
android:label="主界面"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
(3)ContentProvider 的应用场景
场景 | 示例 |
---|---|
访问系统联系人 | 使用系统的 ContactsProvider |
共享应用数据 | 如生词本、用户信息、图片资源等 |
数据同步 | 多个 App 共享同一份数据库 |
内容观察者 | 使用 ContentObserver 监听数据变化 |
(4)ContentProvider 的优缺点
优点 | 缺点 |
---|---|
实现跨应用数据共享 | 开发复杂度较高 |
提供标准化接口 | 性能略低于直接访问数据库 |
支持数据监听(ContentObserver) | 需要配置权限和 authority |
可配合 Loader 和 CursorLoader 使用 | 不适合实时大数据量传输 |
(5)ContentProvider 的权限控制
可以通过在 AndroidManifest.xml 中添加权限来限制访问。
<provider android:name=".MyContentProvider"
android:authorities="com.example.app.myprovider"
android:readPermission="com.example.permission.READ_DATA"
android:writePermission="com.example.permission.WRITE_DATA"
android:exported="true" />
注意:从 Android 6.0 开始,还需要在运行时申请权限。
(6)ContentProvider 与数据库的关系
ContentProvider 通常封装了一个 SQLite 数据库。
它对外隐藏了具体的数据库实现细节,只暴露标准的增删改查接口。
建议使用 SQLiteOpenHelper
来管理数据库。
(7)ContentProvider 与 ContentResolver 的关系
角色 | 功能 |
---|---|
ContentProvider | 数据提供方,负责管理数据并响应请求 |
ContentResolver | 数据消费方,负责调用 ContentProvider 的方法 |
两者通过 Uri
进行通信。
(8)注意事项
注意事项 | 说明 |
---|---|
不要在主线程执行耗时操作 | 否则可能引发 ANR |
Authority 必须唯一 | 推荐使用包名作为前缀 |
exported 设置为 true 时需注意权限 | 防止未授权访问 |
Uri 匹配要准确 | 否则可能导致数据泄露或错误 |
不建议暴露敏感数据 | 如需共享应加密处理 |
六、AIDL
1. AIDL 的基础知识
(1)定义
AIDL(Android Interface Definition Language)是 Android 提供的一种接口定义语言,用于实现跨进程通信(IPC)。它允许一个应用程序通过 Binder 机制访问另一个应用中的对象和服务,适用于需要与远程 Service 或其他应用交互的场景。
(2)AIDL 的基本概念
特性 | 描述 |
---|---|
作用 | 定义跨进程通信的接口 |
底层机制 | 基于 Binder 实现,支持客户端-服务端通信 |
文件类型 | .aidl 接口文件,编译后生成 Java 接口类 |
使用方式 | 在服务端实现接口,在客户端绑定并调用方法 |
(3)AIDL 的工作原理
定义 .aidl
接口文件;
编译时系统自动生成对应的 Binder 类;
服务端实现接口,并在 Service 中返回该接口的 Binder 对象;
客户端通过 bindService()
获取接口实例;
客户端通过接口调用远程方法。
(4)AIDL 的核心组件
AIDL 接口文件(.aidl)
使用类似 Java 接口语法;
方法不能有访问修饰符;
支持基本数据类型、Parcelable、List、Map 等可序列化类型;
所有非默认包下的类都需要手动导入。
// IMyAidlInterface.aidl package com.example.aidldemo;
interface IMyAidlInterface {
int add(int a, int b);
String getMessage();
}
服务端实现 AIDL 接口
public class MyAidlService extends Service {
private final IMyAidlInterface.Stub binder = new IMyAidlInterface.Stub() {
@Override
public int add(int a, int b) {
return a + b;
}
@Override
public String getMessage() {
return "来自远程服务的消息";
}
};
@Nullable
@Override
public IBinder onBind(Intent intent) {
return binder;
}
}
客户端绑定远程服务
private IMyAidlInterface service;
private ServiceConnection connection = new ServiceConnection(){
@Override public void onServiceConnected(ComponentName name,IBinder binder){
service=IMyAidlInterface.Stub.asInterface(binder);
try{
int result=service.add(3,5);
Log.d("AIDL","调用结果:"+result);
}catch(RemoteException e){
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name){
service=null;
}
};
// 绑定远程服务
Intent intent = new Intent();
intent.setComponent(new ComponentName("com.example.remoteapp", "com.example.remoteapp.MyAidlService"));
bindService(intent, connection, Context.BIND_AUTO_CREATE);
(5)AIDL 的数据类型支持
数据类型 | 是否支持 | 说明 |
---|---|---|
基本类型(int, long, boolean 等) | ✅ | 直接支持 |
String | ✅ | 默认支持 |
List | ✅ | 元素必须为 AIDL 支持类型 |
Map | ❌ | 不推荐使用,建议改用 Bundle 或 Parcelable |
自定义对象 | ✅ | 必须实现 Parcelable 接口,并创建对应 .aidl 文件 |
CharSequence | ✅ | 支持如 Spannable、String 等 |
Bundle | ✅ | 跨进程传输常用结构 |
2. AIDL 的基础用法
(1)AIDL 自定义对象示例
Step 1:定义 Parcelable 类
public class User implements Parcelable {
private String name;
private int age;
// 构造方法、get/set...
protected User(Parcel in) {
name = in.readString();
age = in.readInt();
}
public static final Creator<User> CREATOR = new Creator<User>() {
@Override
public User createFromParcel(Parcel in) {
return new User(in);
}
@Override
public User[] newArray(int size) {
return new User[size];
}
};
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(name);
dest.writeInt(age);
}
}
Step 2:创建 User.aidl 文件
// User.aidl package com.example.aidldemo;
parcelable User;
Step 3:修改接口文件使用 User
// IMyAidlInterface.aidl package com.example.aidldemo;
import com.example.aidldemo.User;
interface IMyAidlInterface {
User getUser(int id);
}
(2)AIDL 的同步与异步调用
类型 | 是否同步 | 说明 |
---|---|---|
默认调用 | ✅ 同步 | 客户端线程会阻塞直到服务端返回结果 |
oneway 关键字 | ✅ 异步 | 客户端发送请求后不等待结果,适用于通知类操作 |
oneway interface IMyAidlInterface {
void asyncCall(String msg); // 不会阻塞客户端
}
(3)AIDL 的注册与权限控制
在 AndroidManifest.xml 中注册远程服务:
<service android:name=".MyAidlService" android:exported="true">
<intent-filter>
<action android:name="com.example.aidldemo.MyAidlService" />
</intent-filter>
</service>
android:exported="true"
:允许外部应用访问。
<action>
:用于隐式启动服务。
(4)AIDL 的应用场景
场景 | 示例 |
---|---|
远程服务调用 | 如音乐播放器后台服务 |
跨应用数据共享 | 多个 App 共享用户信息 |
插件化架构 | 主程序调用插件模块的方法 |
系统级通信 | 如系统通知栏、输入法等组件通信 |
(5)AIDL 与 Messenger 的区别
对比项 | AIDL | Messenger |
---|---|---|
底层机制 | Binder | MessageQueue |
支持多线程 | ✅ | ❌(单线程) |
通信效率 | 高 | 低 |
支持复杂逻辑 | ✅ | ❌ |
适合场景 | 复杂 IPC、高性能需求 | 简单消息传递、UI 线程交互 |
(6)AIDL 开发注意事项
注意事项 | 说明 |
---|---|
包名一致 | AIDL 接口所在的包名必须一致 |
接口更新 | 修改接口后,服务端和客户端都要重新编译 |
权限控制 | 可通过权限标签限制访问 |
进程命名 | 远程服务建议指定独立进程:android:process=":remote" |
RemoteException | 每次调用都可能抛出异常,需捕获处理 |
(7)AIDL 的优点与缺点
优点 | 缺点 |
---|---|
支持跨进程通信 | 学习成本较高 |
高性能 | 接口设计复杂 |
支持复杂数据类型 | 需要手动管理生命周期 |
支持回调(oneway、callback) | 不易调试和维护 |
(8)AIDL 的回调机制(远程回调)
AIDL 也支持从服务端回调客户端。
Step 1:定义回调接口
// IRemoteCallback.aidl package com.example.aidldemo;
interface IRemoteCallback {
void onMessageReceived(String msg);
}
Step 2:修改主接口添加注册方法
// IMyAidlInterface.aidl
interface IMyAidlInterface {
void registerCallback(IRemoteCallback callback);
void sendMessage(String msg);
}
Step 3:服务端实现回调
public class MyAidlService extends Service {
private final RemoteCallbackList<IRemoteCallback> callbacks = new RemoteCallbackList<>();
private final IMyAidlInterface.Stub binder = new IMyAidlInterface.Stub() {
@Override
public void registerCallback(IRemoteCallback callback) {
callbacks.register(callback);
}
@Override
public void sendMessage(String msg) {
final int N = callbacks.beginBroadcast();
for (int i = 0; i < N; i++) {
try {
callbacks.getBroadcastItem(i).onMessageReceived(msg);
} catch (RemoteException e) {
e.printStackTrace();
}
}
callbacks.finishBroadcast();
}
};
@Nullable
@Override
public IBinder onBind(Intent intent) {
return binder;
}
}
Step 4:客户端注册回调
private IRemoteCallback callback = new IRemoteCallback.Stub() {
@Override public void onMessageReceived(String msg) throws RemoteException {
Log.d("AIDL", "收到消息:" + msg);
}
};
// 注册回调
try {
service.registerCallback(callback);
} catch (RemoteException e) {
e.printStackTrace();
}
(9)AIDL 的生命周期管理
方法 | 说明 |
---|---|
Stub.asInterface(IBinder) | 将 IBinder 转换为接口 |
linkToDeath() / unlinkToDeath() | 监听远程服务是否死亡 |
queryLocalInterface() | 判断是否本地接口 |
asBinder() | 获取 IBinder 对象 |
(10)AIDL 的常见错误与解决办法
错误 | 原因 | 解决方案 |
---|---|---|
ClassCastException | 接口未正确声明或未匹配 | 检查 aidl 文件路径和内容 |
DeadObjectException | 远程服务已终止 | 重新绑定服务或重启 |
SecurityException | 权限不足 | 添加权限声明或动态申请 |
ClassNotFoundException | Parcelable 类未正确声明 | 确保 aidl 和 java 文件匹配 |
RemoteException | 远程调用失败 | 捕获异常并重试或提示用户 |
(11)总结表格
内容 | 说明 |
---|---|
AIDL 是什么? | Android 接口定义语言,用于跨进程通信 |
依赖技术 | Binder、IBinder、Service |
支持的数据类型 | 基本类型、String、Parcelable、List、Map(部分) |
是否支持回调 | ✅ 支持远程回调 |
是否支持同步/异步 | ✅ 同步为主,可通过 oneway 实现异步 |
是否需要权限 | ✅ 可配置读写权限 |
是否适合高频调用 | ✅ 性能高,适合频繁调用 |
是否适合大数据传输 | ❌ 推荐分页或压缩传输 |
总结
本文后续可能还会补充...
暂无评论内容