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_STATE1+ (API 29 受限)String imei = telephonyManager.getDeviceId();
getImei()获取 IMEI(API 26+,多 SIM 卡支持)。READ_PHONE_STATE26+String imei = telephonyManager.getImei();
getSimOperatorName()获取 SIM 卡运营商名称。1+String operator = telephonyManager.getSimOperatorName();
getNetworkType()获取网络类型(如 4G、5G)。1+int networkType = telephonyManager.getNetworkType();
getCallState()获取电话状态(闲置、响铃、通话中)。READ_PHONE_STATE1+int callState = telephonyManager.getCallState();
listen(PhoneStateListener, int)注册电话状态监听器(如来电、信号变化)。READ_PHONE_STATE1+telephonyManager.listen(listener, PhoneStateListener.LISTEN_CALL_STATE);
getLine1Number()获取 SIM 卡电话号码(可能为空,API 29+ 受限)。READ_PHONE_NUMBERS1+ (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_AUDIOREAD_PHONE_STATE 权限。
  • 电话监听:使用 PhoneStateListener 监控电话状态,在来电或通话中暂停录音和动画。
  • 录音:与前文 MediaRecorder 示例一致,保存为 MPEG-4/AAC 格式。
  • 动画:录音时按钮脉冲动画(scaleX),来电或停止录音时取消。
  • 生命周期onDestroy 释放 MediaRecorder、动画和电话监听器。

其他功能示例

  1. 获取运营商信息
   String operatorName = telephonyManager.getSimOperatorName();
   Toast.makeText(this, "Operator: " + operatorName, Toast.LENGTH_SHORT).show();
  1. 检查网络类型
   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();
  1. 监听信号强度(需 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 缓存 TelephonyManagerPhoneStateListener
  • 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),请告诉我!

类似文章

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注