简介
CSipSimple是Android手机上支持Sip的网络电话软件 , 基于的是pjsip开源sip协议栈
需要了解pjsip , 可以下载PJSUA开发文档中文版看看
源码流程
代码的大致流程如下 :
中间肯定有SipService/PjsioService和DBProvider的交互
UI
===================================
^ |
| |
| V
Intent SipService
DBProvider |
^ |
| V
| PjsipService
UAStateReciver |
^ |
| V
===================================
pjsip底层库
注册流程分析
UI界面 :
public class BasePrefsWizard extends GenericPrefs{
/**
* 保存账户并将账户保存或更新到数据库
* @param wizardId
*/
private void saveAccount(String wizardId) {//BASIC
boolean needRestart = false;
PreferencesWrapper prefs = new PreferencesWrapper(getApplicationContext());
account = wizard.buildAccount(account);//1 . 创建账户 , 策略设计模式
account.wizard = wizardId;
if (account.id == SipProfile.INVALID_ID) {//2 . 创建新的账户时为true
// This account does not exists yet
prefs.startEditing();
wizard.setDefaultParams(prefs);
prefs.endEditing();
applyNewAccountDefault(account);
//3 . 重点 : 将用户信息插入到数据库
Uri uri = getContentResolver().insert(SipProfile.ACCOUNT_URI, account.getDbContentValues());
// After insert, add filters for this wizard
account.id = ContentUris.parseId(uri);
List<Filter> filters = wizard.getDefaultFilters(account);//null
if (filters != null) {
for (Filter filter : filters) { //4 . 如果在设置模块的过滤器进行过配置 , 会执行以下代码
// Ensure the correct id if not done by the wizard
filter.account = (int) account.id;
getContentResolver().insert(SipManager.FILTER_URI, filter.getDbContentValues());
}
}
// Check if we have to restart
needRestart = wizard.needRestart();
} else {//2 . 更新账户信息时执行
// TODO : should not be done there but if not we should add an
// option to re-apply default params
prefs.startEditing();
wizard.setDefaultParams(prefs);
prefs.endEditing();
//3 . 重点 : 将账户信息更新到数据库中
getContentResolver().update(ContentUris.withAppendedId(SipProfile.ACCOUNT_ID_URI_BASE, account.id), account.getDbContentValues(), null, null);
}
// Mainly if global preferences were changed, we have to restart sip stack
if (needRestart) {
//5 . 在创建账户或更新账户时 , 修改过一些设置 , 会执行以下代码 , 进行重启协议栈
// 协议栈会更新配置信息
Intent intent = new Intent(SipManager.ACTION_SIP_REQUEST_RESTART);
sendBroadcast(intent);
}
}
}
/**
* 在上面的[1 . 创建账户]中 , 会使用该类
* 因为在选择向导的时候选择的是Basic , 因此这里选择Basic类进行分析
*/
public class Basic extends BaseImplementation {
/**
* 创建一个账户
*/
public SipProfile buildAccount(SipProfile account) {
Log.d(THIS_FILE, "begin of save ....");
account.display_name = accountDisplayName.getText().trim();//用户名
String[] serverParts = accountServer.getText().split(":");//服务器
account.acc_id = "<sip:" + SipUri.encodeUser(accountUserName.getText().trim()) + "@"+serverParts[0].trim()+">";
String regUri = "sip:" + accountServer.getText();//
account.reg_uri = regUri;
account.proxies = new String[] { regUri } ;
account.realm = "*";
account.username = getText(accountUserName).trim();
account.data = getText(accountPassword);
account.scheme = SipProfile.CRED_SCHEME_DIGEST;
account.datatype = SipProfile.CRED_DATA_PLAIN_PASSWD;
//By default auto transport
account.transport = SipProfile.TRANSPORT_UDP;
return account;
}
}
/**
* 在上面[3 . 重点 : 将用户信息插入到数据库] 和
* [3 . 重点 : 将账户信息更新到数据库中]可以知道 ,
* 最终的用户信息是保存到数据库中的 , 因此下面分析数据库
*/
public class DBProvider extends ContentProvider {
/**
* 将用户信息插入到数据库
*/
@Override
public Uri insert(Uri uri, ContentValues initialValues){
... //省略 代码太多 , 看下重点代码
case ACCOUNTS_STATUS_ID:
long id = ContentUris.parseId(uri);
synchronized (profilesStatus) {
SipProfileState ps = new SipProfileState();
if (profilesStatus.containsKey(id)) {
ContentValues currentValues = profilesStatus.get(id);
ps.createFromContentValue(currentValues);
}
ps.createFromContentValue(initialValues);
ContentValues cv = ps.getAsContentValue();
cv.put(SipProfileState.ACCOUNT_ID, id);
profilesStatus.put(id, cv);
Log.d(THIS_FILE, "Added " + cv);
}
// 1. 通知内容观察者有新的账户注册
getContext().getContentResolver().notifyChange(uri, null);
...// 省略
if (rowId >= 0) {
// TODO : for inserted account register it here
// 2. 重点 , 通知内容观察者有新的账户注册 , 并通过Uri携带注册账户的ID
// 后续可以通过该ID值 , 获取账户的状态信息等
Uri retUri = ContentUris.withAppendedId(baseInsertedUri, rowId);
getContext().getContentResolver().notifyChange(retUri, null);
if (matched == ACCOUNTS || matched == ACCOUNTS_ID) {
broadcastAccountChange(rowId);// 3 . 重点通过广播通知账户信息发生改变
}
... //省略
}
}
/**
* 更新用户数据
*/
@Override
public int update(Uri uri, ContentValues values, String where,String[] whereArgs) {
...// 省略
// 1 . 通知内容观察者有用户的信息发生改变
getContext().getContentResolver().notifyChange(uri, null);
long rowId = -1;
if (matched == ACCOUNTS_ID || matched == ACCOUNTS_STATUS_ID) {
rowId = ContentUris.parseId(uri); // 2. 获取账户的ID
}
if (rowId >= 0) {
if (matched == ACCOUNTS_ID) {
// Don't broadcast if we only changed wizard or only changed
// priority
boolean doBroadcast = true;
if (values.size() == 1) {
if (values.containsKey(SipProfile.FIELD_WIZARD)) {
doBroadcast = false;
} else if (values.containsKey(SipProfile.FIELD_PRIORITY)) {
doBroadcast = false;
}
}
if (doBroadcast) {
broadcastAccountChange(rowId);// 2. 重点 通过广播通知账户信息发生改变
}
} else if (matched == ACCOUNTS_STATUS_ID) {
broadcastRegistrationChange(rowId);
}
... // 省略
}
}
}
/**
* 通过广播通知账户信息发生改变
*/
private void broadcastAccountChange(long accountId) {
Intent publishIntent = new Intent(SipManager.ACTION_SIP_ACCOUNT_CHANGED);// 1. 重点 广播的动作
publishIntent.putExtra(SipProfile.FIELD_ID, accountId);
getContext().sendBroadcast(publishIntent);
BackupWrapper.getInstance(getContext()).dataChanged();
}
}
/**
* 该内容观察者用来接收上面[1. 重点 广播的动作] 的广播 ,
* 该内容观察者的注册是在SipService类中的registerBroadcasts()方法中进行的
*/
public class DynamicReceiver4 extends BroadcastReceiver {
/**
* 处理账户信息发生改变的广播
*/
private void onReceiveInternal(Context context, Intent intent, boolean isSticky) throws SameThreadException {
... // 省略
else if (action.equals(SipManager.ACTION_SIP_ACCOUNT_CHANGED)) { // 1. 处理用户信息发生改变的广播
final long accountId = intent.getLongExtra(SipProfile.FIELD_ID, SipProfile.INVALID_ID);
// Should that be threaded?
if (accountId != SipProfile.INVALID_ID) {
// 2. 通过广播携带的账户ID , 获取到账户的实例
final SipProfile account = service.getAccount(accountId);
if (account != null) {
Log.d(THIS_FILE, "Enqueue set account registration");
// 3 . 重点 进行账户的注册 , 该service就是SipService
service.setAccountRegistration(account, account.active ? 1 : 0, true);
}
}
}
... // 省略
}
}
/**
* 重点 所有的有关通话/信息的操作都是通过该服务来实现的
*/
public class SipService extends Service {
/**
* 注册账户的方法
*/
public boolean setAccountRegistration(SipProfile account, int renew,
boolean forceReAdd) throws SameThreadException {
boolean status = false;
if (pjService != null) {
//1. 重点 实际注册账户是通过PjsipService来实现的
status = pjService.setAccountRegistration(account, renew,forceReAdd);
}
return status;
}
}
/**
* 重点 , 所有与pjsip库进行操作的方法 , 都是通过该类调用pjsua的native方法类实现的
*/
public class PjSipService {
/**
* 进行账户向服务端注册的方法
*/
public boolean setAccountRegistration(SipProfile account, int renew,
boolean forceReAdd) throws SameThreadException {
int status = -1;
if (!created || account == null) {
Log.e(THIS_FILE, "PJSIP is not started here, nothing can be done");
return false;
}
if (account.id == SipProfile.INVALID_ID) {
Log.w(THIS_FILE, "Trying to set registration on a deleted account");
return false;
}
// 获取账户的状态信息
SipProfileState profileState = getProfileState(account);
// If local account -- Ensure we are not deleting, because this would be
// invalid
if (profileState.getWizard().equalsIgnoreCase(
WizardUtils.LOCAL_WIZARD_TAG)) {
if (renew == 0) {
return false;
}
}
// In case of already added, we have to act finely
// If it's local we can just consider that we have to re-add account
// since it will actually just touch the account with a modify
if (profileState != null && profileState.isAddedToStack() && !profileState.getWizard().equalsIgnoreCase(WizardUtils.LOCAL_WIZARD_TAG)) {
// The account is already there in accounts list
service.getContentResolver().delete(ContentUris.withAppendedId(SipProfile.ACCOUNT_STATUS_URI,
account.id), null, null);
Log.d(THIS_FILE,"Account already added to stack, remove and re-load or delete");
if (renew == 1) {
if (forceReAdd) {// 先删除账户 , 然后重新进行添加账户
status = pjsua.acc_del(profileState.getPjsuaId());
addAccount(account); // 1. 重点 添加账户
} else {
// 设置账户的状态为在线 , 并重新进行注册
pjsua.acc_set_online_status(profileState.getPjsuaId(),getOnlineForStatus(service.getPresence()));
status = pjsua.acc_set_registration(profileState.getPjsuaId(), renew);
}
} else {
// if(status == pjsuaConstants.PJ_SUCCESS && renew == 0) {
Log.d(THIS_FILE, "Delete account !!");
status = pjsua.acc_del(profileState.getPjsuaId());// 删除账户
}
} else {
if (renew == 1) {
addAccount(account); // 2 .重点 添加账户
} else {
Log.w(THIS_FILE, "Ask to unregister an unexisting account !!" + account.id);
}
}
// PJ_SUCCESS = 0
return status == 0;
}
/**
* 重点 , 添加账户
*/
public boolean addAccount(SipProfile profile) throws SameThreadException {
int status = pjsuaConstants.PJ_FALSE;
if (!created) {
Log.e(THIS_FILE, "PJSIP is not started here, nothing can be done");
return status == pjsuaConstants.PJ_SUCCESS;
}
PjSipAccount account = new PjSipAccount(profile);// 包装成PjSip账户
account.applyExtraParams(service);// 设置额外的信息
//账户状态实例 , 包含账户是否登录成功的状态
SipProfileState currentAccountStatus = getProfileState(profile);
account.cfg.setRegister_on_acc_add(pjsuaConstants.PJ_FALSE);
if (currentAccountStatus.isAddedToStack()) {// 判断该账户是否曾经加载过
pjsua.csipsimple_set_acc_user_data(// 曾经加载过
currentAccountStatus.getPjsuaId(), account.css_cfg);
status = pjsua.acc_modify(currentAccountStatus.getPjsuaId(),
account.cfg);// 1. 重点 , 对账户的配置进行修改 ,并返回该账户的状态
beforeAccountRegistration(currentAccountStatus.getPjsuaId(),
profile);// 先清除该账户在代理服务器的状态
ContentValues cv = new ContentValues();
cv.put(SipProfileState.ADDED_STATUS, status);// 2. 重点 , 设置该账户的状态
//3 . 重点 , 讲该账户的状态更新到数据库中
service.getContentResolver().update(ContentUris.withAppendedId(
SipProfile.ACCOUNT_STATUS_ID_URI_BASE, profile.id),cv, null, null);
// 判断通用向导的类型 , 如果不是LOCAL ,则继续向下执行 , 我们选择的是BASIC
if (!account.wizard.equalsIgnoreCase(WizardUtils.LOCAL_WIZARD_TAG)) {
// Re register //重新进行注册
if (status == pjsuaConstants.PJ_SUCCESS) {// 账户的配置修改成功
status = pjsua.acc_set_registration(
currentAccountStatus.getPjsuaId(), 1);// 则对账户设置注册
if (status == pjsuaConstants.PJ_SUCCESS) {// 账户设置注册成功
pjsua.acc_set_online_status(
currentAccountStatus.getPjsuaId(), 1);// 则设置该账户为在线状态
}
}
}
} else {// 如果该账户没有注册过
int[] accId = new int[1];
if (account.wizard.equalsIgnoreCase(WizardUtils.LOCAL_WIZARD_TAG)) {// 设置向导选择的BASIC , 不是LOCAL , 不向下执行
// We already have local account by default
// For now consider we are talking about UDP one
// In the future local account should be set per transport
switch (account.transport) {
case SipProfile.TRANSPORT_UDP:
accId[0] = prefsWrapper.useIPv6() ? localUdp6AccPjId
: localUdpAccPjId;
break;
case SipProfile.TRANSPORT_TCP:
accId[0] = prefsWrapper.useIPv6() ? localTcp6AccPjId
: localTcpAccPjId;
break;
case SipProfile.TRANSPORT_TLS:
accId[0] = prefsWrapper.useIPv6() ? localTls6AccPjId
: localTlsAccPjId;
break;
default:
// By default use UDP
accId[0] = localUdpAccPjId;
break;
}
pjsua.csipsimple_set_acc_user_data(accId[0], account.css_cfg);
// TODO : use video cfg here
// nCfg.setVid_in_auto_show(pjsuaConstants.PJ_TRUE);
// nCfg.setVid_out_auto_transmit(pjsuaConstants.PJ_TRUE);
// status = pjsua.acc_modify(accId[0], nCfg);
} else {
// TODO 执行此处的代码
// Cause of standard account different from local account :)
status = pjsua.acc_add(account.cfg, pjsuaConstants.PJ_FALSE,
accId);// 4. 进行账户的添加
pjsua.csipsimple_set_acc_user_data(accId[0], account.css_cfg);// 设置账户数据
beforeAccountRegistration(accId[0], profile);// 在对用户进行注册之前的操作,清除该账户的状态
pjsua.acc_set_registration(accId[0], 1);//重点 4. 对账户进行注册
}
if (status == pjsuaConstants.PJ_SUCCESS) {//5. 账户注册成功
SipProfileState ps = new SipProfileState(profile);// 获取该账户的Sip配置信息
ps.setAddedStatus(status);// 6. 设置注册的状态
ps.setPjsuaId(accId[0]);
service.getContentResolver().insert(
ContentUris.withAppendedId(
SipProfile.ACCOUNT_STATUS_ID_URI_BASE,
account.id), ps.getAsContentValue());// 7 .注册成功,并通知
pjsua.acc_set_online_status(accId[0], 1);// 设置该账户为在线状态
}
}
return status == pjsuaConstants.PJ_SUCCESS;
}
}
/**
* UAStateReceiver , 该类重点 , 所有与pjsip交互的回调 , 都是通过此类完成的 ,
* 相关的api可以参考文档中的pjsua_callback , 这里只关注注册的回调
*/
public class UAStateReceiver{
@Override
public void on_reg_state(final int accountId) {
lockCpu();
pjService.service.getExecutor().execute(new SipRunnable() {
@Override
public void doRun() throws SameThreadException {
// Update java infos
// 1 . 更新账户的状态 , 又回到PjsipService中
pjService.updateProfileStateFromService(accountId);
}
});
unlockCpu();
}
}
/**
* 重点 , 所有与pjsip库进行操作的方法 , 都是通过该类调用pjsua的native方法类实现的
*/
public class PjsipService{
/**
* Synchronize content provider backend from pjsip stack
*
* @param pjsuaId the pjsua id of the account to synchronize
* @throws SameThreadException
*/
public void updateProfileStateFromService(int pjsuaId) throws SameThreadException {
if (!created) {
return;
}
long accId = getAccountIdForPjsipId(service, pjsuaId);// 1. 获取注册的账号
Log.d(THIS_FILE, "Update profile from service for " + pjsuaId + " aka in db " + accId);
if (accId != SipProfile.INVALID_ID) { //2. 判断accId
int success = pjsuaConstants.PJ_FALSE;
pjsua_acc_info pjAccountInfo;
pjAccountInfo = new pjsua_acc_info();
success = pjsua.acc_get_info(pjsuaId, pjAccountInfo); // 3. 重点 , 从pjsip协议栈中获取账户信息
if (success == pjsuaConstants.PJ_SUCCESS && pjAccountInfo != null) {
ContentValues cv = new ContentValues();
// 4 . 设置用户的状态
try {
// Should be fine : status code are coherent with RFC
// status codes
cv.put(SipProfileState.STATUS_CODE, pjAccountInfo.getStatus().swigValue());
} catch (IllegalArgumentException e) {
cv.put(SipProfileState.STATUS_CODE,
SipCallSession.StatusCode.INTERNAL_SERVER_ERROR);
}
cv.put(SipProfileState.STATUS_TEXT, pjStrToString(pjAccountInfo.getStatus_text()));
cv.put(SipProfileState.EXPIRES, pjAccountInfo.getExpires());
//5 . 更新用户的状态
service.getContentResolver().update(
ContentUris.withAppendedId(SipProfile.ACCOUNT_STATUS_ID_URI_BASE, accId),
cv, null, null);
Log.d(THIS_FILE, "Profile state UP : " + cv);
}
} else {
Log.e(THIS_FILE, "Trying to update not added account " + pjsuaId);
}
}
/**
* 1. 获取注册的账号
*/
public static long getAccountIdForPjsipId(Context ctxt, int pjId) {
long accId = SipProfile.INVALID_ID; //1 .设置accId
// 2 . 查找账户
Cursor c = ctxt.getContentResolver().query(SipProfile.ACCOUNT_STATUS_URI, null, null,
null, null);
if (c != null) {
try {
c.moveToFirst();
do {
int pjsuaId = c.getInt(c.getColumnIndex(SipProfileState.PJSUA_ID));
Log.d(THIS_FILE, "Found pjsua " + pjsuaId + " searching " + pjId);
if (pjsuaId == pjId) {
accId = c.getInt(c.getColumnIndex(SipProfileState.ACCOUNT_ID));
break;
}
} while (c.moveToNext());
} catch (Exception e) {
Log.e(THIS_FILE, "Error on looping over sip profiles", e);
} finally {
c.close();
}
}
return accId;
}
}
/**
* 最后又回到DBProvider , 其实所有账户UI层面的状态 , 都是和DBProvider打交道 ,
* MVC模式
*/
public class DBProvider{
@Override
public int update(Uri uri, ContentValues values, String where, String[] whereArgs) {
SQLiteDatabase db = mOpenHelper.getWritableDatabase();
int count;
String finalWhere;
int matched = URI_MATCHER.match(uri);
List<String> possibles = getPossibleFieldsForType(matched);
checkSelection(possibles, where);
// 1. 进行账户的更新 , 到此用户的注册创建注册流程已经走完
// 接下来 , 我们看UI层面是怎么更新用户的状态的
switch (matched) {
case ACCOUNTS:
count = db.update(SipProfile.ACCOUNTS_TABLE_NAME, values, where, whereArgs);
break;
case ACCOUNTS_ID:
finalWhere = DatabaseUtilsCompat.concatenateWhere(SipProfile.FIELD_ID + " = " + ContentUris.parseId(uri), where);
count = db.update(SipProfile.ACCOUNTS_TABLE_NAME, values, finalWhere, whereArgs);
break;
// ... 只看和账户相关的更新
case ACCOUNTS_STATUS_ID:
long id = ContentUris.parseId(uri);
synchronized (profilesStatus){
SipProfileState ps = new SipProfileState();
if(profilesStatus.containsKey(id)) {
ContentValues currentValues = profilesStatus.get(id);
ps.createFromContentValue(currentValues);
}
ps.createFromContentValue(values);
ContentValues cv = ps.getAsContentValue();
cv.put(SipProfileState.ACCOUNT_ID, id);
profilesStatus.put(id, cv);
Log.d(THIS_FILE, "Updated "+cv);
}
count = 1;
break;
default:
throw new IllegalArgumentException(UNKNOWN_URI_LOG + uri);
}
//内容观察者 , 提示数据库发生改变
getContext().getContentResolver().notifyChange(uri, null);
long rowId = -1;
if (matched == ACCOUNTS_ID || matched == ACCOUNTS_STATUS_ID) {
rowId = ContentUris.parseId(uri); //获取更新账户的id
}
if (rowId >= 0) {
if (matched == ACCOUNTS_ID) {
// Don't broadcast if we only changed wizard or only changed priority
boolean doBroadcast = true;
if(values.size() == 1) {
if(values.containsKey(SipProfile.FIELD_WIZARD)) {
doBroadcast = false;
}else if(values.containsKey(SipProfile.FIELD_PRIORITY)) {
doBroadcast = false;
}
}
if(doBroadcast) {
broadcastAccountChange(rowId); // 发送广播 , 告知用户信息发生改变
}
} else if (matched == ACCOUNTS_STATUS_ID) {
broadcastRegistrationChange(rowId); // 发送广播 , 告知用户注册信息发生改变 , 主要更新AppWidget
}
}
return count;
}
}
/**
* 刚才说过 , UI账户状态的操作 , 都是和数据层面打交道 ,
* 因此 , 这里用的是ContentResolver内容观察者
*/
public class AccountsEditListFragment{
@Override
public void onResume() {
super.onResume();
if(statusObserver == null) {
statusObserver = new AccountStatusContentObserver(mHandler);// 1 . 账户状态观察者
getActivity().getContentResolver().registerContentObserver(SipProfile.ACCOUNT_STATUS_URI, true, statusObserver);
}
mAdapter.notifyDataSetChanged();
}
class AccountStatusContentObserver extends ContentObserver {
public AccountStatusContentObserver(Handler h) {
super(h);
}
public void onChange(boolean selfChange) {
Log.d(THIS_FILE, "Accounts status.onChange( " + selfChange + ")");
((BaseAdapter) getListAdapter()).notifyDataSetChanged();// 2. 更新账户状态UI
}
}
}
/**
* 更新UI
*/
public class AccountsEditListAdapter{
@Override
public void bindView(View view, Context context, Cursor cursor) {
super.bindView(view, context, cursor);
AccountListItemViews tagView = (AccountListItemViews) view.getTag();
if (tagView == null) {
tagView = tagRowView(view);
}
// Get the view object and account object for the row
final SipProfile account = new SipProfile(cursor);
AccountRowTag tagIndicator = new AccountRowTag();
tagIndicator.accountId = account.id;
tagIndicator.activated = account.active;
tagView.indicator.setTag(tagIndicator);
tagView.indicator.setVisibility(draggable ? View.GONE : View.VISIBLE);
tagView.grabber.setVisibility(draggable ? View.VISIBLE : View.GONE);
// Get the status of this profile
tagView.labelView.setText(account.display_name);
// 1. 更新用户状态和颜色 , 到此用户的注册流程结束
// Update status label and color
if (account.active) {
AccountStatusDisplay accountStatusDisplay = AccountListUtils.getAccountDisplay(context,
account.id);
tagView.statusView.setText(accountStatusDisplay.statusLabel);
tagView.labelView.setTextColor(accountStatusDisplay.statusColor);
// Update checkbox selection
tagView.activeCheckbox.setChecked(true);
tagView.barOnOff.setImageResource(accountStatusDisplay.checkBoxIndicator);
} else {
tagView.statusView.setText(R.string.acct_inactive);
tagView.labelView.setTextColor(mContext.getResources().getColor(
R.color.account_inactive));
// Update checkbox selection
tagView.activeCheckbox.setChecked(false);
tagView.barOnOff.setImageResource(R.drawable.ic_indicator_off);
}
// Update account image
final WizardInfo wizardInfos = WizardUtils.getWizardClass(account.wizard);
if (wizardInfos != null) {
tagView.activeCheckbox.setBackgroundResource(wizardInfos.icon);
}
}
}