由于公司项目涉及到sip通话这块,所以尝试了pjsip库 android自带的sip库和linphone库 自带的sip库挂断各种回调在测试大多是不回调的。 pjsip库好用回调也准确,但是在公司对接的线路中,可以拨打但是没有声音,如果是接听其它端打过来的则有声音。好像是线路路由方面的问题,所以最后只能放弃该框架。最后选择了linphone这个库 引入远程库包 implementation "org.linphone:linphone-sdk-android:4.2-182-g1eb2091"
新建服务LinphoneService
public class LinphoneService extends Service { private static LinphoneService sInstance; private int retry=0; private int successSize=0; private int zhuceSize=0; private Handler mHandler; private Timer mTimer; private Core mCore; public CoreListenerStub mCoreListener; AccountCreator mAccountCreator; private String name="",pwd="",domain=""; Call myCall; private boolean isComing=false; private boolean isExit=false; TimerTask lTask; public static boolean isReady() { return sInstance != null; } public static LinphoneService getInstance() { return sInstance; } public static Core getCore() { return sInstance.mCore; } @Nullable @Override public IBinder onBind(Intent intent) { return null; } @Override public void onCreate() { super.onCreate(); if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){ // MyNotificationManager.getInstance().getManager().createNotificationChannel(MyNotificationManager.getInstance().getChannel()); // startForeground(1,MyNotificationManager.getInstance().getBuilder().setChannelId("1").build()); MyNotificationManager.getInstance().createService(this,10086); } String basePath = getFilesDir().getAbsolutePath(); Factory.instance().setLogCollectionPath(basePath); Factory.instance().enableLogCollection(LogCollectionState.Enabled); Factory.instance().setDebugMode(false, getString(R.string.app_name)); mHandler = new Handler(); // This will be our main Core listener, it will change activities depending on events mCoreListener = new CoreListenerStub() { @Override public void onRegistrationStateChanged(Core core, ProxyConfig cfg, RegistrationState state, String message) { if (state == RegistrationState.Ok) { // successSize++; // Toast.makeText(LinphoneService.this, "注册成功!"+successSize + message, Toast.LENGTH_SHORT).show(); MyLogUtils.testLog("state==注册成功"); } else if (state == RegistrationState.Failed) { MyLogUtils.testLog("state==注册失败"); // retry++; // new Handler().postDelayed(new Runnable() { // @Override // public void run() { // init(name,pwd,domain); // } // },retry*10000); } } @Override public void onCallStateChanged(Core core, Call call, Call.State state, String message) { System.out.println("state=="+state.name()+" message=="+message+"call"+call.getRemoteAddressAsString()); if (state == Call.State.IncomingReceived) {//来电 // if(isComing) {return;} isComing=true; myCall=call; // if(!App.isInToGroupCall){ // Intent intent = new Intent(LinphoneService.this, CallActivity.class); // intent.putExtra("type","0"); // intent.putExtra("user",call.getRemoteAddressAsString()); // intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); // startActivity(intent); // } // CallParams params = getCore().createCallParams(call); // params.enableVideo(false); // call.acceptWithParams(params); } else if (state == Call.State.Connected) { isComing=true; // This stats means the call has been established, let's start the call activity // Toast.makeText(LinphoneService.this, "接通", Toast.LENGTH_SHORT).show(); }else if (state == Call.State.End) { // Toast.makeText(LinphoneService.this, "通话结束", Toast.LENGTH_SHORT).show(); // if(!App.isInToGroupCall){ // EventBus.getDefault().post(new CallEvent()); // } }else if(state == Call.State.Released){ isComing=false; // Toast.makeText(LinphoneService.this, "就绪", Toast.LENGTH_SHORT).show(); } else if(state== Call.State.StreamsRunning){ isComing=true; // Toast.makeText(LinphoneService.this, "通话中", Toast.LENGTH_SHORT).show(); } } }; try { copyIfNotExist(R.raw.linphonerc_default, basePath + "/.linphonerc"); copyFromPackage(R.raw.linphonerc_factory, "linphonerc"); } catch (IOException ioe) { Log.e(ioe); } try { configureCore(basePath); } catch (Exception e) { } } public void jieting(){//接听电话 if (mCore==null){ MyToastUtils.showToast("接听失败!"); return; } CallParams params = mCore.createCallParams(myCall); params.enableVideo(false); myCall.acceptWithParams(params); } public void init(String name,String pwd,String domain){//注册账号 // if(isExit) { // String basePath = getFilesDir().getAbsolutePath(); // try { // copyIfNotExist(R.raw.linphonerc_default, basePath + "/.linphonerc"); // copyFromPackage(R.raw.linphonerc_factory, "linphonerc"); // } catch (IOException ioe) { // Log.e(ioe); // } // try { // configureCore(basePath); // } catch (Exception e) { // // } // mCore.start();} this.name=name; this.pwd=pwd; this.domain=domain; if(TextUtils.isEmpty(this.name)||TextUtils.isEmpty(this.pwd)||TextUtils.isEmpty(this.domain)){ return; } if(mCore==null){ new Handler().postDelayed(new Runnable() { @Override public void run() { init(name,pwd,domain); } },500); return; } // zhuceSize++; // Toast.makeText(this, "开始注册"+zhuceSize, Toast.LENGTH_SHORT).show(); isExit=false; mAccountCreator=mCore.createAccountCreator(null); mAccountCreator.setUsername(name); mAccountCreator.setDomain(domain); mAccountCreator.setPassword(pwd); mAccountCreator.setTransport(TransportType.Udp); // mAccountCreator.setTransport(TransportType.Tls); ProxyConfig cfg = mAccountCreator.createProxyConfig(); cfg.setExpires(1); mCore.setDefaultProxyConfig(cfg); } public void logOut(){//登出账号 if (mAccountCreator==null) return; // if(!isExit) { // mCore.stop(); // mCore = null; // } isExit=true; ProxyConfig cfg = mAccountCreator.createProxyConfig(); cfg.setExpires(0);//注销 mCore.setDefaultProxyConfig(cfg); // mCore.clearCallLogs(); mCore.removeProxyConfig(cfg); // mAccountCreator=null; // mCore.clearProxyConfig(); // mCore.clearAllAuthInfo(); } @Override public int onStartCommand(Intent intent, int flags, int startId) { super.onStartCommand(intent, flags, startId); // If our Service is already running, no need to continue if (sInstance != null) { return START_STICKY; } // Our Service has been started, we can keep our reference on it // From now one the Launcher will be able to call onServiceReady() sInstance = this; // Core must be started after being created and configured // mCore.start();//这句会导致服务一启动就注册。 // We also MUST call the iterate() method of the Core on a regular basis TimerTask lTask = new TimerTask() { @Override public void run() { mHandler.post( new Runnable() { @Override public void run() { if (isExit){ return; } if (mCore != null) { try { mCore.iterate(); }catch (Exception e){ } } } }); } }; mTimer = new Timer("Linphone scheduler"); mTimer.schedule(lTask, 0, 20); return START_STICKY; } @Override public void onDestroy() { mCore.removeListener(mCoreListener); mTimer.cancel(); mCore.stop(); // A stopped Core can be started again // To ensure resources are freed, we must ensure it will be garbage collected mCore = null; // Don't forget to free the singleton as well sInstance = null; super.onDestroy(); } @Override public void onTaskRemoved(Intent rootIntent) { // For this sample we will kill the Service at the same time we kill the app stopSelf(); super.onTaskRemoved(rootIntent); } private void configureCore(String basePath) { mCore = Factory.instance() .createCore(basePath + "/.linphonerc", basePath + "/linphonerc", this); mCore.enableEchoCancellation(true);//回声消除 mCore.enableAdaptiveRateControl(true);//自适应码率控制 mCore.addListener(mCoreListener); App.isAddCallListen=true; // We will create a directory for user signed certificates if needed String userCerts = basePath + "/user-certs"; File f = new File(userCerts); if (!f.exists()) { if (!f.mkdir()) { Log.e(userCerts + " can't be created."); } } mCore.setUserCertificatesPath(userCerts); } private void dumpDeviceInformation() { StringBuilder sb = new StringBuilder(); sb.append("DEVICE=").append(Build.DEVICE).append("\n"); sb.append("MODEL=").append(Build.MODEL).append("\n"); sb.append("MANUFACTURER=").append(Build.MANUFACTURER).append("\n"); sb.append("SDK=").append(Build.VERSION.SDK_INT).append("\n"); sb.append("Supported ABIs="); for (String abi : Version.getCpuAbis()) { sb.append(abi).append(", "); } sb.append("\n"); Log.i(sb.toString()); } private void dumpInstalledLinphoneInformation() { PackageInfo info = null; try { info = getPackageManager().getPackageInfo(getPackageName(), 0); } catch (PackageManager.NameNotFoundException nnfe) { Log.e(nnfe); } if (info != null) { Log.i( "[Service] Linphone version is ", info.versionName + " (" + info.versionCode + ")"); } else { Log.i("[Service] Linphone version is unknown"); } } private void copyIfNotExist(int ressourceId, String target) throws IOException { File lFileToCopy = new File(target); if (!lFileToCopy.exists()) { copyFromPackage(ressourceId, lFileToCopy.getName()); } } private void copyFromPackage(int ressourceId, String target) throws IOException { FileOutputStream lOutputStream = openFileOutput(target, 0); InputStream lInputStream = getResources().openRawResource(ressourceId); int readByte; byte[] buff = new byte[8048]; while ((readByte = lInputStream.read(buff)) != -1) { lOutputStream.write(buff, 0, readByte); } lOutputStream.flush(); lOutputStream.close(); lInputStream.close(); } }
//拨打电话
address格式为 坐席号码@域名 如1004@192.168.1.10
public static void call(String address){ Core core = LinphoneService.getCore(); if (core==null||TextUtils.isEmpty(address)){ Toast.makeText(context, "拨打失败!", Toast.LENGTH_SHORT).show(); return; } Address addressToCall = core.interpretUrl(address); CallParams params = core.createCallParams(null); params.enableVideo(false); if (addressToCall != null) { core.inviteAddressWithParams(addressToCall, params); } }
//挂断电话
public static void guaduan(){ Core core = LinphoneService.getCore(); if(core==null) { MyToastUtils.showToast("挂断失败!"); return; } if (core.getCallsNb() > 0) { Call call = core.getCurrentCall(); if (call == null) { // Current call can be null if paused for example call = core.getCalls()[0]; } call.terminate(); } }
//项目中遇到一个问题就是注册到线上之后如何注销账号发现linphone库并没有提供api,所以查询了一下网上的资料发现要发送指令Expires=0所以写了如下语句。
ProxyConfig cfg = mAccountCreator.createProxyConfig();
cfg.setExpires(0);//注销
测试发现在APP没有销毁的情况下调用注销确实可以登出话机不再收到通话。
不过将APP杀死重新启动 发现sip自动注册了。
这样就导致会突然收到sip电话的响铃,但是并无界面,需求是在指定页面才能进行通话,这样肯定不符合需求。
发现是LinphoneService中的
mCore.start();//这句会导致服务一启动就注册。
所以将这句注释。问题解决。
退出界面的时候就调用LinphoneService的logout方法即可,在拨打界面注册时先调用mCore.start();再进行init注册即可。