Android で実行される単純な SIP クライアントを作成しようとしています。連絡を取るために SIPDemo プロジェクトをダウンロードしてから、コードの作成を開始しましたが、いくつかの問題が発生しています。
コードを示しましょう。
package com.example.gt_sipclient;
import java.text.ParseException;
import android.net.sip.SipAudioCall;
import android.net.sip.SipException;
import android.net.sip.SipManager;
import android.net.sip.SipProfile;
import android.net.sip.SipRegistrationListener;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.preference.PreferenceManager;
import android.app.Activity;
import android.app.PendingIntent;
import android.content.Intent;
import android.content.SharedPreferences;
import android.util.Log;
import android.view.Menu;
import android.view.MotionEvent;
import android.view.View;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
//Activity android simple pour effectuer quelques appels VoIP
//et réaliser des tests.
public class SIPCliMainActivity extends Activity {
private TextView numText, domText, usrText, pswText, callerIDText, sipText,
statusText;
private EditText numInput, domInput, usrInput, pswInput, callerIDInput,
sipInput;
private Button callBtn, registerBtn, unregisterBtn;
private String number, domain, username, password, callerID, sipPort;
public SipManager mySipManager = null;
public SipProfile mySipProfile = null;
public SipAudioCall call = null;
public IncomingCallReceiver callReceiver;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_sipcli_main);
// TODO: virer les éléments qui ne seront pas utilisés au final
// Get the layout elements
// TextViews
numText = (TextView) findViewById(R.id.number_title);
domText = (TextView) findViewById(R.id.domain);
usrText = (TextView) findViewById(R.id.username);
pswText = (TextView) findViewById(R.id.password);
callerIDText = (TextView) findViewById(R.id.callerid);
sipText = (TextView) findViewById(R.id.sipport);
statusText = (TextView) findViewById(R.id.status);
// EditText views
numInput = (EditText) findViewById(R.id.number_input);
domInput = (EditText) findViewById(R.id.domain_input);
usrInput = (EditText) findViewById(R.id.username_input);
pswInput = (EditText) findViewById(R.id.password_input);
callerIDInput = (EditText) findViewById(R.id.callerid_input);
sipInput = (EditText) findViewById(R.id.sipport_input);
// Button views
callBtn = (Button) findViewById(R.id.callbtn);
registerBtn = (Button) findViewById(R.id.registerbtn);
unregisterBtn = (Button) findViewById(R.id.unregisterbtn);
// make sure that the screen will not turn off during the app lifecycle
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
}
// handle click events without implementing listeners (see layout file
// android:onclick)
// register handling
public void registerClick(View v) {
// Get the SIP account parameters
domain = domInput.getText().toString().trim();
username = usrInput.getText().toString().trim();
// TODO: penser à stocker le psw en MD5 et à le faire aussi dans le
// sip.conf
password = pswInput.getText().toString().trim();
callerID = callerIDInput.getText().toString().trim();
sipPort = sipInput.getText().toString().trim();
sipManagerInit();
}
public void unregisterClick(View v) {
myProfileClose();
}
// TODO: call handling
public void callClick(View v) {
}
public void sipManagerInit() {
if (mySipManager == null)
mySipManager = SipManager.newInstance(this);
myProfileInit();
}
public void myProfileInit() {
if (mySipManager == null)
return;
// TODO: test si un profil est déjà présent
// TODO: Load persistent data if they exist
// SharedPreferences myPreferences =
// PreferenceManager.getDefaultSharedPreferences(getBaseContext());
if (username == null || password == null || domain == null) {
updateStatus("Please fill the form.");
return;
}
try {
SipProfile.Builder builder = new SipProfile.Builder(username,
domain);
builder.setPassword(password);
// important
//builder.setAutoRegistration(false);
mySipProfile = builder.build();
// open myProfile for making and receiving calls
Intent i = new Intent();
i.setAction("com.example.gt_sipclient.INCOMING_CALL");
PendingIntent pi = PendingIntent.getBroadcast(this, 0, i,
Intent.FILL_IN_DATA);
mySipManager.open(mySipProfile, pi, null);
//manual register
//mySipManager.register(mySipProfile, 60, null);
// set SIP registration events
mySipManager.setRegistrationListener(mySipProfile.getUriString(),
new SipRegistrationListener() {
@Override
public void onRegistrationFailed(String arg0, int arg1,
String arg2) {
updateStatus("Registration failed");
}
@Override
public void onRegistrationDone(String arg0, long arg1) {
updateStatus("Registration OK");
registerBtn.setVisibility(View.INVISIBLE);
}
@Override
public void onRegistering(String arg0) {
updateStatus("Registering with SIP server...");
}
});
} catch (ParseException e) {
// TODO getMessage juste pour le debug
updateStatus("Connection error." + e.getMessage());
} catch (SipException e) {
// TODO getMessage juste pour le debug
updateStatus("Connection error SIP." + e.getMessage());
}
}
public void myProfileClose() {
if (mySipManager == null)
return;
try {
if (mySipProfile != null) {
mySipManager.close(mySipProfile.getUriString());
unregisterBtn.setVisibility(View.VISIBLE);
}
} catch (Exception ee) {
Log.d("WalkieTalkieActivity/onDestroy",
"Failed to close local profile.", ee);
}
}
//TODO: completer cette methode pour se desinscrire du serveur manuellement
public void myProfileUnregister(){
if(mySipManager == null)
return;
try {
if(mySipProfile != null && mySipManager.isRegistered(mySipProfile.getUriString())){
mySipManager.unregister(mySipProfile, new SipRegistrationListener(){
@Override
public void onRegistering(String localProfileUri) {
// TODO Auto-generated method stub
}
@Override
public void onRegistrationDone(String localProfileUri,
long expiryTime) {
// TODO Auto-generated method stub
}
@Override
public void onRegistrationFailed(String localProfileUri,
int errorCode, String errorMessage) {
// TODO Auto-generated method stub
}
});
}
} catch (SipException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.sipcli_main, menu);
return true;
}
//Allez on va coder un thread propre pour la maj
public void updateStatus(final String status) {
this.runOnUiThread(new Runnable() {
@Override
public void run() {
statusText.setText(status);
}
});
}
public void updateStatus(SipAudioCall call) {
String useName = call.getPeerProfile().getDisplayName();
// TODO: watch it
if (useName == null)
useName = call.getPeerProfile().getUserName();
updateStatus(useName + "@" + call.getPeerProfile().getSipDomain());
}
}
さて、アプリを実行しているときに、次のエラーが発生しました。
03-06 17:38:35.931: E/JavaBinder(3754): *** Uncaught remote exception! (Exceptions are not yet supported across processes.)
03-06 17:38:35.931: E/JavaBinder(3754): android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
03-06 17:38:35.931: E/JavaBinder(3754): at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:4039)
03-06 17:38:35.931: E/JavaBinder(3754): at android.view.ViewRootImpl.invalidateChild(ViewRootImpl.java:722)
03-06 17:38:35.931: E/JavaBinder(3754): at android.view.ViewRootImpl.invalidateChildInParent(ViewRootImpl.java:771)
03-06 17:38:35.931: E/JavaBinder(3754): at android.view.ViewGroup.invalidateChild(ViewGroup.java:4005)
03-06 17:38:35.931: E/JavaBinder(3754): at android.view.View.invalidate(View.java:8581)
03-06 17:38:35.931: E/JavaBinder(3754): at android.view.View.setFlags(View.java:6766)
03-06 17:38:35.931: E/JavaBinder(3754): at android.view.View.setVisibility(View.java:4617)
03-06 17:38:35.931: E/JavaBinder(3754): at com.example.gt_sipclient.SIPCliMainActivity$1.onRegistrationDone(SIPCliMainActivity.java:163)
03-06 17:38:35.931: E/JavaBinder(3754): at android.net.sip.SipManager$ListenerRelay.onRegistrationDone(SipManager.java:601)
03-06 17:38:35.931: E/JavaBinder(3754): at android.net.sip.ISipSessionListener$Stub.onTransact(ISipSessionListener.java:167)
03-06 17:38:35.931: E/JavaBinder(3754): at android.os.Binder.execTransact(Binder.java:338)
03-06 17:38:35.931: E/JavaBinder(3754): at dalvik.system.NativeStart.run(Native Method)
問題は、渡された登録ステップごとに TextView を更新しようとしているということです。runOnUiThread を使用しても問題ないと思いましたが、明らかに何かが欠けています。登録について心配する必要はありません。うまく機能し (少し遅いですが...)、SIP サーバーでデバイスを確認できました。
私の 2 番目の質問は、自動登録を回避することです。ユーザーがボタンをクリックして SIP サーバーに自分自身を登録/登録解除できるようにしたいと思います (ゾイパー クライアントの場合のように)。それについての例は見つかりませんでした。しかし、あなたがいくつか持っているなら、私はこれらを読んでうれしいです. さらに情報が必要な場合は、教えてください。