TelephonyManager(电话管理器)
TelephonyManager (电话管理器)
TelephonyManager
是 Android 提供的一个系统服务类,用于访问与设备电话功能相关的信息和状态,如 SIM 卡信息、网络状态、电话状态等。它常用于获取设备标识(如 IMEI)、网络类型、运营商信息,或监听电话状态变化(如来电、挂断)。结合 Android 动画合集的背景,TelephonyManager
可用于在动画触发时根据电话状态执行特定操作(如暂停动画来电时)。本文将详细介绍 TelephonyManager
的功能、使用方法、权限要求及注意事项。
TelephonyManager 的作用与原理
- 作用:
TelephonyManager
提供对设备电话服务的访问,包括设备信息(如 IMEI、IMSI)、网络信息(如运营商、信号强度)、电话状态(如通话中、闲置)。它还可以注册监听器以实时监控电话状态变化。 - 原理:
TelephonyManager
通过 Android 的电话模块与底层硬件通信,访问 SIM 卡、基带或网络栈数据。部分功能需要特定权限,且高 API 级别(如 API 29+)对敏感信息(如 IMEI)施加了严格限制。 - 应用场景:
- 获取设备唯一标识(如 IMEI,需权限)。
- 检查网络状态(如 4G、5G)以优化动画加载。
- 监听来电状态,暂停动画或音效。
- 显示运营商信息或信号强度。
- 实现基于位置的动画(如根据漫游状态调整 UI)。
权限要求
使用 TelephonyManager
需要在 AndroidManifest.xml
中声明权限,部分功能需动态请求(API 23+):
<!-- 基本电话状态 -->
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<!-- 获取精确位置(可选,基于网络) -->
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<!-- 获取 SIM 卡信息(API 29+ 受限) -->
<uses-permission android:name="android.permission.READ_PHONE_NUMBERS" />
- 动态权限(API 23+):
READ_PHONE_STATE
:读取电话状态、设备标识。ACCESS_COARSE_LOCATION
:基于网络的位置信息。- 使用
ActivityCompat.requestPermissions
请求。 - 注意:API 29+(Android 10)对设备标识(如 IMEI、IMSI)访问受限,非系统应用通常无法获取。
获取 TelephonyManager
通过 Context
获取 TelephonyManager
实例:
import android.content.Context;
import android.telephony.TelephonyManager;
TelephonyManager telephonyManager = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
常用功能与方法
以下是 TelephonyManager
的核心功能和方法:
方法/功能 | 描述 | 权限要求 | API 级别 | 用法示例 |
---|---|---|---|---|
getDeviceId() | 获取设备 IMEI(API 29+ 受限)。 | READ_PHONE_STATE | 1+ (API 29 受限) | String imei = telephonyManager.getDeviceId(); |
getImei() | 获取 IMEI(API 26+,多 SIM 卡支持)。 | READ_PHONE_STATE | 26+ | String imei = telephonyManager.getImei(); |
getSimOperatorName() | 获取 SIM 卡运营商名称。 | 无 | 1+ | String operator = telephonyManager.getSimOperatorName(); |
getNetworkType() | 获取网络类型(如 4G、5G)。 | 无 | 1+ | int networkType = telephonyManager.getNetworkType(); |
getCallState() | 获取电话状态(闲置、响铃、通话中)。 | READ_PHONE_STATE | 1+ | int callState = telephonyManager.getCallState(); |
listen(PhoneStateListener, int) | 注册电话状态监听器(如来电、信号变化)。 | READ_PHONE_STATE | 1+ | telephonyManager.listen(listener, PhoneStateListener.LISTEN_CALL_STATE); |
getLine1Number() | 获取 SIM 卡电话号码(可能为空,API 29+ 受限)。 | READ_PHONE_NUMBERS | 1+ (API 29 受限) | String number = telephonyManager.getLine1Number(); |
getSimState() | 获取 SIM 卡状态(如就绪、缺失)。 | 无 | 1+ | int simState = telephonyManager.getSimState(); |
- 电话状态(
getCallState
): CALL_STATE_IDLE
:空闲。CALL_STATE_RINGING
:响铃。CALL_STATE_OFFHOOK
:通话中。- 网络类型(
getNetworkType
): NETWORK_TYPE_LTE
:4G。NETWORK_TYPE_NR
:5G(API 29+)。NETWORK_TYPE_UNKNOWN
:未知。
完整示例(监听电话状态 + 动画)
以下是一个示例,监听电话状态并在来电时暂停动画,结合 MediaRecorder
录音(前文已介绍)。
import android.Manifest;
import android.content.Context;
import android.content.pm.PackageManager;
import android.media.MediaRecorder;
import android.os.Bundle;
import android.os.Environment;
import android.telephony.PhoneStateListener;
import android.telephony.TelephonyManager;
import android.widget.Button;
import android.animation.ObjectAnimator;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import java.io.File;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
public class TelephonyAnimationActivity extends AppCompatActivity {
private static final int REQUEST_PERMISSIONS = 100;
private MediaRecorder mediaRecorder;
private boolean isRecording = false;
private String outputFile;
private Button recordButton;
private ObjectAnimator pulseAnimator;
private TelephonyManager telephonyManager;
private PhoneStateListener phoneStateListener;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_telephony_animation);
recordButton = findViewById(R.id.record_button);
// 初始化动画
pulseAnimator = ObjectAnimator.ofFloat(recordButton, "scaleX", 1f, 1.2f, 1f);
pulseAnimator.setDuration(1000);
pulseAnimator.setRepeatCount(ObjectAnimator.INFINITE);
// 初始化 TelephonyManager
telephonyManager = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
// 设置电话状态监听
phoneStateListener = new PhoneStateListener() {
@Override
public void onCallStateChanged(int state, String phoneNumber) {
switch (state) {
case TelephonyManager.CALL_STATE_RINGING:
case TelephonyManager.CALL_STATE_OFFHOOK:
// 来电或通话中,暂停录音和动画
if (isRecording) {
stopRecording();
pulseAnimator.cancel();
Toast.makeText(TelephonyAnimationActivity.this, "Recording paused due to call", Toast.LENGTH_SHORT).show();
}
break;
case TelephonyManager.CALL_STATE_IDLE:
// 通话结束,可恢复操作
Toast.makeText(TelephonyAnimationActivity.this, "Call ended", Toast.LENGTH_SHORT).show();
break;
}
}
};
// 请求权限
if (ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED ||
ContextCompat.checkSelfPermission(this, Manifest.permission.READ_PHONE_STATE) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, new String[]{
Manifest.permission.RECORD_AUDIO,
Manifest.permission.READ_PHONE_STATE
}, REQUEST_PERMISSIONS);
} else {
startTelephonyListener();
}
// 录音按钮
recordButton.setOnClickListener(v -> toggleRecording());
}
private void startTelephonyListener() {
telephonyManager.listen(phoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
}
private void toggleRecording() {
if (isRecording) {
stopRecording();
} else {
startRecording();
}
}
private void startRecording() {
mediaRecorder = new MediaRecorder();
try {
mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
outputFile = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC),
"recording_" + new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US).format(new Date()) + ".m4a")
.getAbsolutePath();
mediaRecorder.setOutputFile(outputFile);
mediaRecorder.prepare();
mediaRecorder.start();
isRecording = true;
recordButton.setText("Stop Recording");
pulseAnimator.start();
Toast.makeText(this, "Recording started", Toast.LENGTH_SHORT).show();
} catch (Exception e) {
e.printStackTrace();
Toast.makeText(this, "Failed to start recording", Toast.LENGTH_SHORT).show();
}
}
private void stopRecording() {
try {
mediaRecorder.stop();
mediaRecorder.release();
mediaRecorder = null;
isRecording = false;
recordButton.setText("Start Recording");
pulseAnimator.cancel();
Toast.makeText(this, "Recording saved: " + outputFile, Toast.LENGTH_LONG).show();
} catch (Exception e) {
e.printStackTrace();
Toast.makeText(this, "Failed to stop recording", Toast.LENGTH_SHORT).show();
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == REQUEST_PERMISSIONS && grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
startTelephonyListener();
toggleRecording();
} else {
Toast.makeText(this, "Permissions denied", Toast.LENGTH_SHORT).show();
}
}
@Override
protected void onDestroy() {
super.onDestroy();
if (mediaRecorder != null) {
if (isRecording) {
mediaRecorder.stop();
}
mediaRecorder.release();
mediaRecorder = null;
}
if (telephonyManager != null) {
telephonyManager.listen(phoneStateListener, PhoneStateListener.LISTEN_NONE);
}
pulseAnimator.cancel();
}
}
- 布局 XML(res/layout/activity_telephony_animation.xml):
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical">
<Button
android:id="@+id/record_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Start Recording" />
</LinearLayout>
- AndroidManifest.xml(添加权限):
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
- 说明:
- 权限:动态请求
RECORD_AUDIO
和READ_PHONE_STATE
权限。 - 电话监听:使用
PhoneStateListener
监控电话状态,在来电或通话中暂停录音和动画。 - 录音:与前文
MediaRecorder
示例一致,保存为 MPEG-4/AAC 格式。 - 动画:录音时按钮脉冲动画(
scaleX
),来电或停止录音时取消。 - 生命周期:
onDestroy
释放MediaRecorder
、动画和电话监听器。
其他功能示例
- 获取运营商信息:
String operatorName = telephonyManager.getSimOperatorName();
Toast.makeText(this, "Operator: " + operatorName, Toast.LENGTH_SHORT).show();
- 检查网络类型:
int networkType = telephonyManager.getNetworkType();
String type = networkType == TelephonyManager.NETWORK_TYPE_LTE ? "4G" :
networkType == TelephonyManager.NETWORK_TYPE_NR ? "5G" : "Other";
Toast.makeText(this, "Network: " + type, Toast.LENGTH_SHORT).show();
- 监听信号强度(需
READ_PHONE_STATE
):
phoneStateListener = new PhoneStateListener() {
@Override
public void onSignalStrengthsChanged(SignalStrength signalStrength) {
int strength = signalStrength.getLevel(); // 0-4 级别
Toast.makeText(TelephonyAnimationActivity.this, "Signal Level: " + strength, Toast.LENGTH_SHORT).show();
}
};
telephonyManager.listen(phoneStateListener, PhoneStateListener.LISTEN_SIGNAL_STRENGTHS);
优缺点
- 优点:
- 提供丰富的电话相关信息(SIM、网络、状态)。
- 支持动态监听(如电话状态、信号变化)。
- 与动画结合可实现交互性强的 UI(如暂停动画来电时)。
- 缺点:
- 高 API 级别(29+)对敏感信息(如 IMEI)限制严格。
- 权限要求高,需用户授权。
- 设备差异可能导致数据不一致(如 SIM 卡缺失)。
- 替代方案:
- ConnectivityManager:获取网络状态(如 Wi-Fi、移动数据)。
- BroadcastReceiver:监听电话状态变化(API 26+ 限制)。
- Jetpack Compose State:结合现代 UI 管理状态。
注意事项
- 权限:API 23+ 需动态请求权限,API 29+ 限制设备标识访问。
- 设备兼容性:检查设备是否支持电话功能(
getSystemService(Context.TELEPHONY_SERVICE) != null
)。 - 隐私:避免获取敏感信息(如 IMEI、电话号码),遵守 Google Play 政策。
- 生命周期:及时取消监听(
listen(listener, PhoneStateListener.LISTEN_NONE)
)和释放资源。 - 调试:通过 Log 检查电话状态、权限结果,验证监听器触发。
扩展:Jetpack Compose 中的实现
在 Jetpack Compose 中,TelephonyManager
可结合 Composable 实现电话状态监听和动画:
import android.Manifest
import android.content.Context
import android.content.pm.PackageManager
import android.media.MediaRecorder
import android.os.Environment
import android.telephony.PhoneStateListener
import android.telephony.TelephonyManager
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.layout.*
import androidx.compose.material.Button
import androidx.compose.material.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import androidx.core.content.ContextCompat
import java.io.File
import java.text.SimpleDateFormat
import java.util.*
@Composable
fun TelephonyScreen() {
val context = LocalContext.current
var isRecording by remember { mutableStateOf(false) }
var mediaRecorder by remember { mutableStateOf<MediaRecorder?>(null) }
var outputFile by remember { mutableStateOf("") }
var callState by remember { mutableStateOf("Idle") }
val scale by animateFloatAsState(
targetValue = if (isRecording) 1.2f else 1f,
animationSpec = infiniteRepeatable(tween(1000))
)
val telephonyManager = remember { context.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager }
val phoneStateListener = remember {
object : PhoneStateListener() {
override fun onCallStateChanged(state: Int, phoneNumber: String?) {
callState = when (state) {
TelephonyManager.CALL_STATE_IDLE -> "Idle"
TelephonyManager.CALL_STATE_RINGING -> "Ringing"
TelephonyManager.CALL_STATE_OFFHOOK -> "Offhook"
else -> "Unknown"
}
if (state != TelephonyManager.CALL_STATE_IDLE && isRecording) {
mediaRecorder?.apply {
stop()
release()
}
mediaRecorder = null
isRecording = false
}
}
}
}
val permissionLauncher = rememberLauncherForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { permissions ->
if (permissions[Manifest.permission.RECORD_AUDIO] == true && permissions[Manifest.permission.READ_PHONE_STATE] == true) {
telephonyManager.listen(phoneStateListener, PhoneStateListener.LISTEN_CALL_STATE)
toggleRecording(context, isRecording, { mediaRecorder = it }, { outputFile = it }, { isRecording = it })
}
}
DisposableEffect(Unit) {
onDispose {
mediaRecorder?.apply {
if (isRecording) stop()
release()
}
telephonyManager.listen(phoneStateListener, PhoneStateListener.LISTEN_NONE)
}
}
Column(
modifier = Modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Text("Call State: $callState")
Spacer(Modifier.height(16.dp))
Button(
onClick = {
if (ContextCompat.checkSelfPermission(context, Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED &&
ContextCompat.checkSelfPermission(context, Manifest.permission.READ_PHONE_STATE) == PackageManager.PERMISSION_GRANTED) {
telephonyManager.listen(phoneStateListener, PhoneStateListener.LISTEN_CALL_STATE)
toggleRecording(context, isRecording, { mediaRecorder = it }, { outputFile = it }, { isRecording = it })
} else {
permissionLauncher.launch(arrayOf(Manifest.permission.RECORD_AUDIO, Manifest.permission.READ_PHONE_STATE))
}
},
modifier = Modifier.scale(scale)
) {
Text(if (isRecording) "Stop Recording" else "Start Recording")
}
if (outputFile.isNotEmpty()) {
Text("Saved: $outputFile")
}
}
}
private fun toggleRecording(
context: Context,
isRecording: Boolean,
setMediaRecorder: (MediaRecorder?) -> Unit,
setOutputFile: (String) -> Unit,
setRecording: (Boolean) -> Unit
) {
if (isRecording) {
try {
mediaRecorder?.stop()
mediaRecorder?.release()
setMediaRecorder(null)
setRecording(false)
} catch (e: Exception) {
e.printStackTrace()
}
} else {
val recorder = MediaRecorder().apply {
setAudioSource(MediaRecorder.AudioSource.MIC)
setOutputFormat(MediaRecorder.OutputFormat.MPEG_4)
setAudioEncoder(MediaRecorder.AudioEncoder.AAC)
val file = File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC),
"recording_${SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US).format(Date())}.m4a")
setOutputFile(file.absolutePath)
setOutputFile(file.absolutePath)
try {
prepare()
start()
setMediaRecorder(this)
setRecording(true)
} catch (e: Exception) {
e.printStackTrace()
}
}
}
}
- 说明:
- 使用
remember
缓存TelephonyManager
和PhoneStateListener
。 animateFloatAsState
实现按钮脉冲动画。rememberLauncherForActivityResult
处理多权限请求。- 电话状态变化时更新 UI 并暂停录音。
示例:显示运营商信息
显示运营商并在来电暂停动画:
TextView operatorText = findViewById(R.id.operator_text);
operatorText.setText("Operator: " + telephonyManager.getSimOperatorName());
phoneStateListener = new PhoneStateListener() {
@Override
public void onCallStateChanged(int state, String phoneNumber) {
if (state == TelephonyManager.CALL_STATE_RINGING) {
pulseAnimator.cancel();
}
}
};
telephonyManager.listen(phoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
如果需要更复杂功能(如信号强度动画、动态网络切换、与 Canvas 结合绘制电话状态)、或继续探讨 Android 动画合集(如过渡动画、Lottie),请告诉我!