title: Android开发学习笔记
date: 2025-11-04 15:46:29
tags:

Android开发学习笔记

四大组件篇

四大组件有哪些?

1️⃣ Activity

2️⃣ Service

3️⃣ BroadcastReceiver

4️⃣ ContentProvider

什么是Activity?

Activity(活动)是一个为实现交互而提供的Android应用组件。每个Activity都有一个窗口,该窗口可以全屏幕填充,也可以是一个小窗口浮动在其他窗口上。一个应用程序通常由多个Activity组成,它会指定应用程序中的某个Activity作为主Activity,这意味着当用户第一次启动应用程序时呈现给用户的活动,并且Activity可以相互跳转来执行不同的操作。

请介绍Activity的生命周期?

Activity的生命周期

Activity生命周期的不同情况

1️⃣ 正常启动与销毁


2️⃣ 从一个 Activity 跳转到另一个


3️⃣ 屏幕旋转(配置更改)


4️⃣ 按 Home 键返回桌面


5️⃣ 应用被系统回收(内存不足)


6️⃣ 弹出 Dialog 或透明 Activity


7️⃣ 多任务切换(Recent Apps)

切换横竖屏Activity生命周期的变化?

在 Android 中,切换横竖屏属于配置更改(Configuration Change),默认行为是系统会销毁并重建当前 Activity,因此会触发完整的生命周期流程:

onPause()→ onStop()→ onDestroy()→ onCreate()→ onStart()→ onResume()

android:configChanges="orientation|screenSize"

当屏幕方向(orientation)或屏幕尺寸(screenSize)发生变化时,当前 Activity 自行处理这些变化,系统不需要重新创建它。

为什么要加 screenSize?

从 Android 3.2(API 13)开始,横竖屏切换不仅改变方向,还可能改变屏幕尺寸(尤其在平板或多窗口设备上)。因此:

Activity的四种启动模式?

启动模式 描述 是否复用已有实例 是否创建新任务栈
standard 默认模式,每次启动都会创建新实例 ❌ 否 ❌ 否
singleTop 如果栈顶已有该 Activity,则复用 ✅ 是(仅栈顶) ❌ 否
singleTask 如果任务栈中已有该 Activity,则复用并清除其上的所有 Activity ✅ 是 ✅ 是(可选)
singleInstance 独占任务栈,其他 Activity 无法进入该栈 ✅ 是 ✅ 是(强制)

1️⃣ standard(默认模式)

2️⃣ singleTop

3️⃣ singleTask

4️⃣ singleInstance

如何设置启动模式?

Manifest 设置方式

AndroidManifest.xml 中为目标 Activity 添加:

<activity
    android:name=".YourActivity"
    android:launchMode="singleTask" />

Intent 设置方式(部分模式)

Intent intent = new Intent(this, YourActivity.class);

// singleTop
intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);

// singleTask(配合 CLEAR_TOP)
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);

// singleInstance(需 Manifest 配合)
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

什么是Service

Service意为“服务”,相对于Activity是一个有可见界面的、为实现交互而提供的Android应用组件,Service则是一个可以在后台长期运行但又不使用用户界面的Android应用组件。它可以和其他的组件形成一定联系,通过传达信息来联系多个组件共同执行某个操作。举个最简单的例子,你此时正在用手机听歌曲,播放歌曲的进程就是一个后台服务。

Service的生命周期

Service的生命周期

startService()

sequenceDiagram
participant Client
participant Service

Client->>Service: startService()
Service->>Service: onCreate()
Service->>Service: onStartCommand()
Client-->>Service: 不需要保持连接

⚠️ 即使启动者退出,服务仍会继续运行,直到显式停止。

bindService()

sequenceDiagram
participant Client
participant Service

Client->>Service: bindService()
Service->>Service: onCreate()
Service->>Service: onBind()
Client->>Service: 与服务通信
Client->>Service: unbindService()
Service->>Service: onUnbind()
Service->>Service: onDestroy()(若无其他绑定)

⚠️ 服务生命周期受绑定者影响,所有客户端解绑后才会销毁。

startService

onCreate()

onStartCommand()

onBind()

onDestory()

这几个方法都是回调方法,且在主线程中执行,由android操作系统在合适的时机调用。

代码实例

public class TestOneService extends Service{

    @Override
    public void onCreate() {
        Log.i("Kathy","onCreate - Thread ID = " + Thread.currentThread().getId());
        super.onCreate();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.i("Kathy", "onStartCommand - startId = " + startId + ", Thread ID = " + Thread.currentThread().getId());
        return super.onStartCommand(intent, flags, startId);
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        Log.i("Kathy", "onBind - Thread ID = " + Thread.currentThread().getId());
        return null;
    }

    @Override
    public void onDestroy() {
        Log.i("Kathy", "onDestroy - Thread ID = " + Thread.currentThread().getId());
        super.onDestroy();
    }
}
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Log.i("Kathy", "Thread ID = " + Thread.currentThread().getId());
        Log.i("Kathy", "before StartService");

        //连续启动Service
        Intent intentOne = new Intent(this, TestOneService.class);
        startService(intentOne);
        Intent intentTwo = new Intent(this, TestOneService.class);
        startService(intentTwo);
        Intent intentThree = new Intent(this, TestOneService.class);
        startService(intentThree);

        //停止Service
        Intent intentFour = new Intent(this, TestOneService.class);
        stopService(intentFour);

        //再次启动Service
        Intent intentFive = new Intent(this, TestOneService.class);
        startService(intentFive);

        Log.i("Kathy", "after StartService");
    }
}
    02-06 15:19:45.090 8938-8938/? I/Kathy: Thread ID = 1
    02-06 15:19:45.090 8938-8938/? I/Kathy: before StartService
    02-06 15:19:45.233 8938-8938/? I/Kathy: onCreate - Thread ID = 1
    02-06 15:19:45.234 8938-8938/? I/Kathy: onStartCommand - startId = 1, Thread ID = 1
    02-06 15:19:45.234 8938-8938/? I/Kathy: onStartCommand - startId = 2, Thread ID = 1
    02-06 15:19:45.235 8938-8938/? I/Kathy: onStartCommand - startId = 3, Thread ID = 1
    02-06 15:19:45.236 8938-8938/? I/Kathy: onDestroy - Thread ID = 1
    02-06 15:19:45.237 8938-8938/? I/Kathy: onCreate - Thread ID = 1
    02-06 15:19:45.237 8938-8938/? I/Kathy: onStartCommand - startId = 1, Thread ID = 1
    02-06 15:19:45.238 8938-8938/? I/Kathy: after StartService

作者:Big不吃鱼
链接:https://www.jianshu.com/p/4c798c91a613
来源:简书

bindService

代码实现

public class TestTwoService extends Service{

    //client 可以通过Binder获取Service实例
    public class MyBinder extends Binder {
        public TestTwoService getService() {
            return TestTwoService.this;
        }
    }

    //通过binder实现调用者client与Service之间的通信
    private MyBinder binder = new MyBinder();

    private final Random generator = new Random();

    @Override
    public void onCreate() {
        Log.i("Kathy","TestTwoService - onCreate - Thread = " + Thread.currentThread().getName());
        super.onCreate();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.i("Kathy", "TestTwoService - onStartCommand - startId = " + startId + ", Thread = " + Thread.currentThread().getName());
        return START_NOT_STICKY;
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        Log.i("Kathy", "TestTwoService - onBind - Thread = " + Thread.currentThread().getName());
        return binder;
    }

    @Override
    public boolean onUnbind(Intent intent) {
        Log.i("Kathy", "TestTwoService - onUnbind - from = " + intent.getStringExtra("from"));
        return false;
    }

    @Override
    public void onDestroy() {
        Log.i("Kathy", "TestTwoService - onDestroy - Thread = " + Thread.currentThread().getName());
        super.onDestroy();
    }

    //getRandomNumber是Service暴露出去供client调用的公共方法
    public int getRandomNumber() {
        return generator.nextInt();
    }
}

Activity、Service、intent之间的联系是什么?

概念

它们之间的联系

Intent intent = new Intent(this, MyService.class);
startService(intent); // 或 bindService(intent, conn, flags);

Activity 是发起者,Intent 是载体,Service 是被启动者。

Intent 中可以携带数据(如 URL、参数等)供 Service 使用。

Intent intent = new Intent(this, SecondActivity.class);
startActivity(intent);

同样通过 Intent 实现组件间跳转和数据传递。

Intent intent = new Intent(this, MainActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);

注意:Service 启动 Activity 时必须加 FLAG_ACTIVITY_NEW_TASK。因为 Service 默认没有 Activity 的任务栈。

在Activtiy和在Service中创建一个Thread的区别?

首先需要了解Service的几个特点。

(1) 默认情况下,Service其实是运行在主线程中的,如果需要执行复杂耗时的操作,必须在Service中再创建一个 Thread来执行任务。

(2) Service的优先级高于后台挂起的Activity,当然,也高于Activity所创建的Thread,因此,系统可能在内存不足的 时候优先杀死后台的Activity或者Thread,而不会轻易杀死Service组件,即使被迫杀死Service,也会在资源可用时重启 被杀死的Service 其实,Service和Thread根本就不是一个级别的东西,Service是系统的四大组件之一,Thread只是一个用来执行后台任 务的工具类,它可以在Activity中被创建,也可以在Service中被创建。因此,我们其实不应该讨论该使用Service还是 Thread,而是应该讨论在什么地方创建Thread。

典型的应用中,它可以在以下三个位置被创建,不同的位置,其生命周期不一样,所以,我们应该根据该Thread的目标 生命周期来决定是在Service中创建Thread还是在Activity中创建它。

(1) 在Activity中被创建 这种情况下,一般在onCreate时创建,在onDestroy()中销毁,否则,Activity销毁后,Thread是会依然在后台运行着。这种情况下,Thread的生命周期即为整个Activity的生命周期。所以,在Activity中创建的Thread只适合完成一些依赖 Activity本身有关的任务,比如定时更新一下Activity的控件状态等。核心特点:该Thread的就是为这个Activity服务的,完成这个特定的Activity交代的任务,主动通知该Activity一些消息和 事件,Activity销毁后,该Thread也没有存活的意义了。

(2)在Application中被创建 这种情况下,一般自定义Application类,重载onCreate方法,并在其中创建Thread,当然,也会在onTerminate()方法 中销毁Thread,否则,如果Thread没有退出的话,即使整个Application退出了,线程依然会在后台运行着。这种情况下,Thread的生命周期即为整个Application的生命周期。所以,在Application中创建的Thread,可以执行一 些整个应用级别的任务,比如定时检查一下网络连接状态等等。核心特点:该Thread的终极目标是为这个APP的各个Activity服务的,包括完成某个Activity交代的任务,主动通知某个 Activity一些消息和事件等,APP退出之后该Thread也没有存活的意义了。以上这两种情况下,Thread的生命周期都不应该超出整个应用程序的生命周期,也就是,整个APP退出之后,Thread都 应该完全退出,这样才不会出现内存泄漏或者僵尸线程。那么,如果你希望整个APP都退出之后依然能运行该Thread, 那么就应该把Thread放到Service中去创建和启动了。

(3)在Service中被创建 这是保证最长生命周期的Thread的唯一方式,只要整个Service不退出,Thread就可以一直在后台执行,一般在Service 的onCreate()中创建,在onDestroy()中销毁。所以,在Service中创建的Thread,适合长期执行一些独立于APP的后台任务,比较常见的就是:在Service中保持与服 务器端的长连接。核心特点:该Thread可以为APP提供一些“服务”或者“状态查询”,但该Thread并不需要主动通知APP任何事件,甚至不 需要知道APP是谁。

Android进程优先级

前台进程(Foreground process)

前台进程是用户当前做的事所必须的进程,如果满足下面各种情况中的一种,一个进程被认为是在前台:

杀死前台进程需要用户交互,因为前台进程的优先级是最高的。

可见进程(Visible process)

如果一个进程不含有任何前台的组件,但仍可被用户在屏幕上所见。当满足如下任一条件时,进程被认为是可见的:

服务进程 (Service process)

如果一个进程中运行着一个service,这个service是通过 startService() 开启的,并且不属于上面两种较高优先级的情况,这个进程就是一个服务进程。

尽管服务进程没有和用户可以看到的东西绑定,但是它们一般在做的事情是用户关心的,比如后台播放音乐,后台下载数据等。所以系统会尽量维持它们的运行,除非系统内存不足以维持前台进程和可见进程的运行需要。

后台进程 (Background process)

如果进程不属于上面三种情况,但是进程持有一个用户不可见的activity(activity的onStop()被调用,但是onDestroy()没有调用的状态),就认为进程是一个后台进程。

后台进程不直接影响用户体验,系统会为了前台进程、可见进程、服务进程而任意杀死后台进程。

通常会有很多个后台进程存在,它们会被保存在一个LRU (least recently used)列表中,这样就可以确保用户最近使用的activity最后被销毁,即最先销毁时间最远的activity。

空进程

如果一个进程不包含任何活跃的应用组件,则认为是空进程。

例如:一个进程当中已经没有数据在运行了,但是内存当中还为这个应用驻留了一个进程空间。
保存这种进程的唯一理由是为了缓存的需要,为了加快下次要启动这个进程中的组件时的启动时间。系统为了平衡进程缓存和底层内核缓存的资源,经常会杀死空进程。

如何保证Service不被杀死?

使用前台服务(Foreground Service)

在onStartCommand里面调用 startForeground()方法把Service提升为前台进程级别,然后再onDestroy里面要记得调用stopForeground ()方法。

onStartCommand方法,返回START_STICKY

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    return START_STICKY;
}
返回值 含义
START_STICKY 被系统杀死后尝试重启服务,但不保留原 Intent
START_NOT_STICKY 被杀后不重启服务
START_REDELIVER_INTENT 被杀后重启服务并重新传入原 Intent
START_STICKY_COMPATIBILITY 与 START_STICKY 类似,兼容旧版本

使用双进程守护(不推荐但常见)

启动两个互相绑定的 Service(A 和 B),当一个被杀时另一个重启它。

⚠️ Android 8.0+ 后效果有限,且可能被系统判定为“滥用”。

绑定组件(如 Activity 或其他 Service)

使用 bindService() 绑定后,Service 生命周期受绑定者影响。若绑定者存活,系统不易杀死 Service。

Broadcast的定义

Broadcast Receiver是Android四大组件之一,是全局大喇叭,就像以前学校里面的大喇叭一样,有什么事情,就通过广播通知到全校师生。Android的广播机制是一种广泛运用的在应用程序之间传输信息的机制。主要有两大角色:广播接收者和广播发送者。因此,我们可以注册一个广播接收者,用来接收其他进程或者系统发出的广播;同样的,我们也可以通过广播发送者,向其他进程发送我们的广播,告诉其他进程需要做某个动作。

静态注册和动态注册是什么?

静态注册

在AndroidManifest.xml清单文件里直接声明的方式叫做静态注册。

<receiver 
    //此广播接收者类是mXRReceiver
    android:name=".mXRReceiver" >
    //用于系统启动完成时,接收系统发送的广播
    <intent-filter>
        <action android:name="android.intent.action.BOOT_COMPLETED" />
    </intent-filter>
</receiver>

然后重写onReceive()函数。

public class mXRReceiver extends BroadcastReceiver {

  //接收到广播后自动调用该方法
  @Override
  public void onReceive(Context context, Intent intent) {
    //写入接收广播后的操作
    }
}

如上面代码,此时注册了mXRReceiver这个广播,当系统启动后,就会发送BOOT_COMPLETED的广播,符合mXRReceiver所设置的过滤条件,就会自动调用onReceive()函数,执行对应内容。

注意:onReceive()函数不能执行耗时流程。

动态注册

在运行时调用 registerReceiver() 注册,必须在合适的时机调用 unregisterReceiver() 取消,否则会导致内存泄漏。

//定义广播接收器
public class MyReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        if (Intent.ACTION_BATTERY_CHANGED.equals(action)) {
            // 处理电量变化
        }
    }
}

//在 Activity/Service 中注册
MyReceiver receiver = new MyReceiver();
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_BATTERY_CHANGED);
registerReceiver(receiver, filter);

//在合适时机取消注册
@Override
protected void onDestroy() {
    super.onDestroy();
    unregisterReceiver(receiver);
}
特点 说明
注册时机 在代码运行时调用 registerReceiver(),通常在 onCreate()onStart()
取消时机 onDestroy()onStop() 中调用 unregisterReceiver(),避免内存泄漏
生命周期 与注册它的组件(Activity/Service)生命周期绑定
灵活性 可以根据业务需要,随时注册/注销
常见用途 监听网络变化、电量变化、蓝牙状态、耳机插拔等只在前台需要的广播

请介绍Android里广播的分类?

普通广播

系统广播

有序广播

特性 描述
发送方式 使用 sendOrderedBroadcast() 方法
接收顺序 按 IntentFilter 中设置的 priority 值从高到低,同级别随机
广播传播控制 接收者可以调用 abortBroadcast() 终止传播
数据修改能力 可以通过 setResult() 修改广播结果
同步执行 接收者按顺序同步执行,效率低于普通广播
适用场景 需要优先级控制或广播结果处理的场景

粘性广播

粘性广播是一种 特殊的广播机制,当广播被发送后,系统会将该广播的 Intent 保存在内存中。后续注册的接收者(即使在广播发送之后才注册)也能立即收到这条广播的最后一次内容。从 Android 5.0(API 21)开始已废弃,官方强烈不推荐使用(安全、性能)

App应用内广播

// 发送广播
Intent intent = new Intent();
intent.setComponent(new ComponentName(context, MyReceiver.class));
intent.setAction("com.example.APP_EVENT");
intent.putExtra("msg", "hello");
context.sendBroadcast(intent);

// 接收广播
public class MyReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        String msg = intent.getStringExtra("msg");
        Log.d("MyReceiver", "收到应用内广播: " + msg);
    }
}

程序A能否接收到程序B的广播?

程序A能接收到程序B的广播,前提是程序B发送的是全局广播(非 LocalBroadcast),并且程序A正确注册了匹配的 Action,同时满足 Android 版本要求(如 8.0+ 动态注册限制)、android:exported=true 属性配置正确,以及必要的权限声明。

程序A不能接收到程序B的广播,常见原因包括:程序B使用了 LocalBroadcast(仅限应用内)、Action 不匹配、程序A未注册或注册方式受限(如隐式广播静态注册在新版本被限制)、权限不足,或者接收器未导出(exported=false)。

什么是内容提供者?(ContentProvider)

之前有说过可以用Intent在组件中传递数据,那么其数据的大小是否有限制呢?很明显是有限制的,Intent传递数据大小的限制大概在1M左右,超过这个限制就会静默崩溃。因此我们就可以通过ContentProvider进行进程间的数据传递,也就是ContentProvider是一种进程间的数据传递的方式。 一般来说,Android数据存储的方式有:文件,数据库,网络,SharePreferences,ContentProvider。
ContentProvider
上图是网上找到的一个图片,然而从图上可以明显的知道,ContentProvider更准确来说只是一个中间者的身份,真正存储数据的是数据库和文件等形式,这一点要分清楚!

ContentProvider用法

创建类:继承 ContentProvider,重写以下方法:

public class MyProvider extends ContentProvider {
    @Override
    public boolean onCreate() {
        // 初始化数据库
        return true;
    }

    @Override
    public Cursor query(Uri uri, String[] projection, String selection,
                        String[] selectionArgs, String sortOrder) {
        // 查询逻辑
        return cursor;
    }

    @Override
    public Uri insert(Uri uri, ContentValues values) {
        // 插入逻辑
        return newUri;
    }

    @Override
    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
        // 更新逻辑
        return count;
    }

    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        // 删除逻辑
        return count;
    }

    @Override
    public String getType(Uri uri) {
        return "vnd.android.cursor.dir/vnd.com.example.table";
    }
}

在 AndroidManifest.xml 注册:

<provider
    android:name=".MyProvider"
    android:authorities="com.example.provider"
    android:exported="true"/>

访问

外部应用访问:通过 content://com.example.provider/… 的 URI 使用 ContentResolver 调用。

ContentProvider原理

ContentProvider 之所以能实现数据共享,核心在于 Binder 跨进程通信机制。应用 A(数据提供方)通过 ContentProvider 暴露统一的 CRUD 接口,应用 B(数据使用方)通过 ContentResolver + URI 访问,系统的 AMS(ActivityManagerService) 负责 Provider 的注册和调度,从而实现跨进程的数据共享。

核心角色

数据访问流程(以 query() 为例)

客户端调用:

Cursor cursor = getContentResolver().query(uri, ...);
ContentResolver 解析 URI,找到对应的 authority。

系统查找 Provider:

Binder 跨进程调用:

结果返回:

说说 ContentProvider、ContentResolver、ContentObserver 之间的关系?

三者的关系可以概括为:ContentProvider 提供数据 → ContentResolver 访问数据 → ContentObserver 监听数据变化。它们共同构成了 Android 中跨进程数据共享与监听的完整机制。

ContentProvider

ContentResolver

ContentObserver

为什么要使用通过ContentResolver类从而与ContentProvider类进行交互,而不直接访问ContentProvider类?

因为 ContentProvider 运行在不同进程,直接访问它并不现实。Android 设计了 ContentResolver 作为中介,统一对外提供数据访问接口,屏蔽了跨进程通信(Binder IPC)的复杂性,同时增强了安全性和解耦性。

ContentProvider的底层是采用Android中的Binder机制,既然已经有了binder实现了进程间通信了为什么还会需要contentProvider?

虽然 Binder 已经能实现进程间通信,但它只是一个 底层 IPC 机制,过于底层、复杂且不适合直接用于应用层的数据共享。ContentProvider 是对 Binder 的进一步封装,提供了统一的 数据访问接口(CRUD + URI)、权限管理 和 生命周期管理,让开发者能更安全、方便地在不同应用之间共享结构化数据。

抽象层级不同

👉 类比:Binder 像“电线”,ContentProvider 像“插座 + 接口标准”,让大家能方便、安全地用电。

数据访问的统一接口

安全性与权限管理

生命周期与系统管理