1.应用组件与生命周期管理

  • 四大组件
    • Activity:负责UI展示和用户交互,生命周期包括onCreate()onStart()onResume()onPause()onStop()onDestroy()等。
    • Service:后台执行长时间运行的任务,生命周期包括onCreate()onStartCommand()onBind()onDestroy()等。
    • BroadcastReceiver:用于接收系统或应用发送的广播消息。
    • ContentProvider:提供数据共享机制,允许应用之间共享数据。
  • 生命周期管理
    • Framework层通过ActivityManagerService(AMS)管理组件的生命周期。
    • 了解ActivityThreadInstrumentation等类的作用。

四大组件

(1)Activity:通俗来讲其实就是APP上用户看到的一个个页面,Activity组件负责界面展示、处理用户交互、进行数据传递等

(2)Service:无界面的后台组件,用于执行长期运行的操作。比如说应用商店后台下载东西、后台播放QQ音乐等等

(3)BroadcastReceiver:手机里的“消息喇叭”,用于监听系统或者应用发出的全局事件的组件,比如网络状态的变化、充电状态的变化等等

(4)ContentProvider:应用间的“数据共享存储桥”,是管理跨应用访问的组件,通过URI来标识数据,比如说咱们的手机通讯录是不是可以被多个应用访问读取

Activity

1.Activity 生命周期

为了在 activity 生命周期的各个阶段之间导航过渡,Activity 类提供了六个核心回调:onCreate()、onStart()、onResume()、onPause()、onStop() 和 onDestroy()。当 activity 进入新状态时,系统会调用其中每个回调。

生命周期.png (界面的生命周期从创建一销毁经历的过程) onCreate() onStart() onResume() onPause() onStop() onRestart() onDestroy()
  1. onCreate()
    当activity第一次创建时调用,用于初始化activity的基本组件,例如布局、视图和数据。
    创建界面
    还在创建,正在配置
    配置界面长什么样子

  2. onStart()
    当 activity 变得可见但还没有获得焦点时调用,用于准备开始用户交互。(Activity 变为可见时调用)

渲染界面
启动
渲染 = 绘制

  1. onResume()
    当 activity 获得焦点并开始与用户交互时调用,用于启动动画、音乐播放等用户体验相关的任务。( Activity 获得焦点并开始与用户交互时调用)
    渲染完毕,等待用户交互(界面看得见,可以进行交互了)
    展现在面前,等待交互,用户拖拉点拽
    ↓activity开始运行了

  2. onPause()
    当activity失去焦点但仍然可见时调用,用于暂停动画、音乐播放等用户体验相关的任务。(Activity失去焦点但仍可见时调用,通常用于保存数据或释放资源)
    在当前Activity被其他Activity覆盖或锁屏时调用;
    停止一切交互事件

  3. onStop()
    界面不可见(Activity完全不可见时调用)
    当activity不再可见时调用,用于释放资源、保存数据等清理工作。

  4. onRestart()
    在Activity从停止状态再次启动时调用(Activity由停止状态变为运行状态之前调用)

  5. onDestroy()
    当activity被销毁时调用,用于释放所有资源、取消注册的监听器等最终的清理工作(Activity销毁时调用,释放资源)。


进入后台:onPause() -> onStop()
从后台返回界面:onRestart() -> onStart() -> onResume()


从A界面切换到B界面
A -> B
A onPause()

B onCreate()
B onStart()
B onResume()

A onStop()

再从B界面返回到A界面
B onPause()

A onRestart()
A onStart()
A onResume()

B onStop()
B onDestory() (由于栈的原因,B返回到A界面就要被销毁了)


  1. onSaveInstanceState(outState: Bundle):Activity即将被销毁前调用,用于保存临时数据,以便在Activity重新创建时恢复状态。
  2. onBackPressed():用户按下返回键时调用,通常用于处理返回键的逻辑。

2.启动 Activity 的方法

2.1同一应用内,依据类名启动:

1
startActivity(new Intent(MainActivity.this, SecondActivity.class));

2.2 同一应用内,依据 action 启动:

SecondActivity 配置如下:

1
2
3
4
5
6
<activity android:name=".SecondActivity">
<intent-filter>
<action android:name="com.test.intent.action.SECOND"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>

启动方法如下:

1
2
Intent secondIntent = new Intent("com.test.intent.action.SECOND");
startActivity(secondIntent);

2.3不同应用,依据 action 启动

不用应用之间也可以使用 action 方式启动,不过需要事先知道目标应用的 action 。
比如,打开原生设置的 wifi 页面,

1
startActivity(new Intent(Settings.ACTION_WIFI_SETTINGS));

2.4不同应用,A 应用启动 B 应用的首页面(需要知道 B 应用的包名):

1
2
Intent packageIntent = getPackageManager().getLaunchIntentForPackage("packageName");
startActivity(packageIntent);

2.5不同应用,A 应用启动 B 应用的特定页面(需要知道 B 应用的包名、页面的类名):

1
2
3
4
Intent intent1= new Intent();
intent1.setComponent(new ComponentName("android.systemupdate.service",
"android.systemupdate.activitys.SettingActivity"));
startActivity(intent1);

2.6启动应用的小技巧

如果需要启动的应用不存在就会报错或者闪退,
建议在使用 Intent 的时候,添加 非空判断 和 Intent.resolveActivity判断。

Intent.resolveActivity()

  • 查找可用的组件:当你创建一个 Intent 并想要启动某个功能或页面时,resolveActivity 可以帮助你确认是否存在能够处理这个 Intent 的 Activity。
  • 避免崩溃:通过检查是否有可处理的组件,可以避免由于没有合适的目标而导致的崩溃。
1
2
3
4
5
Intent secondIntent = new Intent("com.test.intent.action.SECOND");
if (secondIntent != null
&& secondIntent.resolveActivity(getPackageManager()) != null) {
startActivity(secondIntent);
}

3.launchMode 启动模式

android:launchMode=””限制activity在栈里面出现的方式

如上表所示,”standard” 是默认模式,适用于大多数类型的 activity。对众多类型的 activity 而言,”singleTop” 也是常见且有用的启动模式。其他模式(”singleTask”、”singleInstance” 和 “singleInstancePerTask”)不适用于大多数应用。它们所形成的交互模式可能让用户感到陌生,并且与大多数其他应用差别较大。

android:launchMode=””

  • standard 默认的启动模式(标准) ——> A->B->C->D->A->B 始终创建新的activity实例

    • 标准模式,一调用 startActivity() 方法就会产生一个新的实例。
  • singleTop(栈顶复用) ——> 如果在栈的顶部有一个需要启用的对象,就直接用即可,不会再创建一个新的对象
         A->B->C->D->C(这里的C就无法复用,只能复用栈顶的)
         A->B->C(此时C在栈顶,若还想再启动一个C,此时就无需再创建新的,直接复用)

    • 来了intent , 每次都创建新的实例,仅一个例外:当栈顶的activity 恰恰就是该activity 的实例(即需要创建的实例)时,不再创建新实例。这解决了栈顶复用问题。
  • singleTask(栈内复用)
         A->B->C(栈内已有)
         如果想启用D(被singleTask修饰)则会另起一个任务栈
         D->

    • 来了 intent 后,检查栈中是否存在该 activity 的实例,如果存在就把 intent 发送给它,否则就创建一个新的该activity的实例,放入一个新的 task 栈的栈底。
    • 肯定位于一个 task 的栈底,而且栈中只能有它一个该 activity 实例,但允许其他activity加入该栈。解决了在一个 task 中共享一个activity。
  • singleInstance(单例) ——> 栈里面如果存在某个界面,就直接从栈里面弹出来复用,不会创建新的实例,把栈中已有的弹出来,该activity始终是其任务中的唯一,同一个界面在栈里面只有一个对象。

    • 这个跟 singleTask 基本上是一样,只有一个区别:在这个模式下的 Activity实例所处的 task 中,只能有这个 activity 实例,不能有其他的实例。
    • 一旦该模式的activity的实例已经存在于某个栈中,任何应用在激活该 activity 时都会重用该栈中的实例,解决了多个 task 共享一个 activity 。
  • singleInstancePerTask

配置启动模式

方法二的优先级高于方法一

限定范围不同:
方法一无法直接设置 FLAG_ACTIVITY_CLEAR_TOP 标识;
方法二无法设置为 singleInstance ;

方法一:通过注册文件配置

1
2
3
4
5
6
7
<activity android:name=".MainActivity"
android:launchMode="singleTop">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>

方法二:通过 Intent 设置

1
2
3
4
5
6
Intent secondIntent = new Intent("com.test.intent.action.SECOND");
secondIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

if (secondIntent.resolveActivity(getPackageManager()) != null) {
startActivity(secondIntent);
}

Activity 的 flag 有很多,有的可以设定启动模式,有的可以影响 Activity 的运行状态

  • FLAG_ACTIVITY_SINGLE_TOP:指定为 singleTop 启动模式,效果和在 xml 中指定该启动模式相同;

  • FLAG_ACTIVITY_NEW_TASK:指定为 singleTask 启动模式,效果和在 xml 中指定该启动模式相同;

    • 从 Service 里启动 Activity ,需要设置 Intent.FLAG_ACTIVITY_NEW_TASK
  • FLAG_ACTIVITY_CLEAR_TOP:启动时,在同一个任务栈中所有位于它上面的 Activity 都要出栈。这个标记一版会和 singleTask 一起出现,在这种情况下,被启动的 Activity 如果已有实例存在,会回调其 onNewIntent 方法。被启动的 Activity 如果采用 standard 模式启动,那么连同它之上的 Activity 都要出栈,系统会创建新的实例并放入栈顶。

  • FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS:添加这个标记的 Activity 不会出现在历史任务列表中。等同于在 xml 中设置 android:excludeFromRecents="true"

4.Activity之间传递数据

  • **直接使用 Intent**:
    • 适合传递少量简单数据(如基本类型、字符串等)。
    • 代码简洁,适合快速开发。
  • **使用 Bundle**:
    • 适合传递大量数据或复杂数据(如对象、集合等)。
    • 代码结构更清晰,适合需要复用的场景。

4.1使用 intent.putExtra

发送方:

1
2
3
Intent testIntent = new Intent(MainActivity.this, TestActivity.class);
testIntent.putExtra("testIntentFrom", MainActivity.class.getSimpleName());
startActivity(testIntent);

接收方:

1
2
3
4
5
6
7
8
9
10
11
12
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test);

Intent from = getIntent();
if (from != null) {
String date = from.getStringExtra("testIntentFrom");
if (date != null) {
// deal with data
}
}
}

4.2使用 Bundle

在 Android 开发中,Bundle 是一种用于在组件(如 ActivityFragment)之间传递数据的容器。它类似于一个键值对集合,可以存储基本数据类型、字符串、数组、Parcelable 对象等。

Bundle 方式和 Intent 的区别:

  • Intent 方式主要传递基本数据类型;
  • Bundle 方式既可以传递基本数据类型,也可以传递复杂的数据,如序列化对象、数组等。

发送方:

  • 使用 Intent 启动目标 Activity

  • 将需要传递的数据放入 Bundle 中。

  • 使用 Intent.putExtras() 方法将 Bundle 附加到 Intent

  • 调用 startActivity() 方法启动目标 Activity

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 发送数据的 Activity
Intent intent = new Intent(SenderActivity.this, ReceiverActivity.class);

// 创建 Bundle 对象
Bundle bundle = new Bundle();
bundle.putString("key_name", "John Doe"); // 传递字符串
bundle.putInt("key_age", 25); // 传递整数
bundle.putBoolean("key_isStudent", true); // 传递布尔值

// 将 Bundle 附加到 Intent
intent.putExtras(bundle);

// 启动目标 Activity
startActivity(intent);

接收方:

  • 在目标 Activity 中,通过 getIntent() 方法获取传递过来的 Intent

  • 使用 Intent.getExtras() 方法获取 Bundle 对象。

  • 根据键名从 Bundle 中提取数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 接收数据的 Activity
Intent intent = getIntent();

// 获取 Bundle 对象
Bundle bundle = intent.getExtras();
if (bundle != null) {
// 从 Bundle 中读取数据
String name = bundle.getString("key_name");
int age = bundle.getInt("key_age");
boolean isStudent = bundle.getBoolean("key_isStudent");

// 使用数据
Log.d("ReceiverActivity", "Name: " + name);
Log.d("ReceiverActivity", "Age: " + age);
Log.d("ReceiverActivity", "Is Student: " + isStudent);
}

传递复杂对象 实现 ParcelableSerializable

如果需要在 Bundle 中传递自定义对象,该对象必须实现 ParcelableSerializable 接口。

复杂对象:优先使用 Parcelable,因为它比 Serializable 更高效。

Parcelable接口

  • putParcelable()

  • getParcelable()

  1. 定义 Parcelable 对象

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    public class User implements Parcelable {
    private String name;
    private int age;

    // 构造函数、Getter 和 Setter 省略

    // Parcelable 实现
    protected User(Parcel in) {
    name = in.readString();
    age = in.readInt();
    }

    public static final Creator<User> CREATOR = new Creator<User>() {
    @Override
    public User createFromParcel(Parcel in) {
    return new User(in);
    }

    @Override
    public User[] newArray(int size) {
    return new User[size];
    }
    };

    @Override
    public int describeContents() {
    return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
    dest.writeString(name);
    dest.writeInt(age);
    }
    }
  2. 发送 Parcelable 对象

    1
    2
    3
    4
    5
    6
    User user = new User("John Doe", 25);
    Bundle bundle = new Bundle();
    bundle.putParcelable("key_user", user); // 传递 Parcelable 对象

    intent.putExtras(bundle);
    startActivity(intent);
  3. 接收 Parcelable 对象

    1
    2
    3
    4
    5
    6
    Bundle bundle = getIntent().getExtras();
    if (bundle != null) {
    User user = bundle.getParcelable("key_user");
    Log.d("ReceiverActivity", "User Name: " + user.getName());
    Log.d("ReceiverActivity", "User Age: " + user.getAge());
    }

4.3数据回传

startActivityForResult 方法用于启动一个新的活动(Activity),并期待从这个活动中返回结果。

步骤 1:在发送方 Activity(A)中启动目标 Activity 并等待结果

使用 startActivityForResult() 启动目标 Activity,并指定一个请求码(requestCode)以标识请求。

1
2
3
4
5
6
// 发送方 Activity (A)
Intent intent = new Intent(SenderActivity.this, ReceiverActivity.class);

// 启动目标 Activity 并等待结果
int requestCode = 1; // 请求码,用于标识请求
startActivityForResult(intent, requestCode);

步骤 2:在目标 Activity(B)中设置回传数据

在目标 Activity 中,使用 setResult() 方法设置回传数据,并调用 finish() 关闭当前 Activity

1
2
3
4
5
6
7
8
9
10
11
// 目标 Activity (B)
Intent resultIntent = new Intent();

// 设置回传数据
resultIntent.putExtra("key_result", "Data from ReceiverActivity");

// 设置结果码和数据
setResult(Activity.RESULT_OK, resultIntent);

// 关闭当前 Activity
finish();

步骤 3:在发送方 Activity(A)中接收回传数据

在发送方 Activity 中,重写 onActivityResult() 方法以接收回传数据。

1
2
3
4
5
6
7
8
9
10
11
12
// 发送方 Activity (A)
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);

// 检查请求码和结果码
if (requestCode == 1 && resultCode == Activity.RESULT_OK) {
// 获取回传数据
String result = data.getStringExtra("key_result");
System.out.println("Received result: " + result);
}
}

两Activity跳转动画 overridePendingTransition()

用于自定义活动(Activity)切换时的动画效果。

这个方法通常在调用 startActivity()finish() 之后使用,以便在活动之间进行更流畅的过渡。

两个Activity 之间实现跳转的动画:
overridePendingTransition()

创建资源文件夹
res -> New -> Android Resource Directory

anim -> New -> Android Resource File
Root element:scale缩放 alpha渐变 translate平移 rotate旋转

enter exit popEnter popExit

1
2
3
4
5
<?xml version="1.0" encoding="utf-8"?>
<translate xmlns:android="http://schemas.android.com/apk/res/android"
android:fromXDelta="100%"
android:toXDelta="0%"
android:duration = "500" />

给 activity 添加切换动画
overridePendingTransition(R.anim.enter from rightR.anim.exit to left)

Service

Service 生命周期

  • onCreate():这个方法在Service被创建时调用,只会在整个Service的生命周期中被调用一次,可以在这里进行一些初始化操作。(创建服务对象)
  • onBind():当另一个组件通过调用bindService()与服务绑定时,系统将调用此方法,并且需要返回一个Binder实例。 不需要返回null。
  • onStartCommand():此方法在Service被启动时调用。(服务创建完毕,启动服务,真正完成服务)
  • onDestroy():当Service被销毁时调用,用于执行清理工作。(服务被销毁)

调用者调用的方法来管理 Service 的生命周期:

  • startService()
  • stopService()
  • bindService()
  • unbindService()

使用步骤

(1) 第一步:自定义 Service

创建一个自定义MyService 类继承 Service
重写父类的 onCreate()、onBind()、onStartCommand()、onDestroy()方法。

(2)第二步:在清单文件 AndroidManifest.xml 里面注册该服务组件

android:exported="true"声明可以被外部应用调用或启动

1
2
<service android:name=".service.MyService"
android:exported="true"></service>

(3)第三步:启动/关闭服务

Service的启动方式主要有两种,分别是 startService() 和 bindService()

  1. startService & stopService
    onCreate – onStartCommand – onDestroy

  2. bindService & unbindService
    onCreate – onBind – onUnbind – onDestroy

    • onCreate -> onBind 这个时候调用者和 Service 绑定在一起
    • 调用者调用 -> unbindService 方法或者调用者 Context不存在了 (如Activity被finish了),Service就会调用onUnbind- >onDestroy。
      这里所谓的绑定在一起就是说两者共存亡了
  3. startService bindService & unbindService stopService
    onCreate – onStartCommand – onBind – onUnbind – onDestroy


1、startService方法
1
2
3
4
5
6
7
//开启
Intent startIntent = new Intent(MainActivity.this,MyService.class);
startService(startIntent);

//关闭
Intent stopIntent = new Intent(MainActivity.this,MyService.class);
stopService(stopIntent);
2、 bindService 方法

//Activity和Service进行通信
需要在 onBind 方法返回一个 Binder 实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class MyService : Service() {
private val binder = MyBinder()

inner class MyBinder : Binder() {
fun doSomething(): String {
return "Hello from MyService!"
}
}

//onBind方法回调
override fun onBind(intent: Intent): IBinder {
return binder//-----------------
}
}

在 MainActivity 中创建连接服务的组件 ServiceConnection 类,重写 onServiceConnected()、 onServiceDisconnected() 方法。
通过调用 MyBinder 类中的 public 方法来实现 Activity 与 Service 的联系。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class MainActivity : AppCompatActivity() {
//创建ServiceConnection来保持Activity和Service的连接
private lateinit var serviceConnection: ServiceConnection

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)

serviceConnection = object : ServiceConnection {
override fun onServiceConnected(name: ComponentName, service: IBinder) {
// 获取服务实例
val myService = MyService.MyBinder(service)
// 调用服务的方法
myService.doSomething()
}

override fun onServiceDisconnected(name: ComponentName) {
// 服务断开连接时的操作
}
}

// 启动服务
val intent = Intent(this, MyService::class.java)
bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE)
}

override fun onDestroy() {
super.onDestroy()
// 解除绑定服务
unbindService(serviceConnection)
}
}

使用场景

Service是Android中的一个四大组件,用于在后台执行长时间运行的操作处理与应用程序无关的任务
Service适用于以下场景:

  1. 后台任务处理:Service适合用于执行一些需要长时间运行的后台任务,例如下载文件、上传数据、定期更新应用内数据等操作。
  2. 音乐播放 :在音乐播放器应用中,通常会使用 Service 来播放音乐,即使用户切换到其他应用或锁屏,音乐播放仍在后台继续。
  3. 网络连接:在应用中需要进行网络请求或保持长时间的网络连接时,可以使用Service来处理网络通信。
  4. 后台定位服务:需要持续获取用户位置信息或实现位置追踪功能时,可以使用Service在后台获取位置信息。
  5. 消息推送服务:用于接收后端推送的消息通知,弹出通知栏通知或执行相关逻辑。
  6. 执行定时任务:Service可以用来执行定时任务,例如定时检查更新、发送定时提醒等。
  7. 处理离线数据同步:当需要在后台处理离线数据同步或数据备份时,可以使用Service来处理相关任务。

总的来说,Service适用于需要在后台长时间执行操作或处理与用户界面无关的任务的场景。通过Service,可以使应用程序具有后台运行、持续运行的能力,提高用户体验和应用功能的完整性。

启动方式选择

如果你只是想要启动一个后台服务长期进行某项任务,那么使用 startService 便可以了。
如果你还想要与正在运行的 Service 进行交互,那么有两种方法:

  • 一种是使用 broadcast (如果交流较为频繁,容易造成性能上的问题)
  • 另一种是使用 bindService (需要 startService 和 bindService 一起使用了)

Service保活

“Service 保活的常见方案包括:

  1. 前台服务:通过 startForeground() 显示通知,优先级最高;
  2. **返回 START_STICKY**:系统会尝试重启,但不保证及时性;
  3. 双进程守护:两个 Service 互相唤醒(需注意 Android 8+ 限制);
  4. JobScheduler/WorkManager:利用系统调度机制平衡保活和功耗;
    其他方案如 1像素 Activity 或广播监听,因系统限制已不推荐。
    实际开发中,建议优先使用前台服务或 WorkManager定期检查并重启 Service,兼顾合规性和用户体验。”

在 Android 中,Service 保活(防止被系统杀死或提高存活率)是一个常见的需求,尤其是在需要长时间运行后台任务的场景(如音乐播放、即时通讯、位置追踪等)。以下是常见的 Service 保活方案,按优先级和适用性分类:


1. 前台服务(Foreground Service)

  • 通过 startForeground() 将 Service 设置为前台服务,并显示一个持续的通知(Notification)。

  • 系统会优先保留前台服务,降低被回收的概率。

  • Service的等级:

    • 前台进程
    • 可见进程
    • 次要服务进程
    • 后台进程
    • 空进程 —关闭应用后,没有清理缓存
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class MyService extends Service {
@Override
public void onCreate() {
super.onCreate();
// 创建通知渠道(Android 8.0+ 必须)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel channel = new NotificationChannel(
"channel_id", "Foreground Service", NotificationManager.IMPORTANCE_LOW
);
getSystemService(NotificationManager.class).createNotificationChannel(channel);
}

// 创建通知
Notification notification = new NotificationCompat.Builder(this, "channel_id")
.setContentTitle("服务运行中")
.setContentText("正在执行后台任务...")
.setSmallIcon(R.drawable.ic_notification)
.build();

// 设置为前台服务(ID 必须非 0)
startForeground(1, notification);
}

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
// 执行后台任务
return START_STICKY; // 被杀死后尝试重启
}
}

注意事项

  • 必须提供通知:用户可见,否则会抛出异常。
  • Android 9+ 限制:后台启动前台服务需申请 FOREGROUND_SERVICE 权限。
  • Android 12+ 限制:前台服务必须明确声明 <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />

2. 返回 START_STICKY

原理

  • onStartCommand() 中返回 START_STICKY,系统会在 Service 被杀死后尝试重新创建(但 不保证立即执行)。
  • 适合非紧急任务(如定时同步)。
1
2
3
4
5
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
// 执行任务...
return START_STICKY; // 被杀死后尝试重启
}

局限性

  • 不保证及时恢复:系统资源紧张时可能延迟重启。
  • Intent 为 null:重启时 intent 参数可能为 null,需处理数据丢失问题。

3. 双进程守护(AIDL 或 JobService)

  • 启动两个 Service 运行在不同进程,互相监听并唤醒(通过 bindService() + BinderJobScheduler)。
  • 即使一个进程被杀死,另一个进程可以重新启动它。
1
2
3
4
5
6
7
8
9
10
11
12
13
// 进程1的 Service
public class ServiceA extends Service {
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
// 监听进程2的状态,如果被杀则重启
return START_STICKY;
}
}

// 进程2的 Service
public class ServiceB extends Service {
// 类似实现...
}

注意事项

  • Android 8+ 限制:后台执行限制严格,频繁互相唤醒可能失效。
  • 功耗问题:可能增加电池消耗,需谨慎使用。

4. 利用系统机制(JobScheduler / WorkManager)

原理

  • 使用 JobScheduler(API 21+)或 WorkManager(兼容库)在合适时机重启服务。
  • 系统会优化任务执行时机,平衡性能和保活需求。

代码示例(JobScheduler)

1
2
3
4
5
6
JobInfo jobInfo = new JobInfo.Builder(1, new ComponentName(this, MyJobService.class))
.setPersisted(true) // 设备重启后依然有效
.setPeriodic(15 * 60 * 1000) // 15分钟轮询一次
.build();
JobScheduler scheduler = (JobScheduler) getSystemService(JOB_SCHEDULER_SERVICE);
scheduler.schedule(jobInfo);

适用场景适合非实时任务(如数据同步、日志上传)。


5. 其他方案(谨慎使用)

a. 监听系统广播
  • 注册静态广播(如 ACTION_SCREEN_ONACTION_BOOT_COMPLETED)唤醒 Service。
  • 限制:Android 8+ 禁止大部分静态广播注册。
b. 1像素 Activity 保活
  • 在锁屏时启动一个透明的 1px Activity 提升进程优先级。
  • 限制:用户可能察觉,且 Android 10+ 对后台 Activity 限制严格。
c. 账户同步机制
  • 利用 Android 的账户同步服务(AccountManager)定期唤醒。
  • 限制:需要用户添加账户,体验较差。

6. 保活方案对比

方案 有效性 系统兼容性 用户感知 推荐程度
前台服务 ⭐⭐⭐⭐⭐ Android 5+ 有通知 ⭐⭐⭐⭐
START_STICKY ⭐⭐ 所有版本 ⭐⭐
双进程守护 ⭐⭐⭐ Android 5~7 ⭐⭐
JobScheduler/WorkManager ⭐⭐⭐ Android 5+ ⭐⭐⭐⭐
1像素 Activity Android 7 及以下 可能察觉

BroadcastReceiver

BroadcastReceiver即广播,是一个全局的监听器,属于Android四大组件之一
Android 广播分为两个角色:广播发送者、广播接收者
作用是监听 / 接收 应用 App 发出的广播消息,并做出响应
一般应用场景如下

Android不同组件间的通信(含 :应用内 / 不同应用之间)
多线程通信
与 Android 系统在特定情况下的通信(如电话呼入时、网络可用时)

原理

Android中的广播使用了设计模式中的观察者模式:基于消息的发布 / 订阅事件模型
模型中有3个角色:

  • a. 消息订阅者(广播接收者)
  • b. 消息发布者(广播发布者)
  • c. 消息中心(AMS,即Activity Manager Service)

原理概述

  • a. 广播接收者通过Binder机制在AMS注册
  • b. 广播发送者通过Binder机制向AMS发送广播
  • c. AMS根据广播发送者要求,在已注册的列表中寻找合适的广播接收者(寻找依据 IntentFilter/Permission)
  • d. AMS将广播发送到合适的广播接收者的消息循环队列中
  • e. 广播接收者通过消息循环拿到此广播,并回调onReceive();
    特别注意广播发送者和广播接收者的执行是异步的,即广播发送者不关心有无接收者接收,也不确定接收者何时才能接收到

使用步骤

第一步:自定义广播接收者 BroadcastReceiver

  1. 继承 BroadcastReceivre 基类

  2. 必须复写抽象方法 onReceive() 方法

    • 广播接收器接收到相应广播后,会自动回调 onReceive() 方法

    • 一般情况下,onReceive方法会涉及与其他组件之间的交互,如发送 Notification 、 启动 Service 等

    • 默认情况下,广播接收器运行在 UI 线程,因此,onReceive()方法不能执行耗时操作,否则将导致ANR

//点击通知栏按钮,实现切换(给通知栏添加点击事件)

1
2
3
4
5
6
7
8
9
class MyBroadcastReceiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
if (intent?.action == "com.example.MY_ACTION") {
// 在接收到指定Action的广播时执行操作
val message = intent.getStringExtra("message")
Toast.makeText(context, "Received broadcast: $message", Toast.LENGTH_SHORT).show()
}
}
}

注册广播接收器

a. 静态注册

需要在 AndroidManifest.xml 文件中注册这个 BroadcastReceiver ,并设置接收的 Action 和相关的权限:
当此 App首次启动时,系统会自动实例化 MyBroadcastReceiver 类,并注册到系统中

1
2
3
4
5
<receiver android:name=".MyBroadcastReceiver">
<intent-filter>
<action android:name="com.example.MY_ACTION" />
</intent-filter>
</receiver>

b. 动态注册

注册方式:在代码中调用 Context.registerReceiver() 方法
注销方式:在代码中调用 Context.unregisterReceiver() 方法

ContentProvider

ContentProvider其实就是应用间的“数据共享桥”,是管理跨应用访问的组件,通过URI来标识数据。

特点:

数据共享:ContentProvider可以让不同应用之间共享数据。比如,通讯录App可以通过ContentProvider将联系人信息共享给其他App。
统一接口:ContentProvider提供了一套标准化的接口(比如query()、insert()、update()、delete()),其他应用可以通过这些接口访问数据。
数据安全:ContentProvider可以通过权限控制来保护数据。比如,你可以设置只有特定应用才能访问你的数据。

FileProvider 概述

FileProvider 是一个 ContentProvider 子类,用于共享应用内部的文件或文件夹,并提供安全的访问权限。

在 Android 应用中,由于Android 7.0及以上版本对文件访问权限进行了限制,直接使用file:// Uri 访问文件可能会导致 FileUriExposedException 异常。
FileProvider生成的 Uri 会 以 content:// 的形式分享给其他 app 使用。
这个 Uri 可以被其他应用程序访问,而不需要直接暴露文件的真实路径,从而提高了文件共享的安全性。

FileProvider的功能包括:

  1. 提供安全的文件共享 : FileProvider 可以为应用内部的文件或文件夹生成一个content:// Uri,其他应用可以通过这个 Uri 来访问应用内部的文件,而无需暴露文件的真实路径。
  2. 对文件访问权限进行控制 : FileProvider 可以根据配置的路径对文件的访问权限进行控制,只有配置的路径下的文件可以被共享。
  3. 适配 Android 7.0 及以上版本 : FileProvider 是为了适配 Android 7.0 及以上版本对文件访问权限的限制而设计的,可以避免使用file:// Uri 导致的 FileUriExposedException 异常。

总的来说, FileProvider 提供了一种安全且灵活的方式来共享应用内部的文件,同时保护文件的访问权限和安全性。


FileProvider 属于 Android 开发中的内容提供器(ContentProvider)部分。
内容提供器是Android四大组件之一,用于管理应用程序的数据并提供数据的访问和共享。

FileProvider 作为内容提供器的一种特殊实现,用于安全地共享文件并控制文件的访问权限。
在Android开发中,开发者可以使用FileProvider来共享内部文件,以便在应用程序之间安全地传输和访问文件。

使用 content:// Uri 的工作流程:
A仅仅给B分享了 content:// Uri ,具体的文件读取是由内容/数据提供方(App A)来完成的,App B只能去问App A拿数据。
1、A共享ContentURI给B
2、B拿着这个URI找A要数据
3、A读取文件中的数据给B

配置

1.声明 FileProvider

在 AndroidManifest.xml 的 application 下面增加 provider

1
2
3
4
5
6
7
8
9
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="app包名.fileProvider"
android:grantUriPermissions="true"
android:exported="false">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>

android:authorities 需要是唯一的,使用唯一的 android:authorities 与 xml 指定的共享目录进行关联。
一般使用app包名.fileProvider,当然这个名字也可以修改,但是必须唯一。
也可以直接写成 android:authorities=”${applicationId}.fileProvider”

  • android:name 指定 Provider 所在的位置
    直接使用自带的就行,直接输入privoder会自动出现提示。
    “androidx.core.content.FileProvider”
  • android:authorities
    相当于一个用于认证的暗号,在分享文件生成 Uri 时,会通过它的值生成对应的 Uri
    值是一个域名,一般格式为<包名>.fileprovider。
    例如:”com.example.littlepainter.fileprovider”
  • android:grantUriPermissions
    设置为 tru e,这样就能授权接收端的 app 临时访问权限了
  • android:exported
    设置为 false , FileProvider 不需要公开
2.创建一个 XML 文件用于定义共享文件的路径。

在 res/xml 目录下创建一个 file_paths.xml 文件:

<paths> 必须有1个或多个子标签,每个子标签代表要请求的私有文件目录。
不同的子标签代表不同的目录类型。

1
2
3
4
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<files-path name="my_files" path="." />
</paths>
  • <root-path>:设备根目录/
  • <files-path>:代表内部存储的files目录,与Context.getFilesDir()获取的路径对应。
  • <cache-path>:代表内部存储的cache目录,与Context.getCacheDir()获取的路径对应。
  • <external-path>:代表外部存储(sdcard)的cache目录,与Environment.getExternalStorageDirectory()获取的路径对应。
  • <external-files-path>:代表app的外部存储的根目录,与Context.getExternalFilesDir(null)获取的路径对应。
  • <external-cache-path>:代表app外部缓存区域的根目录,与Context.getExternalCacheDir()获取的路径对应。
  • <external-media-path>:代表app外部存储媒体区域的根目录,与Context.getExternalMediaDirs()获取的路径对应。
3.在应用中使用 FileProvider 来获取共享文件的 URI :
  • FileProvider.getUriForFile 用于获取一个用于访问文件的 content:// Uri

为了让其他 app 使用 Content Uri,我们的 app 必须提前生成 Uri

参数:

  1. context:Context 对象,通常为当前 Activity 或应用程序的上下文。这个参数用于获取 FileProvider 的 authority 和配置信息。
  2. authority:String 类型,是 FileProvider 在 AndroidManifest.xml 文件中配置的 authority 属性的值,用于唯一标识 FileProvider。这个值必须与配置文件中的一致。
  3. file:File 对象,表示要共享的文件。这个文件必须是应用内部存储的文件,不能是外部存储的文件。
1
2
3
4
5
val imagePath = File(filesDir, "image.jpg")
val imageUri = FileProvider.getUriForFile(
this,
"com.example.myapp.fileprovider",
imagePath)
4.授权临时权限

如果需要在应用中共享文件给其他应用,需要添加临时的读取权限:

1
2
3
4
5
6
val shareIntent = Intent()
shareIntent.action = Intent.ACTION_SEND
shareIntent.putExtra(Intent.EXTRA_STREAM, imageUri)
shareIntent.type = "image/jpeg"
//启动分享操作
startActivity(Intent.createChooser(shareIntent, "Share image via"))

通过以上步骤,就可以配置 FileProvider 来共享文件和文件访问权限。
这样可以更安全地共享文件,而不会暴露应用的私有文件路径。

面试

Activity生命周期相关

两个Activity相互切换,生命周期变化(字节/小红书)

A -> B

1
2
3
Activity A: onPause() → 
Activity B: onCreate() → onStart() → onResume() →
Activity A: onStop()

B -> A

1
2
3
Activity B: onPause() → 
Activity A: onRestart() → onStart() → onResume() →
Activity B: onStop() → onDestroy()

1.9 说下切换横竖屏时Activity的生命周期?(享学)

在 Android 开发中,当 切换横竖屏(屏幕方向改变) 时,Activity 的生命周期会经历一系列变化。默认情况下,系统会 销毁并重建当前 Activity,以重新加载适配新屏幕方向的布局资源。以下是完整的生命周期调用顺序和关键细节:


1. 默认情况下的生命周期流程(未配置 configChanges

当屏幕方向改变时(例如竖屏 → 横屏),Activity 的生命周期如下:

  1. onPause() Activity 失去焦点,准备进入后台。

  2. onStop() Activity 完全不可见。

  3. onDestroy() Activity 被销毁,释放资源。

  4. onCreate() 重新创建 Activity,加载横屏布局(如 layout-land 中的资源)。

  5. onStart() Activity 可见但未进入前台。

  6. onResume() Activity 恢复交互状态,获得焦点。

关键点:

  • 系统会调用 onSaveInstanceState()onStop() 之前保存临时数据(如文本框内容),并在 onCreate()onRestoreInstanceState()恢复
  • onRestoreInstanceState()onStart() 之后、onResume() 之前执行。
  • 横竖屏切换会触发完整的销毁和重建过程。

2. 手动配置 configChanges 避免重建

如果 Activity 在 AndroidManifest.xml 中配置了 android:configChanges="orientation|screenSize|screenLayout",系统将不会重建 Activity,而是触发 onConfigurationChanged() 方法:

1
2
<activity android:name=".MyActivity"
android:configChanges="orientation|screenSize|screenLayout"/>

此时生命周期变化:

  1. onConfigurationChanged(Configuration newConfig)
    • 开发者需在此方法中手动处理屏幕方向变化(例如调整布局)。

注意:

  • 从 Android 3.2(API 13)开始,必须包含 screenSize,因为屏幕方向改变时物理尺寸也可能变化。
  • 屏幕旋转不仅影响方向,还会影响 screenSize(因为横竖屏的宽高值互换)。
  • 其他生命周期方法(如 onPause())不会被调用,Activity 保持运行状态。
  • 只关心屏幕旋转,可以不加 screenLayout,但加上它可以更全面地防止因其他屏幕布局变化导致的 Activity 重建。
1
2
3
4
5
6
7
8
9
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
// 横屏布局调整
} else {
// 竖屏布局调整
}
}

3. 相关面试问题扩展

  • 为什么默认要重建 Activity?
    系统需要重新加载对应方向的布局资源(如 res/layout-land/res/layout-port/)。

  • 如何保存数据避免丢失?

    • 使用 onSaveInstanceState() 保存简单数据。
    • 对于复杂数据(如网络请求结果),通过 ViewModel + onRetainCustomNonConfigurationInstance() 保留。
  • onRestoreInstanceState()onCreate() 中恢复数据的区别?

    • onCreate()Bundle 参数可能为 null(首次创建时)需额外判空逻辑,而 onRestoreInstanceState() Bundle 一定非空,专门用于状态恢复,只在数据恢复时被调用。

总结答案(面试回答模板)

“在默认情况下,切换横竖屏会导致 Activity 经历销毁和重建:先调用 onPause()onStop()onDestroy(),再通过 onCreate() 重新创建并走完 onStart()onResume()。系统会在销毁前通过 onSaveInstanceState() 保存数据,并在重建后恢复。如果配置了 configChanges 属性,则不会重建,而是触发 onConfigurationChanged(),由开发者手动处理布局调整。”

这样的回答既清晰又覆盖了常见追问点。

1. 解释Android Activity的生命周期方法及其调用顺序

Android Activity的生命周期方法包括以下几个主要方法,调用顺序如下:

  1. **onCreate()**:Activity首次创建时调用,通常用于初始化布局、绑定数据等操作。
  2. **onStart()**:Activity即将可见时调用,此时Activity已进入“可见但不可交互”状态。
  3. **onResume()**:Activity进入前台并可与用户交互时调用。
  4. **onPause()**:Activity部分被遮挡(如弹出对话框)或即将进入后台时调用。
  5. **onStop()**:Activity完全不可见时调用。
  6. **onDestroy()**:Activity被销毁前调用,可能是用户主动关闭或系统资源回收。
  7. **onRestart()**:Activity从停止状态重新启动时调用,之后会调用onStart()

生命周期方法的调用顺序可以总结为:

  • 启动:onCreate() -> onStart() -> onResume()
  • 返回后台:onPause() -> onStop()
  • 重新回到前台:onRestart() -> onStart() -> onResume()
  • 销毁:onPause() -> onStop() -> onDestroy()

2. 在Activity的onCreate()onStart()方法中,分别应该执行哪些操作?

  • **onCreate()**:

    • 初始化布局:调用setContentView()设置Activity的UI。
    • 绑定数据:初始化变量、加载数据、绑定适配器等。
    • 初始化组件:如设置监听器、初始化Fragment等。
    • 恢复状态:从Bundle中恢复Activity的状态(如果有)。
  • **onStart()**:

    • 注册监听器:如广播接收器、位置监听器等。
    • 启动后台任务:如加载数据、初始化动画等。
    • 更新UI:确保UI与当前数据状态一致。

启动模式相关

1.2 LaunchMode 的应用场景?(享学)

LaunchMode 有四种,分别为 Standard, SingleTop, SingleTask 和 SingleInstance

下面说一下具体使用场景:

  • Standard: Standard模式是系统默认的启动模式,一般我们 app 中大部分页面都是由该模式的页面构成的
    • 比较常见的场景是:社交应用中,点击查看用户A信息->查看用户A粉丝 ->在粉丝中挑选查看用户B信息->查看用户A粉丝… 这种情况下一般我们需要保留用户操作 Activity栈的页面所 有执行顺序
  • SingleTop: SingleTop 模式一般常见于社交应用中的通知栏行为功能,例如:App 用户收到几条好友请求的推送消息,需要用户点击推送通知进入到请求者个人信息页,将信息页设置为 SingleTop 模式就可以增强复用性。
  • SingleTask: SingleTask 模式一般用作应用的首页,例如浏览器主页, 用户可能从多个应用启动浏览器,但主界面仅仅启动一次,其余情况都会走onNewIntent,并且会清空主界面上面的其他页面。
  • SingleInstance: SingleInstance 模式常应用于独立栈操作的应用,如闹钟的提醒页面,当你在A应用中看视频时,闹钟响了,你点击闹钟提醒通知后进入提醒详情页面,然后点击返回就再次回到A的视频页面,这样就不会过多干扰到用户先前的操作了。

请说一说Activity的四种启动模式(面字节)

在Android开发中,Activity的启动模式决定了Activity的启动方式和行为。主要有以下四种启动模式:

1. standard(标准模式)

  • 默认模式:每次启动Activity都会创建一个新的实例,即使该Activity已经存在。
  • 特点:适用于大多数场景,但可能导致同一个Activity的多个实例存在。
  • 示例:启动Activity A,再启动Activity A,会创建两个A的实例。

适用于大多数普通页面(如新闻详情页、表单提交页),每次打开都是独立实例。
不适用于 需要复用实例的场景(如主页多次跳转可能堆积相同 Activity)。

2. singleTop(栈顶复用)

  • 栈顶复用:如果目标Activity已经在栈顶,则不会创建新实例,而是调用onNewIntent()方法。
  • 特点:避免栈顶重复创建相同Activity。
  • 示例:栈中有A、B,B在栈顶,启动B时不会创建新实例,而是复用栈顶的B。

防止快速点击重复打开同一页面(如支付按钮防抖)。
通知栏点击跳转,避免重复打开相同的 Activity。

3. singleTask(栈内单例)

  • 栈内复用:系统会检查任务栈中是否已存在该Activity的实例,如果存在,则将其上方的所有Activity出栈,并调用onNewIntent()方法;如果不存在,则创建新实例。
  • 特点:确保一个任务栈中只有一个该Activity的实例。
  • 示例:栈中有A、B、C,启动B时,会移除C,复用B并调用onNewIntent()

App 主页(MainActivity):确保只有一个主页实例,并清除其他页面。
登录页:跳转登录页时清空其他页面,避免返回时回到未登录状态。

4. singleInstance(全局单例)

  • 独立栈:该Activity会单独运行在一个新的任务栈中,且该栈中只有这一个Activity。
  • 特点:适用于需要独立运行的Activity,如拨号界面。
  • 示例:启动Activity D,它会运行在一个新的任务栈中,且该栈中只有D。

系统级独立功能(如拨号界面、相机)。
普通 App 慎用,可能导致任务栈混乱。

总结

  • standard:每次启动都创建新实例。
  • singleTop:栈顶复用,避免栈顶重复创建。
  • singleTask:栈内复用,确保栈中只有一个实例。
  • singleInstance:独立栈,只包含一个Activity。

根据应用需求选择合适的启动模式,能有效管理Activity的实例和任务栈。

onNewIntent()

onNewIntent() 是 Android 中 Activity 的一个生命周期方法,用于处理 Activity 被复用时的新 Intent 数据。

帮助开发者避免重复创建 Activity 实例,开发者可以获取新的 Intent 数据,并及时更新 UI 或处理新的逻辑。它在某些特定的启动模式下(如 singleTopsingleTask)会被调用。

3. 如何保存和恢复Activity的状态?

  • 保存状态

    • onSaveInstanceState(Bundle outState)方法中,将需要保存的数据(如用户输入、临时状态)存入Bundle对象。

    • 示例:

      1
      2
      3
      4
      5
      @Override
      protected void onSaveInstanceState(Bundle outState) {
      super.onSaveInstanceState(outState);
      outState.putString("key", "value"); // 保存数据
      }
  • 恢复状态

    • onCreate(Bundle savedInstanceState)onRestoreInstanceState(Bundle savedInstanceState)中,从Bundle中恢复数据。

    • 示例:

      1
      2
      3
      4
      5
      6
      7
      @Override
      protected void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      if (savedInstanceState != null) {
      String value = savedInstanceState.getString("key"); // 恢复数据
      }
      }

4. 什么是Fragment的生命周期?它与Activity的生命周期有何关联?

  • Fragment的生命周期
    Fragment的生命周期与Activity类似,但更加复杂,主要方法包括:

    • onAttach():Fragment与Activity关联时调用。
    • onCreate():Fragment创建时调用。
    • onCreateView():创建Fragment的UI时调用。
    • onViewCreated():UI创建完成后调用。
    • onStart():Fragment可见时调用。
    • onResume():Fragment可交互时调用。
    • onPause():Fragment部分遮挡或即将进入后台时调用。
    • onStop():Fragment不可见时调用。
    • onDestroyView():Fragment的UI被销毁时调用。
    • onDestroy():Fragment被销毁时调用。
    • onDetach():Fragment与Activity解除关联时调用。
  • 与Activity生命周期的关联

    • Fragment的生命周期受其宿主Activity的生命周期影响。
    • 例如,当Activity调用onPause()时,其内部的Fragment也会调用onPause()
    • Fragment的生命周期方法通常会在Activity的对应方法之后调用。

5. 如何处理Activity被系统销毁并重新创建的情况?

当系统资源不足时,Activity可能会被销毁并在用户返回时重新创建。为了确保用户体验一致,需要正确处理以下情况:

  • 保存状态

    • onSaveInstanceState(Bundle outState)中保存临时数据(如用户输入、滚动位置等)。

    • 将重要的数据保存到Bundle对象中。这通常在Activity即将被销毁时调用。

    • 示例:

      1
      2
      3
      4
      5
      @Override
      protected void onSaveInstanceState(Bundle outState) {
      super.onSaveInstanceState(outState);
      outState.putString("inputText", editText.getText().toString());
      }
  • 恢复状态

    • onCreate(Bundle savedInstanceState)onRestoreInstanceState(Bundle savedInstanceState)中恢复数据。

    • onRestoreInstanceState()方法中,恢复之前保存的数据。

      • onCreate()方法中也可以恢复数据,但当你在onCreate()中恢复数据时,Bundle可能为null,所以优先在onRestoreInstanceState()中处理。
    • 示例:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      @Override
      protected void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.activity_main);
      if (savedInstanceState != null) {
      String inputText = savedInstanceState.getString("inputText");
      editText.setText(inputText);
      }
      }

      @Override
      protected void onRestoreInstanceState(Bundle savedInstanceState) {
      super.onRestoreInstanceState(savedInstanceState);
      myData = savedInstanceState.getString("key");
      }
  • 使用ViewModel

    • 对于更复杂的数据(如网络请求结果),可以使用ViewModel来保存数据。ViewModel在Activity被销毁并重新创建时不会被销毁。

    • 示例:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      public class MyViewModel extends ViewModel {
      private MutableLiveData<String> data = new MutableLiveData<>();
      public LiveData<String> getData() {
      return data;
      }
      public void setData(String value) {
      data.setValue(value);
      }
      }
  • 处理配置变更

    • 如果Activity因配置变更(如屏幕旋转)被销毁并重新创建,可以通过在AndroidManifest.xml中设置android:configChanges来避免重建。

    • 示例:

      1
      2
      <activity android:name=".MainActivity"
      android:configChanges="orientation|screenSize"/>
      • 这部分定义了当前Activity所处理的配置变化。当系统的配置发生变化,如设备的方向改变(从竖屏切换到横屏),或屏幕尺寸发生变化时,Activity通常会被销毁并重新创建,以适应新的配置

      • 通过指定android:configChanges属性,开发者可以告知系统在这些配置变化发生时,不要自动重新创建Activity,而是调用onConfigurationChanged()方法。这意味着开发者需要手动处理这些变化以保持Activity的状态,比如调整UI布局。

1.1 Activity 与 Fragment 之间常见的几种通信方式?(享学)

viewModel 做数据管理,activity 和 fragment 公用同个 viewModel 实现数据传递

1.4 对于 Context,你了解多少?

Context也叫上下文,是有关应用程序环境的全局信息的接 口。这是一个抽象类, 它允许访问特定于应用程序的资源和 类,以及对应用程序级操作的调用,比如启动活动,发送广 播和接收意图等;

(小红书)能不能直接从一个Service中启动一个Activity?

是的,可以直接从一个 Service 中启动一个 Activity,但需要注意以下几点:

1. 如何从 Service 中启动 Activity

Service 中,可以通过 Intent 启动 Activity,就像在 Activity 中启动另一个 Activity 一样。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class MyService extends Service {
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
// 创建 Intent 启动目标 Activity
Intent activityIntent = new Intent(this, TargetActivity.class);

// 设置 FLAG_ACTIVITY_NEW_TASK 标志
activityIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

// 启动 Activity
startActivity(activityIntent);

return START_STICKY;
}

@Override
public IBinder onBind(Intent intent) {
return null; // 本例中不需要绑定
}
}

2. 必须设置 FLAG_ACTIVITY_NEW_TASK 标志

由于 Service 没有任务栈(Task Stack),启动 Activity 时必须设置 FLAG_ACTIVITY_NEW_TASK 标志,以确保 Activity 在一个新的任务栈中启动。

  • Activity 必须运行在一个任务栈中。
  • Service 没有任务栈,因此需要显式指定 FLAG_ACTIVITY_NEW_TASK 来创建一个新的任务栈。

3. 注意事项

  1. 用户体验:从 Service 中直接启动 Activity 可能会打断用户当前的操作,影响用户体验。建议仅在必要时使用(如显示紧急通知)。

  2. 权限:如果目标 Activity 是其他应用的组件,可能需要声明相应的权限。

  3. 后台限制:在 Android 8.0(API 级别 26)及以上版本中,后台服务启动 Activity 会受到限制。建议使用通知(Notification)来引导用户启动 Activity


4. 替代方案:通过通知启动 Activity

  • 不打断用户
    • 直接从 Service 中启动 Activity 可能会突然打断用户当前的操作(如正在玩游戏或观看视频),影响用户体验。
    • 通过通知启动 Activity,用户可以选择在合适的时间点击通知,避免不必要的干扰。
  • 用户控制权
    • 通知让用户有更多的控制权,用户可以选择是否点击通知来启动 Activity,而不是被迫接受。

在 Android 8.0 及以上版本中,推荐通过通知(Notification)来启动 Activity,而不是直接从 Service 中启动。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class MyService extends Service {
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
// 创建 PendingIntent 启动目标 Activity
Intent activityIntent = new Intent(this, TargetActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, activityIntent, PendingIntent.FLAG_UPDATE_CURRENT);

// 创建通知
Notification notification = new NotificationCompat.Builder(this, "channel_id")
.setContentTitle("Title")
.setContentText("Message")
.setSmallIcon(R.drawable.ic_notification)
.setContentIntent(pendingIntent) // 设置点击通知后的行为
.setAutoCancel(true) // 点击后自动取消通知
.build();

// 显示通知
NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.notify(1, notification);

return START_STICKY;
}

@Override
public IBinder onBind(Intent intent) {
return null; // 本例中不需要绑定
}
}

5. 总结

  • 可以直接从 Service 中启动 Activity,但必须设置 FLAG_ACTIVITY_NEW_TASK 标志。
  • 在 Android 8.0 及以上版本中,建议通过通知(Notification)引导用户启动 Activity,以避免后台限制和提升用户体验。

根据具体需求选择合适的方式即可。

Service相关

1.6 谈一谈startService和bindService的区 别,生命周期以及使用场景?

1、生命周期上的区别

执行startService时,Service会经历onCreate- >onStartCommand。

当执行stopService时,直接调用 onDestroy方法。

调用者如果没有stopService,Service 会一 直在后台运行,下次调用者再起来仍然可以stopService。

执行bindService时,Service会经历onCreate- >onBind。

这个时候调用者和Service绑定在一起。

调用者调用 unbindService方法或者调用者Context不存在了(如Activity 被finish了),Service就会调用onUnbind- >onDestroy。

这里所谓的绑定在一起就是说两者共存亡了。


  • 多次调用startService,该Service只能被创建一次,即该Service 的onCreate方法只会被调用一次。
  • 但是每次调用startService, onStartCommand方法都会被调用。
  • 第一次执行bindService时,onCreate和onBind方法会被调用,但是多次执行bindService时,onCreate和onBind 方法并不会被多次调用,即并不会多次创建服务和绑定服务。

2、调用者如何获取绑定后的Service的方法

onBind回调方法将返回给客户端一个IBinder接口实例, IBinder允许客户端回调服务的方法,比如得到Service运行的状态或其他操作。
我们需要IBinder对象返回具体的 Service对象才能操作,所以说具体的Service对象必须首先实现Binder对象。

1. 创建 Service 类

首先,创建一个 Service 类,并在其中定义你想要调用的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class MyService extends Service {

private final IBinder binder = new LocalBinder();

//-------------内部类-----------
public class LocalBinder extends Binder {
MyService getService() {
return MyService.this;
}
}

@Override
public IBinder onBind(Intent intent) {
return binder;
}

// 你想要调用的方法
public void myMethod() {
// 方法实现
}
}
2. 在 Activity 中绑定 Service

在 Activity 中,你需要绑定 Service,并通过 ServiceConnection 获取 Service 的实例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
public class MyActivity extends AppCompatActivity {

private MyService myService;
private boolean isBound = false;

private ServiceConnection serviceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
MyService.LocalBinder binder = (MyService.LocalBinder) service;
myService = binder.getService();
isBound = true;
}

@Override
public void onServiceDisconnected(ComponentName name) {
isBound = false;
}
};

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

Intent intent = new Intent(this, MyService.class);
bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
}

@Override
protected void onDestroy() {
super.onDestroy();
if (isBound) {
unbindService(serviceConnection);
isBound = false;
}
}

// 调用 Service 中的方法-------------------------
private void callServiceMethod() {
if (isBound) {
myService.myMethod();
}
}
}
3. 调用 Service 方法

onServiceConnected 回调中,你可以通过 myService 实例调用 Service 中的方法。例如,在 callServiceMethod() 方法中调用 myService.myMethod()

4. 解绑 Service

在 Activity 销毁时,记得解绑 Service,以避免内存泄漏。

总结
  • Service 类:定义你想要调用的方法,并通过 Binder 返回 Service 实例。
  • Activity:通过 ServiceConnection 绑定 Service,并在绑定成功后调用 Service 中的方法。
  • 解绑:在 Activity 销毁时解绑 Service。

通过这种方式,你可以在绑定 Service 后调用其方法。

3、既使用startService又使用bindService的情况

1.7 Service如何进行保活?(享学)

BroadCast相关

(小红书)发送广播的方式

在Android中,发送广播(Broadcast)是组件间通信的重要方式,主要通过以下三种方式实现:


1. 普通广播(Normal Broadcast)

  • 特点:异步执行,所有接收者几乎同时接收,效率高但无法被拦截
  • 发送方式
    1
    2
    3
    Intent intent = new Intent("自定义Action");
    intent.putExtra("key", "value"); // 附加数据
    sendBroadcast(intent);
  • 适用场景:无需有序或拦截的全局通知(如系统广播:电量变化、网络状态变更)。

2. 有序广播(Ordered Broadcast)

  • 特点

    • 同步执行,广播按优先级priority)依次传递。
    • 可被拦截(abortBroadcast())或修改数据。
  • 发送方式

    1
    2
    Intent intent = new Intent("自定义Action");
    sendOrderedBroadcast(intent, null); // 第二个参数为权限字符串(可选)
  • 接收端配置:在AndroidManifest.xml或代码中为BroadcastReceiver设置优先级:

    1
    2
    3
    <intent-filter android:priority="100">
    <action android:name="自定义Action" />
    </intent-filter>
  • 适用场景:需要控制广播传递顺序的场景(如短信拦截)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 发送有序广播
Intent intent = new Intent("com.example.ORDERED_ACTION");
sendOrderedBroadcast(intent, null);

// 接收端
BroadcastReceiver receiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (isOrderedBroadcast()) {
abortBroadcast(); // 拦截广播
}
}
};
IntentFilter filter = new IntentFilter("com.example.ORDERED_ACTION");
filter.setPriority(100); // 设置优先级
registerReceiver(receiver, filter);

3. 本地广播(Local Broadcast)

  • 特点:仅在应用内传递,安全性高,效率优于全局广播。

  • 发送方式(需使用LocalBroadcastManager):

    1
    2
    Intent intent = new Intent("自定义Action");
    LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
  • 适用场景:应用内部组件通信(如Activity与Service交互)。


4. 系统广播(System Broadcast)

  • 特点:由系统发送,如开机、充电等事件。
  • 监听方式:动态注册或静态注册对应Action的BroadcastReceiver

(小红书)广播注册的方式

在Android中,广播(Broadcast)的注册方式主要有两种:静态注册动态注册。以下是它们的详细说明和区别:


1. 静态注册(Manifest-declared)

  • 定义:在AndroidManifest.xml文件中通过<receiver>标签声明广播接收器(BroadcastReceiver)。
  • 特点
    • 应用未启动时也能接收广播(系统会唤醒应用)。
    • 适合监听系统全局事件(如开机完成、网络状态变化等)。
    • 从Android 8.0(API 26)开始,对静态注册的广播增加了限制,大部分隐式广播(非定向广播)无法使用静态注册。
  • 示例
    1
    2
    3
    4
    5
    <receiver android:name=".MyBroadcastReceiver">
    <intent-filter>
    <action android:name="android.intent.action.BOOT_COMPLETED" />
    </intent-filter>
    </receiver>

2. 动态注册(Context-registered)

  • 定义:在代码中通过Context.registerReceiver()动态注册广播接收器。
  • 特点
    • 仅在注册的Context(如Activity/Service)存活时有效。
    • 灵活性强,可随时注册/注销(避免内存泄漏)。
    • 适合监听应用内自定义广播或特定生命周期内的系统广播。
  • 示例
    1
    2
    3
    4
    5
    6
    7
    // 注册广播
    IntentFilter filter = new IntentFilter("com.example.CUSTOM_ACTION");
    MyBroadcastReceiver receiver = new MyBroadcastReceiver();
    registerReceiver(receiver, filter);

    // 注销广播(通常在onDestroy中调用)
    unregisterReceiver(receiver);
特性 静态注册 动态注册
声明位置 AndroidManifest.xml 代码中(如Activity/Service)
生命周期 应用安装后始终有效 随Context销毁而失效
系统限制 Android 8.0+ 对隐式广播受限 无限制
适用场景 系统广播、持久化监听 应用内广播、临时监听

(小红书)发送广播时是如何匹配注册时对应的action data category

当发送广播时,确保 action、data 和 category 的值匹配接收器的注册信息,才能成功接收到广播。

广播的发送和接收是通过 Intent 过滤(Intent Filter) 机制进行匹配的。当发送广播时,系统会根据接收者注册时声明的 IntentFilter 中的 actiondatacategory 进行匹配,只有符合条件的 BroadcastReceiver 才会收到广播。

Donate
  • Copyright: Copyright is owned by the author. For commercial reprints, please contact the author for authorization. For non-commercial reprints, please indicate the source.

扫一扫,分享到微信

微信分享二维码
  • Copyrights © 2023-2025 Annie
  • Visitors: | Views:

嘿嘿 请我吃小蛋糕吧~

支付宝
微信