乐趣区

关于音视频:融云技术如何使用-Telecom-构建-VOIP-通话应用提高APP用户体验

Android 收紧权限后,如何利用 Telecom 技术构建 VOIP 通话利用,晋升 App 用户的应用体验置信是开发者比较关心的技术话题之一。本文从 Telecom 框架概览、Telecom 启动过程剖析、构建 VOIP 通话利用、解决常见的通话场景几个方面,率领大家领略融云如何应用 Telecom 构建 VOIP 通话利用。

一、背景及现状

Android 低版本收到 IM 语音通话,通过在后盾启动 Activity,能够间接跳转至通话界面;但 Android 10 及更高版本上收到 IM 语音通话时,只能通过弹出告诉栏的形式揭示用户,这种形式不直观,也可能漏接通话,用户体验不佳。

因为从 Android 10 开始,零碎限度了从后盾 (组件) 启动 Activity 的行为。这样有助于最大限度地缩小对用户造成的中断,让用户更好地管制其屏幕上显示的内容,减少用户体验和安全性。后盾利用启动,必须通过“显示告诉”的形式来进行,告诉用户借助 notification 来启动新的 Activity。

在本文中重点介绍 Android Telecom 及应用 Telecom 构建 VOIP 通话利用,在 Android 高版本实现 IM 语音通话的复电界面跳转。

二、Telecom 框架概览

Android Telecom 框架治理 Android 设施上的音频和视频呼叫。其中包含基于 SIM 的呼叫(例如,应用 Telephony 框架)和由 ConnectionService API 实施者提供的 VOIP 呼叫。

如图一所示,Telecom 负责通话状态音讯上报和通话管制音讯下发。

图一:Telecom 音讯解决模型

通话音讯下发流程:
用户在通话界面操作后,通过 InCallAdapter(IInCallAdapter 服务端实现)对象来告诉 Telecom(最终调用 CallsManager 相干函数),Telecom 调用 IConnectionService 封装接口向 RIL 发动通话治理和管制相干 RIL 申请,RIL 转换成对应的音讯发送给 Modem 执行,其中包含拨号,接听电话,拒接电话等。

通话状态更新音讯上报流程:
RIL 收到 Modem 的通话状态变动, 告诉以后 Connection 和 Call 的状态或属性产生的变动,再通过一系列的 Listener 音讯回调解决,最终由 lnCallController 创立 ParcelableCall 对象,应用 llnCallService 服务接口调用发送给 Dialer 利用。

Telecom 起交互桥梁作用,与 InCallUI 和 Telephony【Phone 过程、TeleService】交互,交互过程如图二所示:

图二:Telecom 模块间交互流程

除 phone 过程和 modem 通过 socket 进行通信外,过程间通过 aidl 进行交互,Telecom 过程别离通过 IConnectionService.aid l 和 IInCallService.aidl 来管制 phone 过程和 Incallui 过程。而 phone 过程(com.android.phone)和 Incallui 过程 (com.android.incallui) 通过 IConnectionServiceAdapter.aidl 和 IInCallAdapter.aidl 来告诉 telecom 过程须要变更。

三、Telecom 启动过程剖析

· /frameworks/base/services/java/com/android/server/SystemServer.java
SystemServer 过程初始化实现后, startOtherServices 中启动 TelecomLoaderService 零碎服务,加载 Telecom:
/**

  • Starts a miscellaneous grab bag of stuff that has yet to be refactored and organized.
    */

private void startOtherServices(@NonNull TimingsTraceAndSlog t) {

 ...

// 启动 TelecomLoaderService 零碎服务,用于加载 Telecom t.traceBegin(“StartTelecomLoaderService”);
mSystemServiceManager.startService(TelecomLoaderService.class);
t.traceEnd();

}

左滑查看更多→

· /frameworks/base/services/core/java/com/android/server/telecom/TelecomLoaderService.java

TelecomLoaderService 类中 onBootPhase 函数,用于 SystemServer 告知零碎服务目前系统启动所处的阶段。AMS(ActivityManagerService) 启动实现后,开始连贯 Telecom 服务。
/**

  • Starts the telecom component by binding to its ITelecomService implementation. Telecom is setup
  • to run in the system-server process so once it is loaded into memory it will stay running.
  • @hide
    */

public class TelecomLoaderService extends SystemService {

    private static final ComponentName SERVICE_COMPONENT = new ComponentName(
            "com.android.server.telecom",
            "com.android.server.telecom.components.TelecomService");
    private static final String SERVICE_ACTION = "com.android.ITelecomService";
    // 以后系统启动的阶段
    @Override
    public void onBootPhase(int phase) {if (phase == PHASE_ACTIVITY_MANAGER_READY) {
            ...
            connectToTelecom();}
    }
    // 绑定 Telecom 服务
    private void connectToTelecom() {synchronized (mLock) {if (mServiceConnection != null) {
                // TODO: Is unbinding worth doing or wait for system to rebind?
                mContext.unbindService(mServiceConnection);
                mServiceConnection = null;
            }
            TelecomServiceConnection serviceConnection = new TelecomServiceConnection();
            Intent intent = new Intent(SERVICE_ACTION);
            intent.setComponent(SERVICE_COMPONENT);
            int flags = Context.BIND_IMPORTANT | Context.BIND_FOREGROUND_SERVICE
                    | Context.BIND_AUTO_CREATE;
            // Bind to Telecom and register the service
            if (mContext.bindServiceAsUser(intent, serviceConnection, flags, UserHandle.SYSTEM)) {mServiceConnection = serviceConnection;}
        }
    }
}

左滑查看更多→

· /packages/services/Telecomm/src/com/android/server/telecom/components/TelecomService.java

绑定服务时,调用 TelecomService 的 onBind 接口,对整个 Telecom 零碎进行初始化,返回 IBinder 接口。
/**

  • Implementation of the ITelecom interface.
    */

public class TelecomService extends Service implements TelecomSystem.Component {

@Override
public IBinder onBind(Intent intent) {Log.d(this, "onBind");
    return new ITelecomLoader.Stub() {
        @Override
        public ITelecomService createTelecomService(IInternalServiceRetriever retriever) {
            InternalServiceRetrieverAdapter adapter =
                    new InternalServiceRetrieverAdapter(retriever);

// 初始化整个 Telecom 零碎

            initializeTelecomSystem(TelecomService.this, adapter);

// 返回 IBinder 接口

            synchronized (getTelecomSystem().getLock()) {return getTelecomSystem().getTelecomServiceImpl().getBinder();
            }
        }
    };
}

}

左滑查看更多→

Telecom 零碎初始化,次要是新建一个 TelecomSystem 的类,在这个类中,会对 Telecom 服务的相干类都初始化。
/**

  • This method is to be called by components (Activitys, Services, …) to initialize the
  • Telecom singleton. It should only be called on the main thread. As such, it is atomic
  • and needs no synchronization — it will either perform its initialization, after which
  • the {@link TelecomSystem#getInstance()} will be initialized, or some other invocation of
  • this method on the main thread will have happened strictly prior to it, and this method
  • will be a benign no-op.
    *
  • @param context
    */

static void initializeTelecomSystem(Context context,

                                InternalServiceRetrieverAdapter internalServiceRetriever) {if (TelecomSystem.getInstance() == null) {
    NotificationChannelManager notificationChannelManager =
            new NotificationChannelManager();
    notificationChannelManager.createChannels(context);

// 新建一个单例模式的 TelecomSystem 对象

    TelecomSystem.setInstance(new TelecomSystem(...));
}

}

左滑查看更多→

· /packages/services/Telecomm/src/com/android/server/telecom/TelecomSystem.java

结构一个单例 TelecomSystem 对象,会创立通话无关的类。比方:

· CallsManager
· CallIntentProcessor
· TelecomServiceImpl

四、构建 VOIP 通话利用

4.1 清单申明和权限

申明一项服务,该服务指定用于在您的利用中实现 ConnectionService 类的类。Telecom 子系统要求该服务申明 BIND_TELECOM_CONNECTION_SERVICE 权限,能力与之绑定。以下示例展现了如何在利用清单中申明该服务:
<service android:name=”com.example.telecom.MyConnectionService”

android:label="com.example.telecom"
android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE">
<intent-filter>
    <action android:name="android.telecom.ConnectionService" />
</intent-filter>

</service>

左滑查看更多→

在您的利用清单中,申明您的利用应用 MANAGE_OWN_CALLS 权限,如下所示:
<uses-permission android:name=”android.permission.MANAGE_OWN_CALLS”/>

左滑查看更多→

4.2 实现连贯服务

您的通话利用必须提供 Telecom 子系统可能与之绑定的 ConnectionService 类的实现。示例如下:
public class MyConnectionService extends ConnectionService {

@Override
public void onCreate() {super.onCreate();
}

@Override
public Connection onCreateIncomingConnection(PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request) {super.onCreateIncomingConnection(connectionManagerPhoneAccount, request);
    MyConnection conn = new MyConnection(getApplicationContext());
    conn.setConnectionProperties(Connection.PROPERTY_SELF_MANAGED);
    conn.setCallerDisplayName("Telecom", TelecomManager.PRESENTATION_ALLOWED);
    conn.setAddress(Uri.parse("tel:" + "10086"), TelecomManager.PRESENTATION_ALLOWED);
    conn.setRinging();
    conn.setInitializing();
    conn.setActive();
    return conn;
}

@Override
public Connection onCreateOutgoingConnection(PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request) {return super.onCreateOutgoingConnection(connectionManagerPhoneAccount, request);
}

}

左滑查看更多→

4.3 实现连贯

您的利用应创立 Connection 的子类以示意利用中的复电。示例如下:
public class MyConnection extends Connection {

MyConnection() {}

/**
 * Telecom 子系统会在您增加新的复电时调用此办法,并且您的利用应显示其复电界面。*/
@Override
public void onShowIncomingCallUi() {super.onShowIncomingCallUi();
    // 这里展现您自定义的通话界面
}

}

左滑查看更多→

五、解决常见的通话场景

以 VOIP 通话复电为例,按以下步骤操作:

  1. 注册 PhoneAccount(PhoneAccount 是用来接打电话的,应用 TelecomManager 创立一个 PhoneAccount。PhoneAccount 有惟一的标识叫做 PhoneAccountHandle,Telecom 会通过 PhoneAccountHandle 所提供的 ConnectionService 信息来与 App 进行通信),如下所示:
    TelecomManager tm = (TelecomManager) getSystemService(Context.TELECOM_SERVICE);
    PhoneAccountHandle phoneAccountHandle = new PhoneAccountHandle(

    new ComponentName(this.getApplicationContext(), MyConnectionService.class),
    "AppName");

    PhoneAccount phoneAccount = PhoneAccount.builder(phoneAccountHandle, “AppName”).setCapabilities(PhoneAccount.CAPABILITY_SELF_MANAGED).build();
    telecomManager.registerPhoneAccount(phoneAccount);

左滑查看更多→

  1. 应用 addNewIncomingCall(PhoneAccountHandle, Bundle) 办法在有新复电时告知 Telecom 子系统。
  2. Telecom 子系统绑定您利用的 ConnectionService 实现,应用 onCreateIncomingConnection (PhoneAccountHandle,ConnectionRequest) 办法申请示意新复电的 Connection 类的新实例。
  3. Telecom 子系统会应用 onShowIncomingCallUi() 办法告知您的利用应显示其复电界面。

结语

Android 10 及更高版本上的用户,接管 IM 即时通讯音讯只能通过弹出告诉栏的形式,造成用户体验较差。通过本文的介绍,心愿对 Android 开发者有所帮忙,从而改善用户体验。

退出移动版