as笔记csdn_sp任务视频
activity与fragment通信
接口
在 Android 开发中,Activity
与 Fragment
的通信是一个非常常见的场景。一般来说,有三种常见方式来实现 Activity
与 Fragment
之间的通信:
✅ 一、通过接口回调(Fragment 向 Activity 通信)
这是最推荐也是最规范的方式,Fragment 向宿主 Activity 发送数据或事件。
步骤如下:
- 定义接口
public interface OnFragmentInteractionListener { void onFragmentEvent(String data);}
- Fragment 中声明接口变量并绑定
public class MyFragment extends Fragment { private OnFragmentInteractionListener mListener; @Override public void onAttach(@NonNull Context context) { super.onAttach(context); if (context instanceof OnFragmentInteractionListener) { mListener = (OnFragmentInteractionListener) context; } else { throw new RuntimeException(context.toString() + \" must implement OnFragmentInteractionListener\"); } } private void someEvent() { if (mListener != null) { mListener.onFragmentEvent(\"来自Fragment的数据\"); } }}
- Activity 实现接口
public class MainActivity extends AppCompatActivity implements OnFragmentInteractionListener { @Override public void onFragmentEvent(String data) { // 收到来自 Fragment 的数据 Log.d(\"MainActivity\", \"收到Fragment数据: \" + data); }}
fragment生命周期
onAttach和onDetach用于activity和fragment的绑定
📌 一、Created(已创建状态)
这个阶段主要是初始化 Fragment 的关键部分:
onAttach()
:Fragment 与宿主 Activity 绑定时调用。onCreate()
:初始化 Fragment,比如保留数据、初始化组件等。onCreateView()
:为 Fragment 创建视图(UI界面)。onActivityCreated()
:当宿主 Activity 的onCreate()
方法返回时调用,表明 Activity 和 Fragment 的 view 都已初始化完成。
🚀 二、Started(已启动状态)
onStart()
:Fragment 对用户可见(但还不能交互)时调用。
🟢 三、Resumed(已恢复状态)
onResume()
:Fragment 对用户可见且可以交互时调用(处于活动状态)。
⏸️ 四、Paused(已暂停状态)
onPause()
:Fragment 不再处于前台但仍可见时调用。常用于释放不需要的资源,保存数据等。
⏹️ 五、Stopped(已停止状态)
onStop()
:Fragment 完全不可见时调用。可以进一步释放资源。
❌ 六、Destroyed(已销毁状态)
Fragment 被销毁时,依次调用以下方法:
onDestroyView()
:移除与 Fragment 相关的视图。onDestroy()
:清理剩余资源。onDetach()
:Fragment 与宿主 Activity 分离。
✅ 补充说明
- Fragment 生命周期与 Activity 生命周期紧密相关,但它也拥有自己独立的回调方法。
onCreateView()
是 UI 初始化的关键;如果 Fragment 没有界面,这个方法可以返回null
。- 从
onAttach()
到onDetach()
构成了 Fragment 的完整生命周期。
生命周期切换
当你在 切换 Fragment(例如从 Fragment A 切换到 Fragment B) 时,生命周期的执行顺序会根据是否将 Fragment 加入回退栈(Back Stack) 而有所不同。下面我为你详细说明两种典型情况:
📌 情况一:未加入回退栈(默认行为)
此时切换 Fragment 是“替换”的行为,原 Fragment 会被彻底销毁,生命周期如下:
➤ Fragment A(被移除):
scss复制代码onPause() → onStop() → onDestroyView() → onDestroy() → onDetach()
➤ Fragment B(新加入):
scss复制代码onAttach() → onCreate() → onCreateView() → onActivityCreated() → onStart() → onResume()
也就是说,A 完整退出,B 全新进入。
📌 情况二:加入回退栈(通过 addToBackStack()
添加)
此时是“隐藏当前 Fragment,显示另一个 Fragment”,原 Fragment 并不会被销毁,只是被暂停:
➤ Fragment A(被隐藏):
scss复制代码onPause() → onStop()
(不会执行 onDestroyView()
,除非你显式移除)
➤ Fragment B(被显示):
scss复制代码onAttach()(若第一次加载)→ onCreate()(若第一次加载)→ onCreateView()→ onActivityCreated()→ onStart()→ onResume()
然后如果你按返回键回到 Fragment A,它的生命周期会继续:
scss复制代码onStart() → onResume()
✅ 总结:Fragment 切换生命周期对比
视图View
具体内容见ppt
视图View为那些控件,button、text view等,match_parent为填充父布局的尺寸,wrap_parewnt为包裹内容尺寸,文本有多大,空间就有多大。其他控件共有属性待补充
创建布局和View有两种方式:
Java代码和xml布局方式
容器视图 包括线性布局linear layout,相对布局 relativeLayout,帧布局frame layout,新的constrain layout
viewpager的使用流程
📄 1. 布局文件(res/layout/activity_main.xml)
<?xml version=\"1.0\" encoding=\"utf-8\"?><androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas.android.com/apk/res/android\" xmlns:app=\"http://schemas.android.com/apk/res-auto\" android:layout_width=\"match_parent\" android:layout_height=\"match_parent\"> <androidx.viewpager2.widget.ViewPager2 android:id=\"@+id/viewPager\" android:layout_width=\"0dp\" android:layout_height=\"0dp\" app:layout_constraintTop_toTopOf=\"parent\" app:layout_constraintBottom_toBottomOf=\"parent\" app:layout_constraintStart_toStartOf=\"parent\" app:layout_constraintEnd_toEndOf=\"parent\" /></androidx.constraintlayout.widget.ConstraintLayout>
🧩 2. 每一页的布局(res/layout/item_page.xml)
<?xml version=\"1.0\" encoding=\"utf-8\"?><LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\" android:orientation=\"vertical\" android:gravity=\"center\" android:layout_width=\"match_parent\" android:layout_height=\"match_parent\"> <TextView android:id=\"@+id/textViewPage\" android:layout_width=\"wrap_content\" android:layout_height=\"wrap_content\" android:textSize=\"24sp\" /></LinearLayout>
🎯 3. 页面适配器(MyPagerAdapter.java)
import android.view.LayoutInflater;import android.view.View;import android.view.ViewGroup;import android.widget.TextView;import androidx.annotation.NonNull;import androidx.recyclerview.widget.RecyclerView;import java.util.List;// 自定义适配器类,继承自 RecyclerView.Adapterpublic class MyPagerAdapter extends RecyclerView.Adapter<MyPagerAdapter.PageViewHolder> { // 用于保存每一页要显示的文本数据 private final List<String> pageData; // 构造函数:传入页面数据列表 public MyPagerAdapter(List<String> pageData) { this.pageData = pageData; } // 定义内部类 ViewHolder,用于缓存 item 视图的控件 public static class PageViewHolder extends RecyclerView.ViewHolder { TextView textView; // 用于显示页面文本内容 public PageViewHolder(@NonNull View itemView) { super(itemView); // 绑定布局中的 TextView 控件 textView = itemView.findViewById(R.id.textViewPage); } } // 创建新的 ViewHolder,加载每一页的布局 @NonNull @Override public PageViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { // 将 item_page.xml 布局转为 View 对象 View view = LayoutInflater.from(parent.getContext()) .inflate(R.layout.item_page, parent, false); return new PageViewHolder(view); // 返回新建的 ViewHolder } // 为每个 ViewHolder 设置数据 @Override public void onBindViewHolder(@NonNull PageViewHolder holder, int position) { // 将数据设置到 TextView 上 holder.textView.setText(pageData.get(position)); } // 返回页面的总数 @Override public int getItemCount() { return pageData.size(); }}
🚀 4. 主页面代码(MainActivity.java)
import android.os.Bundle;import androidx.annotation.Nullable;import androidx.appcompat.app.AppCompatActivity;import androidx.viewpager2.widget.ViewPager2;import java.util.Arrays;import java.util.List;public class MainActivity extends AppCompatActivity { @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ViewPager2 viewPager = findViewById(R.id.viewPager); List<String> pages = Arrays.asList(\"第一页\", \"第二页\", \"第三页\"); MyPagerAdapter adapter = new MyPagerAdapter(pages); viewPager.setAdapter(adapter); }}
✅ 小结
Activity组件
Activity切换代码
D:\\Android_studio\\activity_change_04072
Activity生命周期
📘 一、Activity 生命周期各阶段及方法
onCreate()
onStart()
onResume()
onPause()
onStop()
onRestart()
onDestroy()
🔁 二、常见生命周期流程
- 首次启动 Activity
onCreate() → onStart() → onResume()
- 按 Home 键退到后台
onPause() → onStop()
- 重新打开 App
onRestart() → onStart() → onResume()
- 切换到另一个 Activity
A: onPause() → onStop() B: onCreate() → onStart() → onResume()
- 销毁 Activity(如用户退出或系统回收)
onPause() → onStop() → onDestroy()
🧠 三、生命周期图示(文字版)
onCreate() ↓ onStart() ↓ onResume() ↑ onRestart() ← onStop() ↑ ↑ onPause() ← onDestroy()
🛠️ 四、实际开发中如何使用这些方法
onCreate()
onStart()
onResume()
onPause()
onStop()
onDestroy()
✅ 小提示
- 如果你只想监听App是否在前台/后台,监听
onResume()
/onStop()
就够了。 - 不要在
onCreate()
里执行耗时操作,避免界面卡顿。 - 如果
Activity
需要频繁启动/停止,比如切换界面,要合理释放资源防止内存泄漏。
Activity的生命周期
具体内容见ppt
Activity之间的跳转
,显式与隐式
隐式跳转是跳到其他app的页面,显式跳转是跳app内部页面
Activity的页面任务栈
Activity的启动模式
多页面情况下再看activity的生命周期
点击返回键退出与点击home键退出
在Java代码里的添加的模式,只有一次有效,当按返回键重新跳转到当前页面时,还是标准模式。
Activity跳转的参数传递
分散传递 打包传递(bundle)
Service组件
PPT资料:
E:\\Android\\origin_coding\\课程全套pdf-手把手教你学AndroidApp开发入门篇\\手把手教你学Androidapp开发入门篇-课程PDF 35.1-35.3
启动型startService
📦 一、什么是 Service?
Service
是 Android 的四大组件之一(Activity、Service、BroadcastReceiver、ContentProvider),用于在后台执行耗时任务,例如:
- 播放音乐
- 网络下载/上传
- 长连接推送
- 后台日志收集等
与 Activity 不同,Service 没有界面,不与用户交互。
🔁 二、Service 的生命周期
▶ 启动型 Service(startService()
)
适合执行不需要与界面交互的后台任务
生命周期方法:
onCreate()
onStartCommand()
startService()
启动都会调用onDestroy()
生命周期流程:
onCreate() → onStartCommand() → onDestroy()
onStartCommand()
是 Android 服务(Service) 中非常关键的生命周期方法,主要用于处理通过 startService()
启动服务时的逻辑。
我们一起来深入了解它的作用和用法👇
🧩 一、onStartCommand()
是什么?
它是 Service
类中的一个回调方法,在你通过:
Intent intent = new Intent(context, MyService.class);context.startService(intent);
调用启动服务时,系统会自动回调 onStartCommand()
方法。
🎯 二、它的核心作用
Intent
参数Intent
传值,执行不同任务📥 三、方法签名详解
@Overridepublic int onStartCommand(Intent intent, int flags, int startId) { // 你的服务逻辑,比如开启线程、执行任务等 return START_STICKY;}
参数含义:
Intent intent
:启动服务时传入的 Intent 对象(可携带数据)int flags
:附加信息(不常用)int startId
:本次启动的唯一标识,用于停止服务时识别
返回值说明:
START_STICKY
START_NOT_STICKY
START_REDELIVER_INTENT
START_STICKY_COMPATIBILITY
🔗 绑定型 Service(bindService()
)
适合需要和 Activity 通信的场景(如获取播放进度、传递参数等)
生命周期方法:
onCreate()
onBind()
onUnbind()
onDestroy()
🧠 三、Service 与 Thread 的区别
⚠️ 如果在
Service
中执行耗时任务,建议使用IntentService
或 手动开启子线程,避免 ANR(界面卡顿)。
🧪 四、启动 Service 的代码示例
启动型 Service 示例
- 创建服务类(
MyService.java
):
public class MyService extends Service { @Override public void onCreate() { super.onCreate(); Log.d(\"Service\", \"onCreate 被调用\"); } @Override public int onStartCommand(Intent intent, int flags, int startId) { Log.d(\"Service\", \"onStartCommand 被调用\"); // 模拟耗时任务 new Thread(() -> { // 执行后台任务 }).start(); return START_STICKY; } @Override public void onDestroy() { super.onDestroy(); Log.d(\"Service\", \"onDestroy 被调用\"); } @Nullable @Override public IBinder onBind(Intent intent) { return null; // 启动型服务返回 null }}
- 注册到
AndroidManifest.xml
:
<service android:name=\".MyService\" />
- 在 Activity 中启动/停止:
Intent intent = new Intent(this, MyService.class);startService(intent); // 启动服务stopService(intent); // 停止服务
🔗 五、常用场景
Service + MediaPlayer
IntentService
或 WorkManager
JobIntentService
(兼容旧版本)Bound Service
绑定型bindservice
📘 一、什么是 bindService
?
bindService()
是 Android 中用来将 Activity 与 Service 建立绑定关系 的方法,使得客户端可以:
- 与服务交互
- 获取服务提供的数据
- 调用服务中的方法
常用于播放进度获取、后台通信、计时器等场景。
🔁 二、绑定服务的生命周期(Bound Service)
onCreate()
onBind()
onUnbind()
onDestroy()
注意:绑定的服务不会自动销毁,你需要手动解绑。
🧱 三、实现步骤(Java 示例)
步骤 1️⃣:创建 Service 类
public class MyBindService extends Service { // 用于客户端访问服务的接口 private final IBinder binder = new LocalBinder(); // 提供给外部调用的服务方法 public String getMessage() { return \"Hello from MyBindService\"; } // 返回绑定器对象 @Nullable @Override public IBinder onBind(Intent intent) { return binder; } // 自定义 Binder public class LocalBinder extends Binder { public MyBindService getService() { return MyBindService.this; // 返回当前服务实例 } }}
步骤 2️⃣:在 AndroidManifest.xml
中注册 Service
<service android:name=\".MyBindService\" />
步骤 3️⃣:在 Activity 中绑定服务
public class MainActivity extends AppCompatActivity { private MyBindService myService; private boolean isBound = false; // 创建 ServiceConnection 实例 private final ServiceConnection connection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { // 通过 Binder 获取服务实例 MyBindService.LocalBinder binder = (MyBindService.LocalBinder) service; myService = binder.getService(); isBound = true; // 调用服务方法 String msg = myService.getMessage(); Toast.makeText(MainActivity.this, msg, Toast.LENGTH_SHORT).show(); } @Override public void onServiceDisconnected(ComponentName name) { isBound = false; } }; @Override protected void onStart() { super.onStart(); // 绑定服务 Intent intent = new Intent(this, MyBindService.class); bindService(intent, connection, Context.BIND_AUTO_CREATE); } @Override protected void onStop() { super.onStop(); // 解绑服务 if (isBound) { unbindService(connection); isBound = false; } }}
✅ 四、关键参数说明
bindService(intent, connection, Context.BIND_AUTO_CREATE);
intent
connection
BIND_AUTO_CREATE
⚠️ 五、注意事项
- 若 Service 没有客户端绑定,默认系统可能会销毁(除非是前台服务)。
- 不要在
onServiceConnected
里执行耗时任务。 - 服务通信对象(
IBinder
)可以拓展实现更复杂的 AIDL(跨进程通信)。
🧪 常见应用场景
前台service
Activity与Service之间的关系类型
- Activity 启动 Service
Activity 可以通过以下方式启动 Service:
➤ 启动型 Service:
startService(new Intent(this, MyService.class));
- Activity 和 Service 没有直接通信通道
- Service 会一直运行,直到调用
stopService()
或stopSelf()
➤ 绑定型 Service:
bindService(intent, connection, Context.BIND_AUTO_CREATE);
- Activity 和 Service 通过
IBinder
进行通信 - Activity 可调用 Service 中的公开方法
👉 若 Activity 销毁,也应及时解绑 Service,避免内存泄漏。
- Activity 与 Service 通信(双向交互)
常见方式包括:
IBinder
对象Handler
+ Messenger
BroadcastReceiver
EventBus
(第三方)广播组件(BroadcastReceiver)
在 Android 中,广播组件(BroadcastReceiver) 是四大组件之一,主要用于监听和响应系统或应用内部发送的 广播消息。这种机制允许应用之间或应用内部不同组件之间进行松耦合的消息传递。
下面我将从原理、使用方法、生命周期、应用场景、代码示例等多个方面为你详细讲解。
📘 一、BroadcastReceiver 是什么?
BroadcastReceiver
是一个 监听广播消息的组件,可以响应来自系统或应用内的广播事件,如:
- 系统广播(如电量变化、网络变化、开机完成)
- 自定义广播(应用内广播消息)
📦 二、广播的分类
🔁 三、BroadcastReceiver 生命周期
BroadcastReceiver 没有独立的生命周期,它的生命周期非常短:
- 当广播到来时,系统自动调用其
onReceive()
方法 - 执行完
onReceive()
后立即销毁(不适合耗时操作)
✅ 四、使用步骤
步骤 1️⃣:创建广播接收者类 MyReceiver.java
public class MyReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); // com.example.MY_BROADCAST是action 可以作为唯一标识符 if (\"com.example.MY_BROADCAST\".equals(action)) { Toast.makeText(context, \"收到自定义广播\", Toast.LENGTH_SHORT).show(); } }}
步骤 2️⃣:注册广播
(1)静态注册(写在 AndroidManifest.xml 中)
xml复制代码<receiver android:name=\".MyReceiver\"> <intent-filter> <action android:name=\"com.example.MY_BROADCAST\"/> </intent-filter></receiver>
⚠️ Android 8.0(API 26)之后,大部分 静态注册 的广播(特别是隐式广播)被限制,建议使用动态注册。
(2)动态注册(推荐)(在mainActivity中动态注册)
private MyReceiver receiver;@Overrideprotected void onStart() { super.onStart(); // 在onstart中注册广播(订阅) // 动态使用Java代码注册一个广播接收者 receiver = new MyReceiver(); IntentFilter filter = new IntentFilter(\"com.example.MY_BROADCAST\"); registerReceiver(receiver, filter);}// 在动态注册中,注册接收者和发送信息是在同一个activity中@Overrideprotected void onStop() { super.onStop(); unregisterReceiver(receiver);}
步骤 3️⃣:发送广播 (写在mainActivity中)
Intent intent = new Intent(\"com.example.MY_BROADCAST\");sendBroadcast(intent); // 普通广播
🧠 五、常见系统广播
Intent.ACTION_BOOT_COMPLETED
Intent.ACTION_BATTERY_CHANGED
Intent.ACTION_AIRPLANE_MODE_CHANGED
ConnectivityManager.CONNECTIVITY_ACTION
SmsReceiver.ACTION_SMS_RECEIVED
🛠️ 六、注意事项
- ❌ 不能在
onReceive()
里执行耗时操作,否则会触发 ANR(应用无响应) - ✅ 可使用
IntentService
或开启子线程做耗时任务 - 🆕 Android 8.0 后必须使用动态注册监听大部分广播(除了系统明确允许的)
🧪 七、应用场景示例
CONNECTIVITY_ACTION
ACTION_PACKAGE_ADDED
等✅ 八、总结:BroadcastReceiver 特点
onReceive()
存在具体内容见ppt
静态广播与动态广播
静态广播注册界面的action可以加多个,action代表了可以接收与发送方的action相匹配的广播信息
Intent.setPackage()是解决Android 8.0以上,静态隐式广播无法发送的问题
动态注册时,接收方可以单独写一个接收.java文件,也可以直接在activity文件中写接收方和发送方
无序广播与有序广播
有序广播进行修改广播时,是对同一个发送的intent进行修改,即在上一个intent信息的基础上,添加新的键值对,下一个广播通过键值获取字段值。
ContentProvider组件
📘 一、ContentProvider 介绍
- 什么是 ContentProvider?
ContentProvider
是一个 数据共享机制,它为不同的应用提供统一的接口来访问数据。通过它,应用可以共享数据,而不必直接暴露数据库或文件。其他应用可以使用 ContentResolver
与 ContentProvider
进行交互。
- 常见用途:
- 访问系统数据(例如联系人、短信)
- 应用内部数据库共享
- 与其他应用进行数据交互
🛠️ 二、如何使用 ContentProvider?
步骤 1️⃣:创建 ContentProvider (创建MyContentProvider.java)
public class MyContentProvider extends ContentProvider { @Override public boolean onCreate() { // 初始化操作,如打开数据库 return true; } @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { // 返回查询结果 return null; } @Override public Uri insert(Uri uri, ContentValues values) { // 插入数据 return null; } @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { // 更新数据 return 0; } @Override public int delete(Uri uri, String selection, String[] selectionArgs) { // 删除数据 return 0; } @Override public String getType(Uri uri) { // 返回数据类型 return null; }}
步骤 2️⃣:注册 ContentProvider
在 AndroidManifest.xml
中注册:
<provider android:name=\".MyContentProvider\" android:authorities=\"com.example.myapp.provider\" android:exported=\"true\" />
步骤 3️⃣:访问 ContentProvider (在mainActivity中使用)
通过 ContentResolver
来访问数据:
ContentResolver resolver = getContentResolver();Uri uri = Uri.parse(\"content://com.example.myapp.provider/data\");Cursor cursor = resolver.query(uri, null, null, null, null);
🧠 三、ContentProvider 生命周期
ContentProvider
的生命周期较长,它通常在应用程序启动时创建,并且在整个应用程序生命周期内有效。onCreate()
方法在 ContentProvider
被首次访问时调用。
✅ 四、ContentProvider 特点
ContentResolver
和 Uri
进行交互🧪 五、常见应用场景
ContentProvider
向其他应用暴露数据ContentProvider
将数据库暴露给应用的其他组件或其他应用Andriod常用框架——热修复
Tinker是微信开源的一个热修复解决方案,支持dex、库和资源更新,无需重新安装apk。开源地址:https://github.com/Tencent/tinker
可以直接使用Tinker,也可以使用腾讯Bugly服务集成Tinker热修复,后者提供了补丁管理服务建议大家根据官方案例工程进行使用,按照Bugly文档由于版本问题会导致无法使用
https://github.com/BuglyDevTeam/Bugly-Android-Demo/tree/master/BuglyHotfixDemo
版本不一致,但是流程应该近似
SampleApplicationLike中,appId替换成你的在Bugly平台申请的appId
AppId为在bugly平台注册软件得到的号码
Glide使用
引入到build.gradle文件中
命令是在MainActivity中使用
过渡动画设置
变换
Glide更简单的使用
其中依赖是在build.gridle中,AppGlideModule为自己定义的java文件,
将频繁使用的glide功能自己定义一个集合,使用时命令就简单了
网络加载框架OkHttp与Retrofit
同步请求
Call call=okHttpclient.newCall(request); 会造成线程的阻塞
异步请求
不用创建单独的线程,不会阻塞
OkHttp有GET和POST请求
.add( name:“a”,value:“1”).add( name:“b”, value:\"2”)为提交的数据
OkHttp配置
当然可以!下面是关于 OkHttp 的配置与使用教程(Java 版),适合在 Android 开发中进行高效的网络请求操作,包括:基本配置、请求方式(GET、POST)、拦截器、超时设置等内容。
🔧 一、引入 OkHttp 依赖
✅ 在 build.gradle
(app)中添加依赖:
dependencies { implementation \'com.squareup.okhttp3:okhttp:4.12.0\' // 可替换为最新版本}
📦 二、创建 OkHttpClient 实例(全局可复用)
OkHttpClient client = new OkHttpClient();
⚠️ 建议使用单例(全局复用一个
OkHttpClient
对象),避免资源浪费。
🌐 三、常用请求示例
1️⃣ GET 请求
Request request = new Request.Builder() .url(\"https://api.example.com/data\") .build();client.newCall(request).enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { e.printStackTrace(); } @Override public void onResponse(Call call, Response response) throws IOException { if (response.isSuccessful()) { String result = response.body().string(); Log.d(\"OkHttp\", \"响应结果:\" + result); } }});
2️⃣ POST 请求(携带 JSON)
MediaType JSON = MediaType.get(\"application/json; charset=utf-8\");String jsonBody = \"{\\\"name\\\":\\\"张三\\\", \\\"age\\\":25}\";RequestBody body = RequestBody.create(jsonBody, JSON);Request request = new Request.Builder() .url(\"https://api.example.com/user\") .post(body) .build();client.newCall(request).enqueue(...); // 同上
3️⃣ POST 表单上传(application/x-www-form-urlencoded
)
RequestBody formBody = new FormBody.Builder() .add(\"username\", \"zhangsan\") .add(\"password\", \"123456\") .build();Request request = new Request.Builder() .url(\"https://api.example.com/login\") .post(formBody) .build();client.newCall(request).enqueue(...);
⏱️ 四、配置 OkHttpClient(超时、拦截器、重定向)
OkHttpClient client = new OkHttpClient.Builder() .connectTimeout(10, TimeUnit.SECONDS) // 连接超时 .readTimeout(20, TimeUnit.SECONDS) // 读取超时 .writeTimeout(15, TimeUnit.SECONDS) // 写入超时 .retryOnConnectionFailure(true) // 自动重试 .addInterceptor(new LoggingInterceptor()) // 自定义拦截器 .build();
🧩 五、添加日志拦截器(查看请求和响应)
public class LoggingInterceptor implements Interceptor { @Override public Response intercept(Chain chain) throws IOException { Request request = chain.request(); long t1 = System.nanoTime(); Log.d(\"OkHttp\", String.format(\"发送请求 %s%n%s\", request.url(), request.headers())); Response response = chain.proceed(request); long t2 = System.nanoTime(); Log.d(\"OkHttp\", String.format(\"接收响应 %s in %.1fms%n%s\", response.request().url(), (t2 - t1) / 1e6d, response.headers())); return response; }}
🧠 六、注意事项
- OkHttp 的
enqueue()
方法是异步请求,不能直接更新 UI,需用 Handler 或 runOnUiThread 切回主线程。 response.body().string()
只能调用一次,会关闭流。- POST 请求建议设置超时及日志,方便调试。
- 不建议每次都创建新的
OkHttpClient
,影响性能。
✅ 总结:OkHttp 配置要点
Request
+ RequestBody
enqueue()
OkHttpClient.Builder()
addInterceptor()
connectTimeout()
、readTimeout()
static OkHttpClient
Retrofit简介
写在HttpbinService.java中,是一个接口文件
在 Android 开发中,Retrofit 是基于 OkHttp 封装的网络请求库,简化了网络通信流程。而它的强大之处之一,就是通过**注解(Annotations)**来描述网络请求的结构和参数,做到接口即调用。
📘 一、Retrofit 注解的作用:
Retrofit 使用 Java 注解来定义网络请求的 类型、路径、参数、请求体、头部信息等,使你只需定义一个接口,就能自动完成网络调用。
✅ 核心理念:用注解代替繁琐的请求构建过程
Retrofit转换器
📘 一、什么是 Retrofit 的转换器?
Retrofit 默认只返回
ResponseBody
,如果你想直接接收到 Java 对象(比如实体类),就需要一个“转换器(Converter)”来自动解析数据格式。
换句话说:
🔧 二、常见转换器类型
com.squareup.retrofit2:converter-gson
converter-moshi
converter-scalars
converter-simplexml
converter-jackson
🔍 三、最常用:GsonConverter 使用方式
✅ 步骤 1️⃣:添加依赖
gradle复制代码implementation \'com.squareup.retrofit2:converter-gson:2.9.0\'
✅ 步骤 2️⃣:构建 Retrofit 实例并设置转换器
java复制代码Retrofit retrofit = new Retrofit.Builder() .baseUrl(\"https://api.example.com/\") .addConverterFactory(GsonConverterFactory.create()) // 添加 Gson 转换器 .build();
✅ 步骤 3️⃣:定义 API 接口
java复制代码public interface ApiService { @GET(\"user/{id}\") Call getUser(@Path(\"id\") int userId);}
如果返回的是:
json复制代码{ \"id\": 1, \"name\": \"张三\", \"email\": \"zhangsan@example.com\"}
Retrofit 会自动将其转为如下实体类:
java复制代码public class User { public int id; public String name; public String email;}
✨ 四、多个转换器搭配使用
Retrofit 支持链式转换器,例如:
java复制代码Retrofit retrofit = new Retrofit.Builder() .baseUrl(\"https://api.example.com/\") .addConverterFactory(ScalarsConverterFactory.create()) // 放前面,处理基础类型 .addConverterFactory(GsonConverterFactory.create()) // 后面处理 JSON .build();
❗ 顺序很重要,Retrofit 会按注册顺序查找能处理的数据类型。
🧠 五、什么时候用哪些转换器?
GsonConverterFactory
✅SimpleXmlConverterFactory
ScalarsConverterFactory
Moshi
或 Jackson
✅ 六、总结
.addConverterFactory(GsonConverterFactory.create())
.addConverterFactory(ScalarsConverterFactory.create())
.addConverterFactory(SimpleXmlConverterFactory.create())
Retrofit嵌套请求与适配器
先登录再获取列表,直接嵌套会显得非常冗余
转换器适配器的添加
Retrofit适配器
Retrofit 的适配器(CallAdapter / Adapter),这是 Retrofit 架构中另一个重要扩展点,用来决定你接口定义中方法的返回类型,比如你是否要用:
Call
LiveData
RxJava 的 Observable
Kotlin 的 suspend
函数
下面我来详细讲解 Retrofit 的适配器作用、原理、常用类型、配置方式以及实际场景使用👇
📘 一、什么是 Retrofit 的适配器(CallAdapter)?
🔍 定义:
CallAdapter 决定你接口方法的返回类型是 同步/异步/响应式等,而且能与其他框架(如 LiveData、RxJava、协程等)结合使用。
🧠 举个例子:
java复制代码@GET(\"user/{id}\")Call getUser(@Path(\"id\") int id); // 使用默认 CallAdapter// 或者改成 RxJavaObservable getUserRx(@Path(\"id\") int id); // 使用 RxJavaCallAdapter
📌 Retrofit 内部将请求结果通过 CallAdapter 转换为你想要的返回类型!
🧩 二、常见 CallAdapter 类型及用途
Call
RxJava2CallAdapterFactory
Observable
/ Single
CoroutineCallAdapterFactory
suspend fun
LiveDataCallAdapter
(自定义)LiveData
🔧 三、如何配置 CallAdapter?
✅ 1. 默认(不需要配置)
Retrofit 默认支持 Call
,你只需这样写:
java复制代码@GET(\"user/{id}\")Call getUser(@Path(\"id\") int id);
✅ 2. RxJava2 / RxJava3 适配器配置
添加依赖:
groovy复制代码implementation \'com.squareup.retrofit2:adapter-rxjava2:2.9.0\' // 或 rxjava3implementation \'io.reactivex.rxjava2:rxjava:2.x.x\'
配置 Retrofit:
java复制代码Retrofit retrofit = new Retrofit.Builder() .baseUrl(\"https://api.example.com/\") .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) .addConverterFactory(GsonConverterFactory.create()) .build();
接口使用 RxJava:
java复制代码@GET(\"user/{id}\")Observable getUserRx(@Path(\"id\") int id);
✅ 3. Kotlin 协程适配器(推荐 Kotlin 开发)
添加依赖(Retrofit 2.6+ 内置支持协程 CallAdapter):
suspend fun getUser(@Path(\"id\") Int): User
Retrofit 配置:
kotlin复制代码Retrofit.Builder() .baseUrl(\"https://api.example.com/\") .addConverterFactory(GsonConverterFactory.create()) .build()
✅ 不再需要显式添加 CallAdapter,Retrofit 会自动识别
suspend
✅ 4. 自定义 LiveData 适配器(进阶)
适合 MVVM + Jetpack
架构:
java复制代码@GET(\"user/{id}\")LiveData<ApiResponse> getUserLiveData();
需要手动实现 CallAdapterFactory
,或使用 Jetpack Lifecycle Retrofit Adapter
🎯 四、总结:CallAdapter 用法对比
Call
Observable
/ Single
RxJava2CallAdapterFactory
suspend fun
LiveData
🧪 五、实际场景推荐
Call
or RxJavasuspend
协程LiveData
✅ 六、总结一句话
Retrofit 的 CallAdapter 就是你接口返回什么类型,Retrofit 就如何包装请求结果的“适配桥梁”。
Retrofit文件上传与下载
Retrofit 不仅可以处理常规的 GET/POST 请求,也支持 文件上传(如图片、视频、文档) 和 文件下载(如APK、PDF等),并且结合 OkHttp 可以做到高效、可靠且支持进度监听。
📤 一、Retrofit 上传文件(支持多文件、多参数)
✅ 步骤 1:接口定义(使用 @Multipart + @Part
)
java复制代码@Multipart@POST(\"upload\")Call uploadFile( @Part MultipartBody.Part file, // 单个文件 @Part(\"description\") RequestBody description // 额外参数);
✅ 步骤 2:Java 调用上传文件
java复制代码// 创建文件对象File file = new File(\"/storage/emulated/0/DCIM/test.jpg\");// 创建 RequestBody(设置媒体类型)RequestBody requestFile = RequestBody.create( file, MediaType.parse(\"image/jpeg\"));// 包装成 MultipartBody.PartMultipartBody.Part body = MultipartBody.Part.createFormData(\"file\", file.getName(), requestFile);// 其他参数(文本字段)RequestBody desc = RequestBody.create(\"上传说明\", MediaType.parse(\"text/plain\"));// 发起请求api.uploadFile(body, desc).enqueue(new Callback() { @Override public void onResponse(Call call, Response response) { Log.d(\"上传成功\", \"code: \" + response.code()); } @Override public void onFailure(Call call, Throwable t) { Log.e(\"上传失败\", t.getMessage()); }});
📁 多文件上传
java复制代码@Multipart@POST(\"upload/multi\")Call uploadMultipleFiles(@Part List files);
你可以将多个文件都构造为 MultipartBody.Part
并一并上传。
📥 二、Retrofit 下载文件(保存到本地)
✅ 步骤 1:接口定义(使用 @Streaming
防止内存溢出)
java复制代码@Streaming@GETCall downloadFile(@Url String fileUrl);
@Streaming
关键字避免大文件加载进内存造成 OOM。
✅ 步骤 2:下载并保存到本地(Java 实现)
java复制代码Call call = api.downloadFile(\"https://example.com/file.apk\");call.enqueue(new Callback() { @Override public void onResponse(Call call, Response response) { boolean result = writeResponseBodyToDisk(response.body()); Log.d(\"下载\", result ? \"成功\" : \"失败\"); } @Override public void onFailure(Call call, Throwable t) { Log.e(\"下载失败\", t.getMessage()); }});// 保存到本地方法private boolean writeResponseBodyToDisk(ResponseBody body) { try { File file = new File(getExternalFilesDir(null) + \"/downloaded_file.apk\"); InputStream inputStream = null; OutputStream outputStream = null; try { byte[] fileReader = new byte[4096]; inputStream = body.byteStream(); outputStream = new FileOutputStream(file); while (true) { int read = inputStream.read(fileReader); if (read == -1) break; outputStream.write(fileReader, 0, read); } outputStream.flush(); return true; } catch (IOException e) { return false; } finally { if (inputStream != null) inputStream.close(); if (outputStream != null) outputStream.close(); } } catch (IOException e) { return false; }}
🧠 三、上传与下载的注意事项
@Multipart + POST
@Streaming + GET
image/*
, video/*
等@Streaming
✅ 四、总结
@Multipart + @Part
POST
@Part List
POST
@Streaming + @GET
GET
Json解析框架——Gson
Java对象序列化与反序列化
gson.toJson()的使用
Java嵌套对象序列化与反序列化
Array数组的序列化与反序列化
List的序列化与反序列化
Map与Set的序列化与反序列化
RXJava
响应式编程思维
什么是响应式编程:根据上一层的响应,影响下一层的变化
RxJava RXJS RxXXX RX系列框架为什么把所有函数都称为操作符 因为我们的函数要去操作 从起点 流向 终点7
🚀 一、RxJava 是什么?
RxJava(ReactiveX for Java)是一种响应式编程框架,核心思想是:
“一切皆数据流(Stream)”,数据的产生、变换、消费通过链式操作完成。
📦 二、RxJava 三大核心角色
Observable
/ Flowable
Observer
/ Subscriber
subscribe()
📌 三、基础使用示例(RxJava2)
java复制代码Observable observable = Observable.just(\"Hello RxJava\");Observer observer = new Observer() { @Override public void onSubscribe(Disposable d) { Log.d(\"Rx\", \"订阅开始\"); } @Override public void onNext(String s) { Log.d(\"Rx\", \"收到数据:\" + s); } @Override public void onError(Throwable e) { Log.e(\"Rx\", \"出错:\" + e.getMessage()); } @Override public void onComplete() { Log.d(\"Rx\", \"数据接收完毕\"); }};// 建立连接observable.subscribe(observer);
💡 四、线程切换(Schedulers)
RxJava 内置强大的线程调度功能:
Schedulers.io()
Schedulers.computation()
Schedulers.newThread()
AndroidSchedulers.mainThread()
示例:
java复制代码Observable.just(\"网络数据\") .subscribeOn(Schedulers.io()) // 在子线程执行发射 .observeOn(AndroidSchedulers.mainThread()) // 在主线程处理结果 .subscribe(s -> { Log.d(\"Rx\", \"UI线程收到:\" + s); });
⚙️ 五、常用操作符(链式操作的核心)
map()
flatMap()
filter()
debounce()
zip()
java复制代码Observable.just(\"100\") .map(s -> Integer.parseInt(s)) // String → int .subscribe(i -> Log.d(\"Rx\", \"数字:\" + i));
📱 六、结合 Retrofit 使用
配置 Retrofit + RxJava:
gradle复制代码implementation \'com.squareup.retrofit2:adapter-rxjava2:2.9.0\'implementation \'io.reactivex.rxjava2:rxjava:2.2.21\'implementation \'io.reactivex.rxjava2:rxandroid:2.1.1\'
接口定义:
java复制代码@GET(\"user/info\")Observable getUserInfo();
发起请求:
java复制代码api.getUserInfo() .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(user -> showUser(user), error -> showError(error));
✅ 七、RxJava 优缺点
🧠 八、RxJava 推荐使用场景
- 🧲 网络请求链式处理(配合 Retrofit)
- 🔍 搜索输入防抖
- ⏱️ 定时器、倒计时、轮询
- ⚡ 多请求并发(如同时请求多个接口并合并数据)
- 🎯 按事件驱动 UI(响应式 UI 绑定)
🧰 九、注意事项
- 记得释放资源 →
Disposable.dispose()
,建议使用CompositeDisposable
- 避免内存泄漏 → 绑定生命周期(可用
RxLifecycle
、AutoDispose
) - 尽量链式写法 + Lambda 简洁表达
RxJava 下载图片的完整流程思维
目标:从网络下载图片,并展示到 ImageView 中
🌐 基本流程思维图:
mathematica复制代码下载URL → 发起请求 → 获取InputStream → 转Bitmap → 显示到ImageView
这个过程可以用 RxJava 表达为:
java复制代码Observable.just(imageUrl) .subscribeOn(Schedulers.io()) // 在IO线程下载图片 .map(url -> downloadBitmap(url)) // 转为 Bitmap .observeOn(AndroidSchedulers.mainThread()) // 回到主线程更新UI .subscribe(bitmap -> imageView.setImageBitmap(bitmap));
🧰 二、完整示例代码(Java)
📦 工具方法:下载图片(返回 Bitmap)
java复制代码private Bitmap downloadBitmap(String urlStr) throws IOException { URL url = new URL(urlStr); HttpURLConnection connection = (HttpURLConnection) url.openConnection(); connection.setDoInput(true); connection.connect(); InputStream input = connection.getInputStream(); return BitmapFactory.decodeStream(input);}
🚀 使用 RxJava 下载图片并展示
java复制代码String imageUrl = \"https://example.com/image.jpg\";Observable.just(imageUrl) .subscribeOn(Schedulers.io()) // 网络请求在线程池 .map(this::downloadBitmap) // 将 URL → Bitmap .observeOn(AndroidSchedulers.mainThread()) // 回到主线程设置 UI .subscribe( bitmap -> imageView.setImageBitmap(bitmap), throwable -> Log.e(\"RxDownload\", \"下载失败: \" + throwable.getMessage()) );
🎨 三、补充:使用 Glide 或 Picasso 替代手写下载?
虽然 RxJava 能做到非常优雅,但如果你只是下载图片显示,推荐使用:
- Glide:
Glide.with(context).load(url).into(imageView);
- Picasso:
Picasso.get().load(url).into(imageView);
✅ 它们内部已处理线程、缓存、错误处理,非常适合直接用。
📌 四、RxJava 适合的进阶场景
flatMap()
或 zip()
并发处理map()
写入文件retry()
自动重试机制Flowable
或自定义 Observable 发射进度值细节代码
🧩 一、实现 downloadBitmap(String urlStr)
方法
这个方法从网络上下载图片并返回 Bitmap
:
java复制代码private Bitmap downloadBitmap(String urlStr) throws IOException { URL url = new URL(urlStr); HttpURLConnection connection = (HttpURLConnection) url.openConnection(); connection.setDoInput(true); connection.connect(); InputStream input = connection.getInputStream(); return BitmapFactory.decodeStream(input); // 解析图片流成Bitmap}
注意要
import java.net.*;
和android.graphics.BitmapFactory;
🧱 二、确保引入的 RxJava + AndroidSchedulers 依赖
在你的 build.gradle
中添加以下依赖:
gradle复制代码implementation \'io.reactivex.rxjava2:rxjava:2.2.21\'implementation \'io.reactivex.rxjava2:rxandroid:2.1.1\'
然后在 Java 文件中引入:
java复制代码import io.reactivex.Observable;import io.reactivex.android.schedulers.AndroidSchedulers;import io.reactivex.schedulers.Schedulers;
🧷 三、确保你的代码处于 Activity 或 Fragment 中,并 imageView 是有效对象
java复制代码ImageView imageView = findViewById(R.id.imageView); // 确保已绑定 ImageViewString imageUrl = \"https://example.com/image.jpg\"; // 替换为你自己的图片链接
📦 四、完整整合示例(可直接运行)
java复制代码public class MainActivity extends AppCompatActivity { private ImageView imageView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); imageView = findViewById(R.id.imageView); String imageUrl = \"https://example.com/image.jpg\"; // 替换为真实链接 Observable.just(imageUrl) .subscribeOn(Schedulers.io()) // 下载图片在子线程 .map(this::downloadBitmap) // URL → Bitmap .observeOn(AndroidSchedulers.mainThread()) // 回主线程更新 UI .subscribe(bitmap -> imageView.setImageBitmap(bitmap), throwable -> Log.e(\"RxImage\", \"下载失败\", throwable)); } // 实现下载并转为Bitmap的方法 private Bitmap downloadBitmap(String urlStr) throws IOException { URL url = new URL(urlStr); HttpURLConnection connection = (HttpURLConnection) url.openConnection(); connection.setDoInput(true); connection.connect(); InputStream input = connection.getInputStream(); return BitmapFactory.decodeStream(input); }}
🔐 权限(如需访问网络)
确保在 AndroidManifest.xml
中加入权限:
xml复制代码
✅ 小结
你实现的每一个函数/步骤:
downloadBitmap(url)
Schedulers.io()
AndroidSchedulers.mainThread()
Observable.just()
数据存储
数据存储在APP内部,data文件夹
数据存储有哪些
SP存储
很小很简单的数据可以保存到首选项SP里面去
数据文件夹在Device File Explorer,data/data/com.xxx.myproject/
SQLite存储
SQLite增删改查
通过继承 SQLiteOpenHelper
类来创建数据库和表,管理版本升级等。下面我将一步一步带你实现:
📘 一、基本结构:SQLite 创建数据库与表的方式
你需要做的主要步骤:
- 创建一个继承自
SQLiteOpenHelper
的类 - 在
onCreate()
方法中执行建表 SQL - 使用
getWritableDatabase()
或getReadableDatabase()
获取数据库操作对象
🧱 二、示例:创建数据库和一个用户表
👇 1. 创建数据库帮助类 MyDatabaseHelper.java
java复制代码public class MyDatabaseHelper extends SQLiteOpenHelper { private static final String DATABASE_NAME = \"MyApp.db\"; // 数据库名 private static final int DATABASE_VERSION = 1; // 数据库版本 // 表名和建表语句 public static final String TABLE_USER = \"user\"; private static final String CREATE_TABLE_USER = \"CREATE TABLE \" + TABLE_USER + \" (\" + \"id INTEGER PRIMARY KEY AUTOINCREMENT, \" + \"name TEXT NOT NULL, \" + \"age INTEGER, \" + \"email TEXT UNIQUE)\"; public MyDatabaseHelper(Context context) { super(context, DATABASE_NAME, null, DATABASE_VERSION); } // 创建数据库时调用,只执行一次 @Override public void onCreate(SQLiteDatabase db) { db.execSQL(CREATE_TABLE_USER); // 执行建表语句 } // 数据库升级时调用 @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { // 可选:表结构变更时执行 db.execSQL(\"DROP TABLE IF EXISTS \" + TABLE_USER); onCreate(db); // 重新创建 }}
👇 2. 在 Activity 中使用
java复制代码public class MainActivity extends AppCompatActivity { private MyDatabaseHelper dbHelper; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 创建数据库和表(如不存在) dbHelper = new MyDatabaseHelper(this); SQLiteDatabase db = dbHelper.getWritableDatabase(); // 会自动调用 onCreate 或 onUpgrade }}
📦 三、建表语句详解
sql复制代码CREATE TABLE user ( id INTEGER PRIMARY KEY AUTOINCREMENT, -- 主键自增 name TEXT NOT NULL, -- 不允许为空 age INTEGER, -- 整型 email TEXT UNIQUE -- 唯一约束)
你可以根据需要增加更多字段或创建多个表。
🛠️ 四、常用的 SQLite 数据类型
int
, long
float
, double
String
byte[]
🧠 五、小技巧
SQLiteDatabase
有两种获取方式:getReadableDatabase()
和getWritableDatabase()
。- 不需要自己手动创建
.db
文件,系统会自动创建在/data/data/包名/databases/
目录下。 - 升级数据库时记得修改版本号并处理
onUpgrade()
方法逻辑。 - 表创建完成后,可以用
insert()
、update()
、delete()
、query()
等方法操作数据。
SQLite增删改查
📦 表结构回顾
我们之前建表语句是:
sql复制代码CREATE TABLE user ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, age INTEGER, email TEXT UNIQUE)
✅ 一、插入数据(Insert)
java复制代码public void insertUser(SQLiteDatabase db, String name, int age, String email) { ContentValues values = new ContentValues(); values.put(\"name\", name); values.put(\"age\", age); values.put(\"email\", email); long rowId = db.insert(\"user\", null, values); if (rowId != -1) { Log.d(\"SQLite\", \"插入成功,ID:\" + rowId); } else { Log.d(\"SQLite\", \"插入失败\"); }}
🔍 二、查询数据(Query)
java复制代码public void queryAllUsers(SQLiteDatabase db) { Cursor cursor = db.query(\"user\", null, null, null, null, null, null); while (cursor.moveToNext()) { int id = cursor.getInt(cursor.getColumnIndexOrThrow(\"id\")); String name = cursor.getString(cursor.getColumnIndexOrThrow(\"name\")); int age = cursor.getInt(cursor.getColumnIndexOrThrow(\"age\")); String email = cursor.getString(cursor.getColumnIndexOrThrow(\"email\")); Log.d(\"SQLite\", \"用户:\" + id + \", \" + name + \", \" + age + \", \" + email); } cursor.close();}
📝 三、更新数据(Update)
java复制代码public void updateUserAge(SQLiteDatabase db, String email, int newAge) { ContentValues values = new ContentValues(); values.put(\"age\", newAge); int rows = db.update(\"user\", values, \"email = ?\", new String[]{email}); Log.d(\"SQLite\", \"更新了 \" + rows + \" 条记录\");}
❌ 四、删除数据(Delete)
java复制代码public void deleteUserByEmail(SQLiteDatabase db, String email) { int rows = db.delete(\"user\", \"email = ?\", new String[]{email}); Log.d(\"SQLite\", \"删除了 \" + rows + \" 条记录\");}
📄 五、完整调用示例(在 Activity 中使用)
java复制代码MyDatabaseHelper dbHelper = new MyDatabaseHelper(this);SQLiteDatabase db = dbHelper.getWritableDatabase();insertUser(db, \"张三\", 25, \"zhangsan@example.com\");queryAllUsers(db);updateUserAge(db, \"zhangsan@example.com\", 30);deleteUserByEmail(db, \"zhangsan@example.com\");
🧠 小技巧 & 建议
ContentValues
Cursor
close()
?
db.execSQL()
Room存储
Room是SQLite数据库的抽象
使用注解
Room实现增删改查
Intent
Intent传递基本类型
在 Android 中,Intent
是用于不同 Activity 或 组件 之间进行通信的主要方式。通过 Intent
,你可以在 Activity 之间传递各种类型的数据,特别是 基本数据类型 和 对象。
下面我将详细介绍如何通过 Intent
传递常见的基本数据类型(如 int
, String
, boolean
, float
等)。
📘 一、如何传递基本数据类型?
Intent
提供了一个方法 putExtra()
来将数据传递给目标 Activity
,接收方则通过 getIntent().get...Extra()
来获取这些数据。
基本数据类型支持的方式:
boolean
putExtra(key, value)
和 getBooleanExtra(key, defaultValue)
int
putExtra(key, value)
和 getIntExtra(key, defaultValue)
float
putExtra(key, value)
和 getFloatExtra(key, defaultValue)
String
putExtra(key, value)
和 getStringExtra(key)
long
putExtra(key, value)
和 getLongExtra(key, defaultValue)
double
putExtra(key, value)
和 getDoubleExtra(key, defaultValue)
🧩 二、代码示例
👇 1. 传递数据(在 源 Activity 中)
java复制代码Intent intent = new Intent(this, SecondActivity.class);intent.putExtra(\"user_name\", \"John Doe\");intent.putExtra(\"user_age\", 25);intent.putExtra(\"is_active\", true);intent.putExtra(\"user_score\", 95.5f);startActivity(intent);
这里我们通过
putExtra()
将String
,int
,boolean
,float
四种基本数据类型传递给目标SecondActivity
。
👇 2. 接收数据(在 目标 Activity 中)
java复制代码Intent intent = getIntent();// 获取基本数据类型String userName = intent.getStringExtra(\"user_name\");int userAge = intent.getIntExtra(\"user_age\", 0); // 第二个参数是默认值boolean isActive = intent.getBooleanExtra(\"is_active\", false);float userScore = intent.getFloatExtra(\"user_score\", 0.0f);// 显示数据Log.d(\"IntentData\", \"用户名:\" + userName);Log.d(\"IntentData\", \"年龄:\" + userAge);Log.d(\"IntentData\", \"是否激活:\" + isActive);Log.d(\"IntentData\", \"得分:\" + userScore);
你可以通过
getIntent()
获取传递过来的数据,并用相应的get...Extra()
方法提取数据。
Intent传递Bundle
在 Android 中,Intent
可以通过 Bundle
来传递更复杂的数据集合。Bundle
是一个 键值对集合,可以存储多种类型的数据,比如基本数据类型、Serializable
或 Parcelable
对象等。
Bundle
通常用于:
- 传递多个数据项
- 传递复杂的数据集合
- 在
Activity
之间传递数据
📘 一、基本使用方式
Intent
通过 putExtras()
方法将 Bundle
对象添加到意图中,接收方通过 getExtras()
方法获取。
👇 1. 在发送方 (源 Activity) 中使用 Bundle
传递数据:
java复制代码// 创建一个 IntentIntent intent = new Intent(this, SecondActivity.class);// 创建一个 BundleBundle bundle = new Bundle();bundle.putString(\"name\", \"John\");bundle.putInt(\"age\", 25);bundle.putBoolean(\"isActive\", true);// 将 Bundle 添加到 Intentintent.putExtras(bundle);// 启动目标 ActivitystartActivity(intent);
👇 2. 在接收方 (目标 Activity) 中获取 Bundle
数据:
java复制代码// 获取 Intent 中的 BundleBundle bundle = getIntent().getExtras();// 从 Bundle 中获取数据if (bundle != null) { String name = bundle.getString(\"name\"); int age = bundle.getInt(\"age\", 0); // 默认值 0 boolean isActive = bundle.getBoolean(\"isActive\", false); // 默认值 false Log.d(\"ReceivedData\", \"Name: \" + name + \", Age: \" + age + \", Is Active: \" + isActive);}
📦 二、传递多种类型的数据
Bundle
支持多种类型的数据:
- 基本数据类型:
int
,boolean
,String
,float
,double
,long
Serializable
对象Parcelable
对象
👇 示例:传递更多类型的数据
java复制代码// 创建 Intent 和 BundleIntent intent = new Intent(this, SecondActivity.class);Bundle bundle = new Bundle();// 放入基本数据类型bundle.putString(\"userName\", \"Alice\");bundle.putInt(\"userAge\", 30);bundle.putFloat(\"userHeight\", 1.75f);// 放入 Parcelable 对象Person person = new Person(\"Bob\", 35);bundle.putParcelable(\"personData\", person);// 放入 Serializable 对象Car car = new Car(\"Toyota\", 2020);bundle.putSerializable(\"carData\", car);// 将 Bundle 传递给 Intentintent.putExtras(bundle);startActivity(intent);
Person 类(实现 Parcelable
)
java复制代码public class Person implements Parcelable { private String name; private int age; // 构造方法,Getter 和 Setter 略去 // Parcelable 实现 protected Person(Parcel in) { name = in.readString(); age = in.readInt(); } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeString(name); dest.writeInt(age); } @Override public int describeContents() { return 0; } public static final Creator CREATOR = new Creator() { @Override public Person createFromParcel(Parcel in) { return new Person(in); } @Override public Person[] newArray(int size) { return new Person[size]; } };}
👇 3. 在目标 Activity 中接收数据(从 Bundle 中提取数据):
java复制代码// 获取 Intent 中的 BundleBundle bundle = getIntent().getExtras();if (bundle != null) { String userName = bundle.getString(\"userName\"); int userAge = bundle.getInt(\"userAge\"); float userHeight = bundle.getFloat(\"userHeight\"); // 获取 Parcelable 对象 Person person = bundle.getParcelable(\"personData\"); // 获取 Serializable 对象 Car car = (Car) bundle.getSerializable(\"carData\"); Log.d(\"ReceivedData\", \"Name: \" + userName + \", Age: \" + userAge + \", Height: \" + userHeight); Log.d(\"ReceivedData\", \"Person: \" + person.getName() + \", Car: \" + car.getModel());}
🧩 三、总结:Bundle
在 Intent
中的使用
putExtras()
Bundle
数据添加到 Intent
中getExtras()
Bundle
数据putString()
, putInt()
, putFloat()
等Parcelable
putParcelable()
, getParcelable()
Serializable
putSerializable()
, getSerializable()
传递Serializable接口
Android 中使用 Intent
来 传递实现了 Serializable
接口的对象。这是在组件之间(如 Activity → Activity)传递自定义类对象的经典方式之一。
📘 一、什么是 Serializable
?
Serializable
是 Java 提供的一种 对象序列化机制,通过实现它,你的类对象就可以被写入或读取为字节流,从而传输到另一个组件(比如另一个 Activity)。
相比 Parcelable
,它实现更简单,但性能略低。适合数据量小、开发快的场景。
✅ 二、完整使用流程(传递对象)
1️⃣ 创建实体类并实现 Serializable
java复制代码import java.io.Serializable;public class User implements Serializable { private String name; private int age; // 构造函数 public User(String name, int age) { this.name = name; this.age = age; } // Getter public String getName() { return name; } public int getAge() { return age; } // Setter public void setName(String name) { this.name = name; } public void setAge(int age) { this.age = age; }}
⚠️ 注意:实体类必须实现
java.io.Serializable
接口,并且不需要实现任何方法!
2️⃣ 在源 Activity 中传递对象
java复制代码User user = new User(\"张三\", 28);Intent intent = new Intent(this, SecondActivity.class);intent.putExtra(\"user_data\", user); // 通过 Intent 传递 Serializable 对象startActivity(intent);
3️⃣ 在目标 Activity 中接收对象
java复制代码Intent intent = getIntent();User user = (User) intent.getSerializableExtra(\"user_data\");if (user != null) { Log.d(\"UserData\", \"姓名:\" + user.getName() + \",年龄:\" + user.getAge());}
📦 三、传递 Serializable 列表(List)
你也可以传递实现了 Serializable
的对象集合:
1️⃣ 源 Activity 中传递:
java复制代码ArrayList userList = new ArrayList();userList.add(new User(\"张三\", 28));userList.add(new User(\"李四\", 32));Intent intent = new Intent(this, SecondActivity.class);intent.putExtra(\"user_list\", userList);startActivity(intent);
2️⃣ 目标 Activity 中接收:
java复制代码ArrayList userList = (ArrayList) getIntent().getSerializableExtra(\"user_list\");for (User user : userList) { Log.d(\"ListData\", user.getName() + \" - \" + user.getAge());}
🔐 四、注意事项
Serializable
比 Parcelable
性能差一些View
, Context
等不能序列化传递Parcelable接囗
通过 Intent
传递实现了 Parcelable
接口 的对象。这是 Android 推荐的对象传递方式,性能优于 Serializable
,特别适用于频繁传递或大量数据。
📘 一、什么是 Parcelable
?
Parcelable
是 Android 特有的对象序列化接口- 它的设计目标是:更快、更轻量,适合在组件(如 Activity / Service)之间传递对象
- 它使用显式读写字段的方式进行序列化(不像 Serializable 使用反射)
✅ 二、完整使用流程(传递自定义对象)
1️⃣ 创建实体类并实现 Parcelable
import android.os.Parcel;import android.os.Parcelable;// User 类实现 Parcelable 接口,用于 Intent 传递对象public class User implements Parcelable { private String name; private int age; // 构造方法 public User(String name, int age) { this.name = name; this.age = age; } // Getter 方法 public String getName() { return name; } public int getAge() { return age; } // =========== Parcelable 必须实现的方法 =========== // 从 Parcel 中读取数据(构造函数) protected User(Parcel in) { name = in.readString(); // 顺序要和写入顺序一致 age = in.readInt(); } // 写入数据到 Parcel(用于传递) @Override public void writeToParcel(Parcel dest, int flags) { dest.writeString(name); // 写入字段 dest.writeInt(age); } // 通常返回 0 @Override public int describeContents() { return 0; } // Parcelable 的 CREATOR,必须定义 public static final Creator CREATOR = new Creator() { @Override public User createFromParcel(Parcel in) { return new User(in); // 从 Parcel 创建 User 对象 } @Override public User[] newArray(int size) { return new User[size]; } };}
2️⃣ 在源 Activity 中传递对象
// 创建 User 对象User user = new User(\"李四\", 30);// 创建 Intent,跳转到 SecondActivityIntent intent = new Intent(this, SecondActivity.class);// 通过 putExtra 传递 Parcelable 对象intent.putExtra(\"user_data\", user);// 启动 SecondActivitystartActivity(intent);
3️⃣ 在目标 Activity 中接收对象
// 接收 Intent 中传递的 Parcelable 对象User user = getIntent().getParcelableExtra(\"user_data\");if (user != null) { // 使用获取到的数据(如打印) Log.d(\"UserData\", \"姓名:\" + user.getName() + \",年龄:\" + user.getAge());}
📦 三、传递 Parcelable 对象列表
1️⃣ 传递方:
ArrayList userList = new ArrayList();userList.add(new User(\"张三\", 25));userList.add(new User(\"王五\", 33));// 放入 IntentIntent intent = new Intent(this, SecondActivity.class);intent.putParcelableArrayListExtra(\"user_list\", userList);startActivity(intent);
2️⃣ 接收方:
ArrayList userList = getIntent().getParcelableArrayListExtra(\"user_list\");if (userList != null) { for (User user : userList) { Log.d(\"UserList\", \"姓名:\" + user.getName() + \",年龄:\" + user.getAge()); }}
🧠 四、Parcelable 与 Serializable 对比
🛠️ 五、快捷写法(Android Studio 插件)
你可以使用 Android Studio 的插件 来快速生成 Parcelable
代码:
- 安装插件
Parcelable Code Generator
- 右键类名 →
Generate
→Parcelable
- 自动生成读写代码
✅ 总结
Parcelable
接口、重写 writeToParcel
和构造方法putExtra()
传递对象getParcelableExtra()
接收对象putParcelableArrayListExtra()
Android多媒体开发
MediaRecorder录制视频
MediaPlayer播放视频
VideoView播放视频
VideoView封装了MediaPlayer,使用更方便。但UI等界面不可调整,仅适用于简单的使用。
SoundPool播放音效
前端做好,是一个网页
DOCKER封装后端的代码,可以独立运行
rabiitMQ前后端通信
QT写个简单的前端,直接打包过去,PYC加密。
SP真正实战编写代码
可以通过这个生成APK
GIT使用
版本历史
Git和其他VCS的主要区别
Git基本操作
Git入门图文教程(1.5W字40图)🔥🔥–深入浅出、图文并茂 - 安木夕 - 博客园
🧠 详细解析:git pull
做了什么?
执行:
bashgit pull
其实 Git 做了以下两步操作:
git fetch
:从远程仓库获取(fetch)更新的分支信息,更新本地的远程分支(如origin/main
);git merge
:把这些远程更新合并(merge)到你当前的本地分支(如main
)。
📌 使用前提
-
当前分支已经设置了远程关联(upstream):
bash复制代码git branch -vv
-
否则你需要明确指定:
bash复制代码git pull origin main
origin main这两个参数是远程的项目名和分支名
SP实战
manifest中的启动项activity、
<?xml version=\"1.0\" encoding=\"utf-8\"?><manifest xmlns:android=\"http://schemas.android.com/apk/res/android\" xmlns:tools=\"http://schemas.android.com/tools\"><application android:allowBackup=\"true\" android:dataExtractionRules=\"@xml/data_extraction_rules\" android:fullBackupContent=\"@xml/backup_rules\" android:icon=\"@mipmap/ic_launcher\" android:label=\"@string/app_name\" android:roundIcon=\"@mipmap/ic_launcher_round\" android:supportsRtl=\"true\" android:theme=\"@style/Theme.SPcoding_0414\" tools:targetApi=\"31\"> <!-- 把 MainActivity 改为普通页面(非启动页) --> <activity android:name=\".MainActivity\" android:exported=\"false\" android:label=\"@string/app_name\" android:theme=\"@style/Theme.SPcoding_0414\" /> <!-- 设置 MainActivity2 为启动入口 --> <activity android:name=\".MainActivity2\" android:exported=\"true\" android:label=\"@string/app_name\"> <intent-filter> <action android:name=\"android.intent.action.MAIN\" /> <category android:name=\"android.intent.category.LAUNCHER\" /> </intent-filter> </activity></application>
启动页配置说明
android:exported=\"true\"
intent-filter
的组件都必须加)intent-filter
内的 MAIN
和 LAUNCHER
主题报错说明
首先说解决办法:在manifest文件中,下需要加入android:theme=“@style/Theme.AppCompat.Light.NoActionBar”>这一行代码,替换原先主题
在MainActivity中,第一句定义public class MainActivity2 extends AppCompatActivity {
这里继承了某一个风格,但是创建activity时res/values/themes.xml文件中也parent了一个
<?xml version=\"1.0\" encoding=\"utf-8\"?><resources> <style name=\"Theme.SPcoding_0414\" parent=\"android:Theme.Material.Light.NoActionBar\" /></resources>
发生了冲突
在manifest文件中,需要加入android:theme=“@style/Theme.AppCompat.Light.NoActionBar”>这一行代码
<activity android:name=\".MainActivity2\" android:exported=\"true\" android:label=\"@string/app_name\" android:theme=\"@style/Theme.AppCompat.Light.NoActionBar\"> <intent-filter> <action android:name=\"android.intent.action.MAIN\" /> <category android:name=\"android.intent.category.LAUNCHER\" /> </intent-filter>
你问的是:
❓ 为什么我们在
AndroidManifest.xml
中给MainActivity2
设置的是
android:theme=\"@style/Theme.AppCompat.Light.NoActionBar\"
而不是你自己定义的android:theme=\"@style/Theme.SPcoding_0414\"
?
✅ 答案分两种情况来看:
🚩 情况 1:你自己的主题 Theme.SPcoding_0414
是正确的(继承了 AppCompat)
✅ 那么你完全可以写成:
复制代码android:theme=\"@style/Theme.SPcoding_0414\"
只要这个主题是在 res/values/themes.xml
里定义,而且继承的是 Theme.AppCompat
,比如:
xml复制代码
这才是最终推荐做法 ✅
为什么我在创建 Empty Activity 时,Android Studio 自动在
AndroidManifest.xml
中设置:xml复制代码android:theme=\"@style/Theme.SPcoding_0414\"
而不是用系统主题
Theme.AppCompat.Light
?
✅ 原因:Android Studio 模板默认会帮你生成一个自定义主题
当你新建项目时,Android Studio 会自动:
- ✅ 创建一个和项目名相匹配的主题,比如
Theme.SPcoding_0414
- ✅ 这个主题会自动写入
res/values/themes.xml
中 - ✅ 并且设置为 Application 和 Activity 的默认主题
- ✅ 这个主题默认是基于
MaterialComponents
的!
🔍 示例:自动生成的主题长这样
xml复制代码 @color/purple_500 @color/purple_700 @color/white
你可以看到,默认模板用的是:
xml复制代码parent=\"Theme.MaterialComponents.DayNight.DarkActionBar\"
也就是说:它并不是 Theme.AppCompat
!
❗所以为什么你后面用 AppCompatActivity
会崩溃?
因为你用了 AppCompatActivity
,但主题却不是 Theme.AppCompat
风格,而是 MaterialComponents
风格 → 不兼容 → 抛出异常。
🛠 解决方案(两种选择):
✅ 方案 1:保留 AppCompatActivity → 修改主题继承链
xml复制代码
👉 然后在 Manifest
中继续用:
android:theme=\"@style/Theme.SPcoding_0414\"
✅ 方案 2:保留 MaterialComponents 主题 → 不要用 AppCompatActivity
你可以把 Activity 改成继承:
public class MainActivity2 extends ComponentActivity
或者使用 MaterialTheme
支持的方式去设计布局。
Button R.id.bt_标红问题
一、问题描述
二、解决办法
在修改了manifest之后,需要重新build文件。从上方菜单栏选择build,clean project,然后assemble project, run app。
在gradle.properties中加上下面四行代码:
android.enableJetifier=true
android.defaults.buildfeatures.buildconfig=true
android.nonTransitiveRClass=true
android.nonFinalResIds=false
关键是android.nonFinalResIds=false
✅ 作用:
让 R.id.xx
等资源保持为 final
常量(可以用于 switch case
)
❗ 为什么影响 Button 使用?
如果设为 true
,所有 R.id.xxx
就不再是 final
,你在 Java 中就无法:
java复制代码switch (view.getId()) { case R.id.bt_login: // ❌ 报错“需要常量表达式”}
这正是你之前遇到的核心错误!✅
✅ 结论:
必须设为 false
,否则会破坏 Java 中很多经典语法场景(如 switch-case
控件点击事件判断)
android studio build菜单常用构建操作对比说明:
release.apk
或 .aab
build
中所有编译文件app打包一图看懂:两者区别对比
.apk
/ .aab
(debug).apk
/ .aab
(release)✅ 详细解释
🔹 Generate App Bundles or APKs
这是个通用入口,有时可用于构建 debug 或 release 版本。
点击后可能进一步提供选择:
- Build APK(s)
- Build Bundle(s)
⚠️ 这个入口并不强制你使用签名,也不一定生成最终正式包。
适合:
- 你只是想临时打一个
.apk
来测试或装到手机上 - 用于本地调试,不需要正式发布
🔸 Generate Signed App Bundle or APK…
这是用于生成“正式发布包”的流程,整个流程会引导你:
- 选择 APK 还是 AAB(App Bundle)
- 选择 Keystore 签名文件
- 输入密码、别名等
- 选择 debug 或 release 构建类型
- 生成签名后的
.apk
或.aab
文件
适合:
- 上架应用市场(如 Google Play)
- 对外分发正式版本
- 测试带签名的 APK 安装兼容性
🎯 总结推荐:
Build APK(s)
或 Generate App Bundles or APKs
Generate Signed App Bundle or APK...
(必须签名).aab
文件上传✅ 补充知识点:APK vs AAB
.apk
.aab
APK打包教程
Android Studio 打包APK(详细版)_as打包apk-CSDN博客
重命名由于版本问题仍存在问题
APK发布到实际手机运行
如何把android studio中的项目发布到手机上(超详细版)_android studio工程部署至手机-CSDN博客
源码使用说明
b站 子林Android 源码由于版本冲突,无法直接使用,但新建一个project并复制过来后是可以直接使用的
匿名内部类(Anonymous Inner Class)
Java 中的一个非常强大的语法特性:匿名内部类(Anonymous Inner Class)。
回答概括:
你看到的这个语句:
private Handler mHandler = new Handler(Looper.myLooper()) { @Override public void handleMessage(@NonNull Message msg) { ... }};
是在 声明变量的同时创建了一个匿名内部类对象,并重写了方法。
✅ 拆解解释
🔹 正常写法
如果你平时熟悉这样写:
class MyHandler extends Handler { @Override public void handleMessage(@NonNull Message msg) { ... }}private Handler mHandler = new MyHandler();
那你现在这句其实就是它的简写版(匿名写法)。
🔹 当前写法 = 匿名类写法
new Handler(Looper.myLooper()) { @Override public void handleMessage(Message msg) { ... }}
这个表示:
“创建一个继承
Handler
的子类,并立即重写handleMessage()
方法,然后直接用这个子类实例来给变量mHandler
赋值”。
也就是说,Handler
是个类,你在 new Handler(...)
时,立刻就定义了一个它的子类,并立即重写了它的方法。
✳️ 为什么要这么写?
- 代码更简洁
不用多写一个类名,适合只用一次的情况。 - 逻辑更集中
mHandler
是一个带有特定行为的对象,你可以一眼看到它具体要做什么。 - 避免类污染
不用为一次性用途写额外类文件。
✅ 小结:
new 类名() { ... }
Handler
、OnClickListener
、Runnable
等虽然你在 Android 项目中看不到传统 Java 程序那种:
java复制代码public static void main(String[] args) { ... }
但 Android 应用有自己的启动机制,它的入口不是 main()
,而是由 系统框架控制的。下面是详细解释:
Android 应用的启动流程(简要版)
入口不是 main()
,而是 Activity
的生命周期方法
在 Android 中,应用程序的执行入口由 操作系统的 Zygote
进程 负责启动。Zygote 会在应用第一次启动时调用你的 Activity
,并最终执行:
java复制代码onCreate(Bundle savedInstanceState)
也就是说:
你写的
MainActivity2
是 Android 系统调用的,不是自己主动运行的。
谁控制了启动?→ AndroidManifest.xml
关键启动配置在你的 AndroidManifest.xml
中:
xml复制代码
意思是:
MAIN
:这个 Activity 是主入口LAUNCHER
:会显示在桌面应用图标中- 所以你一点击图标,系统就会通过这个配置来启动
MainActivity2
Android 系统底层的 main()
存在吗?
是的,整个 App 的真正入口仍然是 main()
方法,但是由 Android 系统自己维护:
java复制代码public static void main(String[] args) { // 由 ActivityThread 类控制,之后调用 onCreate() 等}
你写的 Java 代码并不会直接接触这个 main()
,而是通过 Android 框架在幕后帮你处理了。
🔁 总结
main()
?MainActivity2
,然后执行 onCreate()
开辟异步线程,在new Thread()、选择new Runnable()后,会直接出现@Override public void run() {},里面的内容就是异步线程的实现。最后.start()代表启动了线程
public void start(View view) { // 做一个耗时任务// String strFromNet = getStringFromNet(); new Thread(new Runnable() { @Override public void run() { strFromNet = getStringFromNet(); Message message = new Message(); message.what = 0; message.obj = strFromNet; mHandler.sendMessage(message);// tvContent.setText(strFromNet);// try {// Thread.sleep(1000);// } catch (InterruptedException e) {// e.printStackTrace();// } } }).start(); Toast.makeText(this, \"任务完成!\", Toast.LENGTH_SHORT).show(); }
应用内通信方式(Handler)和进程间通信方式(AIDL)
Handler内容
E:\\Android\\origin_coding\\课程全套pdf-手把手教你学AndroidApp开发入门篇\\手把手教你学Androidapp开发入门篇-课程PDF 30-Android多线程通信-handler机制
AIDL内容
所有进程通信的原理都是这张图所示,请求bindservice,服务器同意链接返回Binder,客户端调用远程的方法
这张图展示了 AIDL(Android Interface Definition Language)机制的通信流程,它体现的是 基于 Binder 的跨进程通信(IPC)。下面我详细解析这个过程:
🧠 一、AIDL 的核心机制:Binder 跨进程通信
关键词解释:
- Client:调用端(例如 App)
- Server:服务端(例如运行在另一个进程的 Service)
- Binder:Android 的核心 IPC 框架,用于跨进程通信
- AIDL:定义了客户端和服务端都能识别的接口(IRemoteInterface)
🧩 二、流程解析(配合图)
① Client
调用 bindService
Client
通过调用bindService()
,请求绑定远程服务。- 这个请求会发送给 AMS(ActivityManagerService),由系统完成进程间连接。
② Server
返回 Binder
- 服务端实现了 AIDL 接口,并在
onBind()
中返回一个Stub
类型的 Binder。 - 这个
Binder
被系统传回给 Client。
java复制代码@Overridepublic IBinder onBind(Intent intent) { return myStub; // myStub 是 Stub 的实例}
③ Client
拿到 Binder,进行接口转换
java复制代码IMyAidlInterface service = IMyAidlInterface.Stub.asInterface(serviceBinder);
Client
将 Binder 转换成 AIDL 接口对象,从而调用远程方法。
④ 调用远程方法(跨进程通信)
Client
实际是调用本地Proxy
对象的方法。Proxy
会通过 Binder 调用 Server 进程中的Stub
。
🔄 过程总结:
bindService()
onBind()
,返回 Binder 对象asInterface()
得到接口🔐 三、为什么 AIDL 需要?
- 普通对象不能跨进程传递,AIDL 利用了 Binder 机制,让我们像调用本地接口一样调用远程服务。
- 适合多线程/跨进程场景,例如音乐播放器服务、后台下载服务等。
✅ 示例
- AIDL 接口定义(
IMyService.aidl
):
interface IMyService { String getData();}
- Server 实现接口:
public class MyService extends Service { private final IMyService.Stub mBinder = new IMyService.Stub() { public String getData() { return \"来自远程服务的数据\"; } }; @Override public IBinder onBind(Intent intent) { return mBinder; }}
- Client 获取服务:
IMyService myService = IMyService.Stub.asInterface(service);String result = myService.getData();
demo实例
下面是一个 完整的 AIDL 跨进程通信 Demo 实例,包括客户端和服务端代码、AIDL 接口、AndroidManifest 配置,适合你直接运行实验。
🌟 项目结构概览
text复制代码app/├── aidl/│ └── com/example/myaidldemo/│ └── IMyAidlInterface.aidl├── java/│ └── com/example/myaidldemo/│ ├── MyService.java│ └── MainActivity.java
1️⃣ AIDL 接口文件(IMyAidlInterface.aidl
)
📁 路径:app/src/main/aidl/com/example/myaidldemo/IMyAidlInterface.aidl
package com.example.myaidldemo;// AIDL接口,定义方法interface IMyAidlInterface { String getData();}
💡 注意:
aidl
文件必须和 Java 包名一致。- 创建 AIDL 文件时,确保
aidl
文件夹已标记为 AIDL source directory(右键 -> Mark Directory as)。
2️⃣ 服务端:MyService.java
📁 路径:app/src/main/java/com/example/myaidldemo/MyService.java
package com.example.myaidldemo;import android.app.Service;import android.content.Intent;import android.os.IBinder;import android.os.RemoteException;public class MyService extends Service {//////////////定义mBinder并new出来后,AS会自动跳出aidl中的函数,使其在service中进行实现////////////////////////// private final IMyAidlInterface.Stub mBinder = new IMyAidlInterface.Stub() { @Override public String getData() throws RemoteException { return \"这是从服务端返回的数据\"; } };////创建service自带函数/////////// TODO: Return the communication channel to the service.//// @Override public IBinder onBind(Intent intent) { return mBinder; }}
3️⃣ 客户端调用:MainActivity.java
📁 路径:app/src/main/java/com/example/myaidldemo/MainActivity.java
java复制代码package com.example.myaidldemo;import android.content.ComponentName;import android.content.Intent;import android.content.ServiceConnection;import android.os.Bundle;import android.os.IBinder;import android.os.RemoteException;import android.widget.TextView;import android.widget.Button;import android.view.View;import android.content.Context;import android.widget.Toast;import androidx.appcompat.app.AppCompatActivity;public class MainActivity extends AppCompatActivity {/////////////////类内私有变量,aidl接口和与service通信的textview////////////////////////////////// private IMyAidlInterface myAidlInterface; private TextView textView;/////////////ServiceConnection也是AS自带的类,用于service通信,一旦定义就会自动出现两个函数进行实现//////////////// private ServiceConnection connection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { myAidlInterface = IMyAidlInterface.Stub.asInterface(service); Toast.makeText(MainActivity.this, \"服务已连接\", Toast.LENGTH_SHORT).show(); } @Override public void onServiceDisconnected(ComponentName name) { myAidlInterface = null; } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); textView = findViewById(R.id.tv_content); // 绑定服务 Intent intent = new Intent(this, MyService.class); bindService(intent, connection, Context.BIND_AUTO_CREATE); Button button = findViewById(R.id.btn_get_data); button.setOnClickListener(v -> { if (myAidlInterface != null) { try { String result = myAidlInterface.getData(); textView.setText(result); } catch (RemoteException e) { e.printStackTrace(); } } }); } @Override protected void onDestroy() { super.onDestroy(); unbindService(connection); }}
4️⃣ activity_main.xml
示例界面
📁 路径:res/layout/activity_main.xml
xml复制代码
5️⃣ AndroidManifest.xml 注册服务
xml复制代码
✅ 总结:如何运行?
- 创建
IMyAidlInterface.aidl
文件并同步 Gradle。 - 创建服务端
MyService.java
并实现接口。 - 创建
MainActivity
调用 AIDL 接口。 - 运行 App,点击按钮触发 AIDL 调用。
代码详解
🧩 一、AIDL 接口文件
📄 IMyAidlInterface.aidl
aidl复制代码package com.example.myaidldemo;interface IMyAidlInterface { String getData();}
✍️ 解释:
package
:包名必须和 Java 文件一致。interface
:定义一个跨进程使用的接口。String getData()
:声明一个方法供客户端远程调用。
💡 注意:只能使用基本类型、String、CharSequence、Parcelable、自定义AIDL类型。
🧩 二、服务端代码(MyService.java
)
public class MyService extends Service {
📌 表示这是一个后台服务。
java复制代码private final IMyAidlInterface.Stub mBinder = new IMyAidlInterface.Stub() { @Override public String getData() throws RemoteException { return \"这是从服务端返回的数据\"; }};
🔍 解释:
Stub
是系统自动生成的 Binder 实现类。mBinder
实际是服务端的 远程代理对象,客户端会通过这个代理访问服务端方法。getData()
是对 AIDL 接口中方法的具体实现。
java复制代码@Overridepublic IBinder onBind(Intent intent) { return mBinder;}
📌 onBind()
用来将 mBinder
返回给客户端,以便它获得远程接口。
你提到的 onBind()
方法没有调用 —— 这个问题非常常见,而且与 启动 Service 的方式 有直接关系。
调用了 bindService(...)
,就会执行 onBind()
方法
✅ 关键点:只有绑定服务时才会调用 onBind()
也就是说:
✅ 调用了 bindService(...)
,才会执行 onBind()
方法。
java复制代码Intent intent = new Intent(this, MyService.class);bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
❌ 如果只调用了 startService(...)
呢?
java复制代码Intent intent = new Intent(this, MyService.class);startService(intent);
这种方式只会触发 onCreate()
和 onStartCommand()
,不会调用 onBind()
。
🧩 三、客户端代码(MainActivity.java
)
- 定义 ServiceConnection
java复制代码private ServiceConnection connection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { myAidlInterface = IMyAidlInterface.Stub.asInterface(service); } @Override public void onServiceDisconnected(ComponentName name) { myAidlInterface = null; }};
✅ 用于管理 服务连接 的回调。
onServiceConnected()
中拿到服务端传过来的 Binder,然后通过asInterface()
转换成IMyAidlInterface
。onServiceDisconnected()
是意外断开时的回调。
myAidlInterface = IMyAidlInterface.Stub.asInterface(service);
是 AIDL(Android Interface Definition Language)通信机制中最关键的一步之一,它的作用是将 远程 Binder 对象 转换为你在本地可以直接调用的方法接口对象。
IMyAidlInterface.Stub.asInterface(service)
做了什么?
IMyAidlInterface
是你定义的.aidl
接口文件。- Android 编译器根据这个
.aidl
文件自动生成了一个 Java 类,结构如下:
java复制代码public interface IMyAidlInterface extends android.os.IInterface { public static abstract class Stub extends android.os.Binder implements IMyAidlInterface { public static IMyAidlInterface asInterface(android.os.IBinder obj) { // 核心逻辑 } }}
asInterface(...)
方法的逻辑:
public static IMyAidlInterface asInterface(IBinder obj) { if (obj == null) { return null; } IInterface iin = obj.queryLocalInterface(DESCRIPTOR); if ((iin != null) && (iin instanceof IMyAidlInterface)) { return (IMyAidlInterface) iin; } return new IMyAidlInterface.Stub.Proxy(obj); // 使用代理类封装远程调用}
👉 总结:把远程 service 这个 Binder 对象转换成你可以直接调用的接口(其实底层会通过 Binder 做 IPC 通信)。
✅ 最终效果
调用后你就可以像调用普通对象一样访问远程服务方法了:
String result = myAidlInterface.getData();
系统会自动通过 Binder 通信把这个调用发送到 Service 端。
补充知识:为什么要用这个转换?
因为 Android 的进程是隔离的,你不能直接使用另一个进程的对象。Binder 提供了一种方式,让你像本地方法一样调用另一个进程的方法。asInterface()
就是这中间的“桥梁”。
- 绑定服务
Intent intent = new Intent(this, MyService.class);bindService(intent, connection, Context.BIND_AUTO_CREATE);
📌 使用 Intent 启动服务并绑定,自动创建服务实例(BIND_AUTO_CREATE
)。
- 按钮点击调用服务
if (myAidlInterface != null) { String result = myAidlInterface.getData(); textView.setText(result);}
🧠 这段逻辑是通过 AIDL
远程调用服务端的 getData()
方法,并把结果显示在界面上。
- Activity 生命周期清理
@Overrideprotected void onDestroy() { unbindService(connection);}
🔐 防止内存泄漏,解绑服务。
🧩 四、布局文件(activity_main.xml)
TextView
显示服务端返回数据。Button
点击触发远程调用。
🧩 五、AndroidManifest.xml 注册服务
xml复制代码
exported=\"true\"
表示该服务可以被外部(或其他 App)绑定。- 若
exported=false
,只能当前 App 绑定。
🔄 执行流程总结(图示)
text复制代码MainActivity(客户端) —bindService→ MyService(服务端) ↓ ↑ onServiceConnected() 返回 Binder 接口 ↓myAidlInterface.getData() —→ Stub 调用 getData() ←— 返回数据
❗ 常见问题排查
Stub
报错ServiceConnection
不回调bindService()
或服务未注册RemoteException
抛出AIDL
不支持对象类型Parcelable
或 AIDL interface
onServiceConnected(ComponentName name, IBinder service)` 是怎么被自动调用的
你是想问 onServiceConnected(ComponentName name, IBinder service)
是怎么被自动调用的?你明明没有手动调用它,它怎么就执行了?
——这其实是 Android 系统在服务绑定完成后自动调用的,下面是完整原理 👇
🧠 一句话解释
onServiceConnected()
是 Android 系统自动回调的,用于告诉你:你请求绑定的服务已经连接成功了!
🧩 触发流程详解(基于 AIDL 或普通服务都一样)
① 你调用 bindService(intent, conn, flags)
:
bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
serviceConnection
就是你写的ServiceConnection
实例- 这个接口里有两个重要方法:
onServiceConnected(...)
onServiceDisconnected(...)
② Android 系统会把 ServiceConnection
存起来,并尝试连接你指定的服务
如果连接成功(包括 AIDL 进程间服务或本地 Service):
➡️ 系统就会自动调用:
java复制代码serviceConnection.onServiceConnected(ComponentName name, IBinder service);
name
:连接成功的服务组件service
:就是你在Service
里返回的onBind()
方法中的IBinder
③ 你拿到的 service
参数,就是你跟服务通信的“桥梁”🔗
比如:
java复制代码myAidlInterface = IMyAidlInterface.Stub.asInterface(service);
你就可以通过 myAidlInterface
去调用 AIDL 方法。
🖼️ 流程图示意
text复制代码你调用 bindService() ——————————————→ 系统启动/连接目标服务 ↓ 服务创建成功 ↓ 系统自动调用你的: serviceConnection.onServiceConnected(...)
复现AIDL代码过程中的问题
AIDL文件如何创建,应该放在哪个目录下
放在app/src/main下,aidl文件和java同级目录
如何创建 AIDL 文件?
- 在 Project 视图中切换到 “Project” 模式(不是 Android 模式)
- 找到:
app/src/main
目录 - 右键
main
目录,选择:
New > Directory > 命名为 aidl
- 在
aidl
目录下按照包名结构创建文件夹:
com/example/aidlwithservice_0416/
- 右键
aidlwithservice_0416
,选择:
New > AIDL > AIDL File
命名为:IMyAidlInterface.aidl
此时若出现
在 build.gradle
启用 AIDL
请打开你模块下的 build.gradle
(一般是 app/build.gradle.kts
或 build.gradle
)文件,然后根据你使用的是 Kotlin DSL(kts
)还是 Groovy 格式,添加如下配置:
选择这个文件:build.gradle.kts (Module: app)
。
✅ 如果你使用的是 Kotlin DSL(build.gradle.kts
):将false改为true
android { // 其他配置... buildFeatures { aidl = true }}
如果你使用的是 Groovy 格式(build.gradle
):
android { // 其他配置... buildFeatures { aidl true }}
修改 build.gradle
后,点击 Sync Now
若没有④ 右键 aidl 文件夹 → Mark Directory as → 会出现 AIDL source directory
这时候已经成功了,可以通过以下方法进行检查:
AIDL文件编译后会自动生成一个 Java 文件:
生成文件位置:
build/generated/aidl_source_output_dir/debug/out/com/example/aidldemo/IMyAidlInterface.java
你也可以点击 Android Studio 顶部菜单:
Build > Make Project(或 Rebuild Project)
- 包名必须与 Java 文件的包名一致
- 编译时会自动生成
IMyAidlInterface.java
文件(在build/generated
文件夹里) - 若没有自动生成,请点击 Build > Rebuild Project
APK的构成和编译原理
构成
APK的基本结构 - 知乎
[【APK文件结构深度解析】:全面掌握从基础到高级的APK架构 - CSDN文库](https://wenku.csdn.net/column/7c4xpwm69q#1. APK文件结构概述)
✅ 一、APK 的构成(本质是一个 ZIP 文件)
APK 是经过压缩的 .zip
文件,里面包含了运行 Android 应用所需的所有资源和代码,主要目录如下:
AndroidManifest.xml
classes.dex
resources.arsc
res/
assets/
lib/
META-INF/
kotlin/
二、APK 编译原理(从源码到 APK 的全过程)
编译流程图:
Java/Kotlin + XML资源 ↓ 编译为 .class + R.java ↓ D8 或 R8 转为 .dex ↓资源打包(AAPT2) ↓ 签名 + 对齐(zipalign) ↓ APK
具体编译步骤详解:
- 资源编译(AAPT2)
.xml
(布局、颜色、样式等)文件经过aapt2 compile
编译成中间二进制格式。AndroidManifest.xml
也会被编译为二进制。- 最终通过
aapt2 link
把所有资源链接成一个resources.apk
。
- Java/Kotlin 源码编译
- Java →
.class
文件(通过javac
) - Kotlin →
.class
文件(通过kotlinc
)
- 字节码转 DEX
.class
→.dex
,使用 D8(旧的是 DX),如果启用了混淆、压缩、删除无用代码,还会走 R8。
- 合成 APK
- 所有资源、
.dex
文件、AndroidManifest.xml
、lib 文件被打包成.apk
。
- APK 签名
- 使用
jarsigner
或 Android Gradle Plugin 自动完成签名(开发者必须签名后才能安装)。
- APK 对齐(zipalign)
- Google 要求 APK 进行对齐优化,以提升运行时性能。
三、打包相关的关键工具
aapt2
javac
kotlinc
D8/R8
zipalign
apksigner
四、构建产物位置(以 Gradle 为例)
默认打包后的 APK 位于:
app/build/outputs/apk/debug/app-debug.apk
五、补充:构建过程中的 Gradle 脚本作用
build.gradle(Project)
build.gradle(Module)
gradle-wrapper.properties
🧠 总结
APK 是一个集资源、代码、配置、签名于一体的压缩包,背后经历了资源编译 → 源码编译 → DEX 转换 → 资源链接 → 签名 → 对齐等多个步骤,最终打包生成。整个过程主要由 Gradle 和 Android 构建工具链自动完成。
Java 源码编译为 DEX 的完整过程
💡 简要结论:
Java/Kotlin 源码 先编译为
.class
(Java 字节码),然后再使用 D8(或旧版本中的 DX 工具)将这些.class
转换为 .dex(Dalvik Executable)文件,即 Dalvik 字节码。
🧩 详细流程:
🔹 1. Java 源码(.java
)
写的源代码文件,例如:
public class HelloWorld { public static void main(String[] args) { System.out.println(\"Hello, world!\"); }}
🔹 2. 编译为 .class
文件(Java 字节码)
使用 javac
工具(由 Gradle 自动调用),将 .java
编译为 JVM 可执行的 .class
文件。
javac HelloWorld.java
生成的就是 .class
文件,里面是标准的 Java 字节码(JVM 指令集)。
🔹 3. .class
转换为 .dex
文件(Dalvik 字节码)
然后使用 D8(Android 8.0 以后推荐)或者 DX(旧版本)工具:
d8 HelloWorld.class
这一步把 Java 字节码转换成 Dalvik 字节码,生成 .dex
文件。这个文件格式是为 Android 虚拟机(Dalvik / ART)设计的。
每个 APK 都必须至少包含一个
.dex
文件(通常叫classes.dex
)
⚙️ ⚠️ Kotlin 同理!
Kotlin 会使用 kotlinc
先转为 .class
文件,然后同样走 D8 → .dex
路径。所以:
.java / .kt → .class → .dex(Dalvik 字节码)
✅ DEX 是干什么的?
.dex
文件是 Android 虚拟机(Dalvik/ART)可以直接解释执行的格式,它打包了多个 .class
文件,并进行了优化和压缩,以适应移动端设备内存紧张的情况。
✅ 总结流程图:
.java / .kt(源代码) ↓ javac / kotlinc .class(Java 字节码) ↓ D8 / R8 .dex(Dalvik 字节码) ↓ 打包进 APK,手机执行
🧱 Java 与 C 的编译流程对比图
.java
文件.c
文件javac
编译为 .class
字节码文件gcc
、clang
编译为 .s
汇编代码.class
就是中间格式.s
文件 → 汇编器 → .o
目标文件.class
文件.o
文件链接为 .exe
或 .out
可执行文件.class
→ .dex
(Android 上)→ JVM/ART 执行🔹 Java 源码
public class Hello { public static void main(String[] args) { System.out.println(\"Hello World\"); }}
🔹 编译后(用 javap -c Hello
反编译 .class
文件)看到的 Java 字节码:
public static void main(java.lang.String[]); Code: 0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 3: ldc #3 // String Hello World 5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 8: return
这是 Java 字节码,不是 CPU 汇编,但类似“虚拟机的汇编”。
资源(res 文件夹下)最终被编译为二进制
res 文件夹下的资源最终是会被编译为二进制的,不过这个过程是分步骤完成的,我来给你详细讲清楚整个“资源编译为二进制”的过程,以及它和 R.java
是怎么配合的。
🧠 关键理解:资源本身是二进制,但访问资源需要用 Java 代码中的 ID(R.java)来指向它们。
✅ 一、res 资源编译过程详解
📁 你写的资源(原始形式):
res/├── layout/activity_main.xml├── values/strings.xml├── drawable/logo.png
这些都是你人类可读的文本(XML)或图片。
🛠 编译阶段 1:AAPT2 编译资源 → 二进制格式
使用 Android 的资源打包工具 AAPT2(Android Asset Packaging Tool)进行两步处理:
1️⃣ compile
阶段(资源 → 中间二进制)
.xml
文件(如布局)被转换成 二进制 XML.png
、.jpg
可能会被压缩或转换为优化格式
2️⃣ link
阶段(生成 resources.arsc)
- 所有资源 ID 被分配(对应
R.java
中的 ID) - 生成
.flat
文件(中间文件) - 最终生成
resources.arsc
:包含所有已编译的资源索引、资源值、ID 映射等信息
✅ 二、R.java 的作用:Java → 资源的桥梁
R.layout.activity_main // 实际是一个整数 ID,如 0x7f0a0012R.string.app_name // 也是整数 ID,指向 values/strings.xml 中的字符串
这些 ID 是系统在
resources.arsc
中查表用的!
✅ 三、最终打包进 APK 的结构
当你运行 Build > Build APK(s)
,最终会在 APK 中看到以下结构:
text复制代码APK 文件内容:├── AndroidManifest.xml(已转二进制)├── classes.dex├── res/ ← 包含所有编译过的图片、布局资源├── resources.arsc ← 所有资源 ID 和内容索引表(二进制)├── META-INF/
🔍 总结对比表
res/
文件resources.arsc
R.java
.class
🧠 类比一下:
就像你去图书馆借书:
R.java
→ 借书证上的编号resources.arsc
→ 图书馆的索引系统res/
→ 真实的书籍(图片、XML等)
你通过 R.layout.xxx
找编号,系统再去 resources.arsc
找到文件资源的位置,从 APK 中提取出来。
R.java文件
R.java
是系统自动生成的一个非常重要的中间文件,它是你的代码与资源之间的桥梁。
✅ 一句话解释:
R.java
是一个自动生成的 Java 类,包含了你项目中所有资源(res 文件夹下)的 ID 常量引用,让你可以在代码中方便地访问布局、字符串、图片、颜色等资源。
📂 举例说明:你的项目结构
res/├── layout/│ └── activity_main.xml├── values/│ └── strings.xml├── drawable/│ └── icon.png
你在代码中看到的:
R.layout.activity_mainR.string.app_nameR.drawable.icon
这些 R.xxx.yyy 全部来自于自动生成的 R.java
文件!
🧾 自动生成的 R.java 是什么样?
生成路径(编译后)大概是:
app/build/generated/source/r/debug/com/example/myapp/R.java
内容类似这样:
public final class R { public static final class layout { public static final int activity_main = 0x7f0a0000; } public static final class string { public static final int app_name = 0x7f0b0000; } public static final class drawable { public static final int icon = 0x7f0c0000; }}
🧠 原理:
- 每个资源文件(如
activity_main.xml
、icon.png
、app_name
)都会被分配一个整数 ID。 - 你在代码中使用
R.layout.activity_main
,其实是使用对应的整型资源 ID。 - 编译器会将这些 ID 和资源文件绑定,在运行时通过
Resources
类去加载。
⚙️ R.java 的生成时机
在你执行以下任一操作时生成:
- 点击 “Build > Make Project”
- 修改任何
res/
下的资源文件 - Sync Gradle
如果出错(如报 R cannot be resolved
),可能是:
buildFeatures { viewBinding true }
(如用 binding
)build.gradle
中启用File > Sync Project with Gradle Files
✅ 和 AIDL、.class 有什么关系?
当 Java/Kotlin 源码编译成 .class
文件时,R.java
也会一起被编译成 .class
文件,供应用逻辑调用。
所以:
res/layout/activity_main.xml ↓(aapt2 编译资源)生成 R.java ↓(javac 编译)生成 R.class
✅ 小结
R.layout.activity_main
、R.drawable.icon
AOSP安卓系统架构概览
官方文档
https://source.android.com/docs/core/architecture?hl=zh-cn
GPT 自下而上解释
你这张图是 AOSP(Android Open Source Project)软件架构图,非常经典!它展示了 Android 系统从底层到上层的整体分层结构。下面我给你详细逐层解析一下每一部分的作用和相互关系:
🧱 AOSP 架构分层详解(自底向上)
✅ 1. Linux Kernel(Linux 内核)
- Android 最底层的基础,由 Linux 内核构成。
- 提供:
- 进程管理
- 内存管理
- 文件系统
- 网络协议栈
- 硬件驱动(驱动 WiFi、蓝牙、摄像头、USB、音频等)
- 厂商可以在内核层添加定制驱动程序。
🔧 类似于 Windows 的 NT 内核,是所有上层功能的基础。
✅ 2. System Services and Daemons(系统服务与守护进程)
- 包含像
init
、logd
、servicemanager
、vold
等系统级守护进程。 - 管理系统启动流程、设备管理、安全策略(如 SELinux)等。
- 启动时由
init.rc
脚本配置。
🧠 起到调度和资源管理的“幕后管家”作用。
✅ 3. HAL(Hardware Abstraction Layer,硬件抽象层)
- 用于屏蔽硬件细节,为 Android Runtime 提供统一接口。
- 每种设备功能(摄像头、音频、传感器等)都对应一个 HAL 模块,如
camera.default.so
- 厂商定制设备时需要实现 HAL 接口。
📦 相当于 Windows 中的“设备驱动中间层”。
✅ 4. Android Runtime(ART/Dalvik)
- Android 应用的运行环境,包含:
- ART 虚拟机:执行
.dex
字节码 - 栈内存管理、GC、JIT/AOT 编译器
- ART 虚拟机:执行
- 每个 App 都有一个独立的 ART 实例(运行在独立进程中)
🧠 就像 Java 的 JVM,专门为 Android 优化的运行时环境。
✅ 5. System Services(系统服务层)
- 用 Java/C++ 编写的系统服务,如:
ActivityManagerService
WindowManagerService
PackageManagerService
InputManagerService
- 提供进程/窗口/包管理等功能。
- 这些服务通常运行在
system_server
进程中。
💡 是 Framework 与底层通信的桥梁,核心逻辑都在这里。
✅ 6. Android Framework(Android 框架层)
- 所有开发者使用的核心 API 都定义在这里,如:
Activity
、Service
、ContentProvider
、BroadcastReceiver
View
、Notification
、WindowManager
、LocationManager
- Java/Kotlin 开发的应用通过这些接口操作系统功能。
🧑💻 Android 开发者最熟悉的一层,是对系统服务的“门面”。
✅ 7. Application 层(最顶层)
包括三种类型的应用:
这些应用运行在 Framework 提供的 API 之上,通过 Binder
机制与 System Server 中的服务通信。
🔄 通信机制核心:Binder 跨进程通信
在整个 AOSP 架构中,最核心的通信方式是 Binder IPC(跨进程通信),它连接:
- 应用 ↔ Framework
- Framework ↔ System Service
- System Service ↔ HAL
Binder 是 Android 自研的一套高效、安全的通信机制。
🧭 总结:层次与职责关系
[App/Framework 层]👆 高层业务逻辑👇[System Service/Runtime 层]👆 管理逻辑 & 抽象 API👇[HAL/Kernel 层]👆 硬件抽象 & 管理接口👇[Linux 内核] → 驱动硬件
断点调试
单步调试与多步调试
断点类型 普通断点 方法断点 属性断点 条件断点 依赖断点
段页式内存管理
页表:用户程序中的某一页 对应物理地址中的某一块
分段管理
GDB调试
参考博客:
使用GDB、LLDB调试安卓程序 | Xhy’s Blog
参考视频:
【小神仙讲 GDB】 通俗易懂版教程 | 一小时入门GDB | Debug | c/c++程序员必备 | 佩雨小神仙_哔哩哔哩_bilibili
GDB是什么
GD8 调试器 可以运行你在程序运行的时候检查里面到底发生了什么?
GDB可以做以下四件事情
-
Start your program, specifying anything that might affect its behavior.开始并设置参数
-
Make your program stop on specified conditions, 打断点 在特殊情况下停止
-
Examine what has happened, when your program has stopped,当你程序停止,检查发生了什么。
-
Change things in your program, so you can experiment with correcting the effects of one bug and go on to learn about another.
Those programs might be executing on the same machine as GDB (native), on another machine(remote), or on a simulator, GDB can run on most popular UNlX and Microsoft Windows variants, as well as on Mac OS X.
搭建实验环境
安装gdb
yum install gdb
检查gdb是否成功安装
gdb --version
如果是Windows下的MinGW, 无法使用yum命令,但会自带GDB工具,可以使用检查命令。
Quickstart
#include using namespace std;int main(){ int arr[4] = {1, 2, 3, 4}; for(int i = 0; i < 4; i++){ cout << arr[i] << endl; } return 0;}
使用g++编译为可执行文件
g++ test_2.cpp -o test2
使用gdb运行可执行文件
gdb test2.exe
gdb基本命令
run r 运行程序quit q 退出gdb模式list 查看我们的源代码b break打断点函数的地方 函数名字在第几行打断点info b 查看断点的情况print 打印变量arr[0]&arr[0]step s 跳转到某一个具体的函数调试(将断点打到跳转函数执行那一行,run 执行后,输入step或s进行函数跳转)
gdb小技巧
- shell 去调用终端的命令(linux下)
(gdb) shell cat test.cpp
- 日志功能
set logging on
生成一个日志文件,将gdb的过程记录下来(每次运行gdb,都需要执行该命令 才可以记录)
- watchpoint
观察变量是否变化
info 来査看我们的watchpoint
使用tips:需要将先将断点打到定义某一变量i时的命令行,执行watch i, 或者
watch *0x5ffe9c(地址),往下继续执行代码,当变量值出现变化时,会显 示新值和旧值
以上gdb使用时调试的.exe或者a.out二进制文件,当程序挂掉时,需要调试core文件。或者需要调试一个正在运行的进程。
1 当程序挂掉,需要调试core
编译时要加-g选项
编译后未生成core文件要
一个电脑 多人共享的,shell 做一些限制,core文件 不会默认生成
需要使用ulimit -a 查看core文件的size设置,
ulimit -c unlimited
重新编译会生成
-rw------ 1 root root 245760 Dec 26 11:12 core.19761
之后使用gdb
gdb ./a.out core.19761 //gdb 二进制文件 core文件
会显示程序dump在了哪里
2 调试正在运行的程序
死循环程序,在后端执行程序,使用/a.out &,会返回一个进程号给终端。
[root@iZm5e7bxhsv9ft1z0g5s5aZ gdb_study]# ./a.out &[4]21528
或者
ps -ef grep a.out // 查看进程号
使用gdb调试这段进程
gdb -p 21528
Android中Binder设计原则
参考博客:
Android Binder框架实现之Binder的设计思想-CSDN博客
写给 Android 应用工程师的 Binder 原理剖析 - 知乎
Binder架构也是采用分层架构设计, 每一层都有其不同的功能:
- Java应用层: 对于上层应用通过调用AMP.startService, 完全可以不用关心底层,经过层层调用,最终必然会调用到AMS.startService.
- Java IPC层: Binder通信是采用C/S架构, Android系统的基础架构便已设计好Binder在Java framework层的Binder客户类BinderProxy和服务类Binder;
- Native IPC层: 对于Native层,如果需要直接使用Binder(比如media相关), 则可以直接使用BpBinder和BBinder(当然这里还有JavaBBinder)即可, 对于上一层Java IPC的通信也是基于这个层面.
- Kernel物理层: 这里是Binder Driver, 前面3层都跑在用户空间,对于用户空间的内存资源是不共享的,每个Android的进程只能运行在自己进程所拥有的虚拟地址空间, 而内核空间却是可共享的. 真正通信的核心环节还是在Binder Driver.
Binder IPC原理
可以看出无论是注册服务和获取服务的过程都需要ServiceManager,需要注意的是此处的Service Manager是指Native层的ServiceManager(C++),并非指framework层的ServiceManager(Java)。ServiceManager是整个Binder通信机制的大管家,是Android进程间通信机制Binder的守护进程,Client端和Server端通信时都需要先获取Service Manager接口,才能开始通信服务, 当然查找懂啊目标信息可以缓存起来则不需要每次都向ServiceManager请求。
图中Client/Server/ServiceManage之间的相互通信都是基于Binder机制。既然基于Binder机制通信,那么同样也是C/S架构,则图中的3大步骤都有相应的Client端与Server端。
1.注册服务:首先AMS注册到ServiceManager。该过程:AMS所在进程(system_server)是客户端,ServiceManager是服务端。
2.获取服务:Client进程使用AMS前,须先向ServiceManager中获取AMS的代理类AMP。该过程:AMP所在进程(app process)是客户端,ServiceManager是服务端。
3.使用服务: app进程根据得到的代理类AMP,便可以直接与AMS所在进程交互。该过程:AMP所在进程(app process)是客户端,AMS所在进程(system_server)是服务端。
图中的Client,Server,Service Manager之间交互都是虚线表示,是由于它们彼此之间不是直接交互的,而是都通过与Binder Driver进行交互的,从而实现IPC通信方式。其中Binder驱动位于内核空间,Client,Server,Service Manager位于用户空间。Binder驱动和Service Manager可以看做是Android平台的基础架构,而Client和Server是Android的应用层.
这3大过程每一次都是一个完整的Binder IPC过程, 接下来从源码角度, 仅介绍第3过程使用服务, 即展开AMP.startService是如何调用到AMS.startService的过程
Framework开发
系统框架AMS(组件管理)、WMS(窗口管理)、PMS(包管理)等核心组件
参考博文:
Framework学习(三)之PMS、AMS、WMS_ams pms-CSDN博客
透视Android系统AMS、PMS和WMS,了解开发中的重要角色 - 知乎
→ 驱动硬件
### 断点调试单步调试与多步调试断点类型 普通断点 方法断点 属性断点 条件断点 依赖断点 ### 段页式内存管理 [外链图片转存中...(img-xCH3qQiM-1753187247174)] [外链图片转存中...(img-hc2zBxA9-1753187247174)] 页表:用户程序中的某一页 对应物理地址中的某一块[外链图片转存中...(img-u08meZkP-1753187247174)] [外链图片转存中...(img-3booEpzx-1753187247174)] [外链图片转存中...(img-hnMMPTxk-1753187247174)] [外链图片转存中...(img-Gn2XLcA0-1753187247174)] 分段管理[外链图片转存中...(img-kL3qniif-1753187247174)] ### GDB调试参考博客:[使用GDB、LLDB调试安卓程序 | Xhy\'s Blog](https://blog.xhyeax.com/2022/05/06/debug-android-by-gdb-and-lldb/)参考视频:[【小神仙讲 GDB】 通俗易懂版教程 | 一小时入门GDB | Debug | c/c++程序员必备 | 佩雨小神仙_哔哩哔哩_bilibili](https://www.bilibili.com/video/BV1EK411g7Li?spm_id_from=333.788.videopod.episodes&vd_source=780e7ef4b190df7fadfaa9238d24ba0d)#### GDB是什么GD8 调试器 可以运行你在程序运行的时候检查里面到底发生了什么?GDB可以做以下四件事情- Start your program, specifying anything that might affect its behavior.开始并设置参数- Make your program stop on specified conditions, 打断点 在特殊情况下停止- Examine what has happened, when your program has stopped,当你程序停止,检查发生了什么。- Change things in your program, so you can experiment with correcting the effects of one bug and go on to learn about another. Those programs might be executing on the same machine as GDB (native), on another machine(remote), or on a simulator, GDB can run on most popular UNlX and Microsoft Windows variants, as well as on Mac OS X.#### 搭建实验环境安装gdb
yum install gdb
检查gdb是否成功安装
gdb --version
如果是Windows下的MinGW, 无法使用yum命令,但会自带GDB工具,可以使用检查命令。#### Quickstart```c++#include using namespace std;int main(){ int arr[4] = {1, 2, 3, 4}; for(int i = 0; i < 4; i++){ cout << arr[i] << endl; } return 0;}
使用g++编译为可执行文件
g++ test_2.cpp -o test2
使用gdb运行可执行文件
gdb test2.exe
gdb基本命令
run r 运行程序quit q 退出gdb模式list 查看我们的源代码b break打断点函数的地方 函数名字在第几行打断点info b 查看断点的情况print 打印变量arr[0]&arr[0]step s 跳转到某一个具体的函数调试(将断点打到跳转函数执行那一行,run 执行后,输入step或s进行函数跳转)
gdb小技巧
- shell 去调用终端的命令(linux下)
(gdb) shell cat test.cpp
- 日志功能
set logging on
生成一个日志文件,将gdb的过程记录下来(每次运行gdb,都需要执行该命令 才可以记录)
- watchpoint
观察变量是否变化
info 来査看我们的watchpoint
使用tips:需要将先将断点打到定义某一变量i时的命令行,执行watch i, 或者
watch *0x5ffe9c(地址),往下继续执行代码,当变量值出现变化时,会显 示新值和旧值
[外链图片转存中…(img-QPs3WAWd-1753187247174)]
以上gdb使用时调试的.exe或者a.out二进制文件,当程序挂掉时,需要调试core文件。或者需要调试一个正在运行的进程。
1 当程序挂掉,需要调试core
编译时要加-g选项
编译后未生成core文件要
一个电脑 多人共享的,shell 做一些限制,core文件 不会默认生成
需要使用ulimit -a 查看core文件的size设置,
ulimit -c unlimited
重新编译会生成
-rw------ 1 root root 245760 Dec 26 11:12 core.19761
之后使用gdb
gdb ./a.out core.19761 //gdb 二进制文件 core文件
会显示程序dump在了哪里
2 调试正在运行的程序
死循环程序,在后端执行程序,使用/a.out &,会返回一个进程号给终端。
[root@iZm5e7bxhsv9ft1z0g5s5aZ gdb_study]# ./a.out &[4]21528
或者
ps -ef grep a.out // 查看进程号
使用gdb调试这段进程
gdb -p 21528
Android中Binder设计原则
参考博客:
Android Binder框架实现之Binder的设计思想-CSDN博客
写给 Android 应用工程师的 Binder 原理剖析 - 知乎
Binder架构也是采用分层架构设计, 每一层都有其不同的功能:
- Java应用层: 对于上层应用通过调用AMP.startService, 完全可以不用关心底层,经过层层调用,最终必然会调用到AMS.startService.
- Java IPC层: Binder通信是采用C/S架构, Android系统的基础架构便已设计好Binder在Java framework层的Binder客户类BinderProxy和服务类Binder;
- Native IPC层: 对于Native层,如果需要直接使用Binder(比如media相关), 则可以直接使用BpBinder和BBinder(当然这里还有JavaBBinder)即可, 对于上一层Java IPC的通信也是基于这个层面.
- Kernel物理层: 这里是Binder Driver, 前面3层都跑在用户空间,对于用户空间的内存资源是不共享的,每个Android的进程只能运行在自己进程所拥有的虚拟地址空间, 而内核空间却是可共享的. 真正通信的核心环节还是在Binder Driver.
Binder IPC原理
可以看出无论是注册服务和获取服务的过程都需要ServiceManager,需要注意的是此处的Service Manager是指Native层的ServiceManager(C++),并非指framework层的ServiceManager(Java)。ServiceManager是整个Binder通信机制的大管家,是Android进程间通信机制Binder的守护进程,Client端和Server端通信时都需要先获取Service Manager接口,才能开始通信服务, 当然查找懂啊目标信息可以缓存起来则不需要每次都向ServiceManager请求。
图中Client/Server/ServiceManage之间的相互通信都是基于Binder机制。既然基于Binder机制通信,那么同样也是C/S架构,则图中的3大步骤都有相应的Client端与Server端。
1.注册服务:首先AMS注册到ServiceManager。该过程:AMS所在进程(system_server)是客户端,ServiceManager是服务端。
2.获取服务:Client进程使用AMS前,须先向ServiceManager中获取AMS的代理类AMP。该过程:AMP所在进程(app process)是客户端,ServiceManager是服务端。
3.使用服务: app进程根据得到的代理类AMP,便可以直接与AMS所在进程交互。该过程:AMP所在进程(app process)是客户端,AMS所在进程(system_server)是服务端。
图中的Client,Server,Service Manager之间交互都是虚线表示,是由于它们彼此之间不是直接交互的,而是都通过与Binder Driver进行交互的,从而实现IPC通信方式。其中Binder驱动位于内核空间,Client,Server,Service Manager位于用户空间。Binder驱动和Service Manager可以看做是Android平台的基础架构,而Client和Server是Android的应用层.
这3大过程每一次都是一个完整的Binder IPC过程, 接下来从源码角度, 仅介绍第3过程使用服务, 即展开AMP.startService是如何调用到AMS.startService的过程
Framework开发
系统框架AMS(组件管理)、WMS(窗口管理)、PMS(包管理)等核心组件
参考博文:
Framework学习(三)之PMS、AMS、WMS_ams pms-CSDN博客
透视Android系统AMS、PMS和WMS,了解开发中的重要角色 - 知乎