会话页面
发送消息
sendButton是发送消息按钮,点击发送消息按钮:
SendButtonListener -> sendMessage() -> sendTextMessage() -> AsyncTask#doInBackground() -> MessageSender.send() -> sendTextMessage() -> sendSms() -> MmsSendJob.enqueue()
//ConversationActivity.java
private class SendButtonListener implements OnClickListener, TextView.OnEditorActionListener {
@Override
public void onClick(View v) {
sendMessage();
}
@Override
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if (actionId == EditorInfo.IME_ACTION_SEND) {
sendButton.performClick();
return true;
}
return false;
}
}
看下sendMessage():
private void sendMessage() {
if (inputPanel.isRecordingInLockedMode()) {
inputPanel.releaseRecordingLock();
return;
}
try {
Recipient recipient = getRecipient();
if (recipient == null) {
throw new RecipientFormattingException("Badly formatted");
}
String message = getMessage();
TransportOption transport = sendButton.getSelectedTransport();
boolean forceSms = (recipient.isForceSmsSelection() || sendButton.isManualSelection()) && transport.isSms();
int subscriptionId = sendButton.getSelectedTransport().getSimSubscriptionId().or(-1);
long expiresIn = recipient.getExpireMessages() * 1000L;
boolean initiating = threadId == -1;
boolean needsSplit = !transport.isSms() && message.length() > transport.calculateCharacters(message).maxPrimaryMessageSize;
boolean isMediaMessage = attachmentManager.isAttachmentPresent() ||
recipient.isGroup() ||
recipient.getEmail().isPresent() ||
inputPanel.getQuote().isPresent() ||
linkPreviewViewModel.hasLinkPreview() ||
needsSplit;
Log.i(TAG, "isManual Selection: " + sendButton.isManualSelection());
Log.i(TAG, "forceSms: " + forceSms);
if ((recipient.isMmsGroup() || recipient.getEmail().isPresent()) && !isMmsEnabled) {
handleManualMmsRequired();
} else if (!forceSms && (identityRecords.isUnverified() || identityRecords.isUntrusted())) {
handleRecentSafetyNumberChange();
} else if (isMediaMessage) {
sendMediaMessage(forceSms, expiresIn, false, subscriptionId, initiating);
} else {
sendTextMessage(forceSms, expiresIn, subscriptionId, initiating);
}
} catch (RecipientFormattingException ex) {
Toast.makeText(ConversationActivity.this,
R.string.ConversationActivity_recipient_is_not_a_valid_sms_or_email_address_exclamation,
Toast.LENGTH_LONG).show();
Log.w(TAG, ex);
} catch (InvalidMessageException ex) {
Toast.makeText(ConversationActivity.this, R.string.ConversationActivity_message_is_empty_exclamation,
Toast.LENGTH_SHORT).show();
Log.w(TAG, ex);
}
}
这里会判断当前发送的是多媒体消息还是文本消息,若是发送多媒体消息则执行sendMediaMessage,
若是发送文本消息则执行sendTextMessage,看下sendTextMessage:
//ConversationActivity.java
private void sendTextMessage(final boolean forceSms, final long expiresIn, final int subscriptionId, final boolean initiating)
throws InvalidMessageException
{
if (!isDefaultSms && (!isSecureText || forceSms)) {
showDefaultSmsPrompt();
return;
}
final Context context = getApplicationContext();
final String messageBody = getMessage();
OutgoingTextMessage message;
if (isSecureText && !forceSms) { //发送的是加密文本消息,创建的是OutgoingEncryptedMessage对象
message = new OutgoingEncryptedMessage(recipient.get(), messageBody, expiresIn);
ApplicationContext.getInstance(context).getTypingStatusSender().onTypingStopped(threadId);
} else { //发送的是普通消息,创建的是OutgoingTextMessage对象
message = new OutgoingTextMessage(recipient.get(), messageBody, expiresIn, subscriptionId);
}
Permissions.with(this)
.request(Manifest.permission.SEND_SMS)
.ifNecessary(forceSms || !isSecureText)
.withPermanentDenialDialog(getString(R.string.ConversationActivity_signal_needs_sms_permission_in_order_to_send_an_sms))
.onAllGranted(() -> {
silentlySetComposeText("");
// 一、将消息放入聊天列表中
final long id = fragment.stageOutgoingMessage(message);
new AsyncTask<OutgoingTextMessage, Void, Long>() {
@Override
protected Long doInBackground(OutgoingTextMessage... messages) {
//二、消息数据入数据库,发送消息到服务器
return MessageSender.send(context, messages[0], threadId, forceSms, () -> fragment.releaseOutgoingMessage(id));
}
@Override
protected void onPostExecute(Long result) {
//三、消息发送完成的页面更新工作
sendComplete(result);
}
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, message);
})
.execute();
}
一、将消息放入聊天列表中
//ConversationFragment.java
/**
* 多媒体类型的消息
*/
public long stageOutgoingMessage(OutgoingMediaMessage message) {
//1.给消息添加唯一id,创建时间等,最后组装成MessageRecord对象返回
MessageRecord messageRecord = DatabaseFactory.getMmsDatabase(getContext()).readerFor(message, threadId).getCurrent();
//2.然后添加messageRecord到聊天列表框中进行显示
if (getListAdapter() != null) {
clearHeaderIfNotTyping(getListAdapter());
setLastSeen(0);
getListAdapter().addFastRecord(messageRecord);
list.post(() -> list.scrollToPosition(0));
}
return messageRecord.getId();
}
/**
* 文本类型的消息
*/
public long stageOutgoingMessage(OutgoingTextMessage message) {
MessageRecord messageRecord = DatabaseFactory.getSmsDatabase(getContext()).readerFor(message, threadId).getCurrent();
if (getListAdapter() != null) {
clearHeaderIfNotTyping(getListAdapter());
setLastSeen(0);
getListAdapter().addFastRecord(messageRecord);
list.post(() -> list.scrollToPosition(0));
}
return messageRecord.getId();
}
二、消息数据入数据库,发送消息到服务器
doInBackground中调用MessageSender.send(context, messages[0], threadId, forceSms, () -> fragment.releaseOutgoingMessage(id));
进行发送消息以及处理回调:
//MessageSender.send()
public static long send(final Context context,
final OutgoingTextMessage message,
final long threadId,
final boolean forceSms,
final SmsDatabase.InsertListener insertListener)
{
SmsDatabase database = DatabaseFactory.getSmsDatabase(context);
Recipient recipient = message.getRecipient();
boolean keyExchange = message.isKeyExchange();
long allocatedThreadId;
if (threadId == -1) {
allocatedThreadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipient);
} else {
allocatedThreadId = threadId;
}
//1.先将消息存入数据库
long messageId = database.insertMessageOutbox(allocatedThreadId, message, forceSms, System.currentTimeMillis(), insertListener);
//2.然后真正的将消息发送到服务器(通过websocket)
sendTextMessage(context, recipient, forceSms, keyExchange, messageId);
//3.消息发送完的回调
onMessageSent();
return allocatedThreadId;
}
1.先将消息存入数据库
long messageId = database.insertMessageOutbox(allocatedThreadId, message, forceSms, System.currentTimeMillis(), insertListener);
(1).看下insertMessageOutbox方法:
//SmsDatabase.java
public long insertMessageOutbox(long threadId, OutgoingTextMessage message,
boolean forceSms, long date, InsertListener insertListener)
{
long type = Types.BASE_SENDING_TYPE;
if (message.isKeyExchange()) type |= Types.KEY_EXCHANGE_BIT;
else if (message.isSecureMessage()) type |= (Types.SECURE_MESSAGE_BIT | Types.PUSH_MESSAGE_BIT);
else if (message.isEndSession()) type |= Types.END_SESSION_BIT;
if (forceSms) type |= Types.MESSAGE_FORCE_SMS_BIT;
if (message.isIdentityVerified()) type |= Types.KEY_EXCHANGE_IDENTITY_VERIFIED_BIT;
else if (message.isIdentityDefault()) type |= Types.KEY_EXCHANGE_IDENTITY_DEFAULT_BIT;
RecipientId recipientId = message.getRecipient().getId();
Map<RecipientId, Long> earlyDeliveryReceipts = earlyDeliveryReceiptCache.remove(date);
ContentValues contentValues = new ContentValues(6);
contentValues.put(RECIPIENT_ID, recipientId.serialize());
contentValues.put(THREAD_ID, threadId);
contentValues.put(BODY, message.getMessageBody());
contentValues.put(DATE_RECEIVED, System.currentTimeMillis());
contentValues.put(DATE_SENT, date);
contentValues.put(READ, 1);
contentValues.put(TYPE, type);
contentValues.put(SUBSCRIPTION_ID, message.getSubscriptionId());
contentValues.put(EXPIRES_IN, message.getExpiresIn());
contentValues.put(DELIVERY_RECEIPT_COUNT, Stream.of(earlyDeliveryReceipts.values()).mapToLong(Long::longValue).sum());
SQLiteDatabase db = databaseHelper.getWritableDatabase();
//消息入库
long messageId = db.insert(TABLE_NAME, null, contentValues);
if (insertListener != null) {
insertListener.onComplete();
}
if (!message.isIdentityVerified() && !message.isIdentityDefault()) {
DatabaseFactory.getThreadDatabase(context).update(threadId, true);
DatabaseFactory.getThreadDatabase(context).setLastSeen(threadId);
}
DatabaseFactory.getThreadDatabase(context).setHasSent(threadId, true);
notifyConversationListeners(threadId);
if (!message.isIdentityVerified() && !message.isIdentityDefault()) {
ApplicationDependencies.getJobManager().add(new TrimThreadJob(threadId));
}
return messageId;
}
这里需要注意,如果新增了消息类型type,那么这里需要做些判断,根据参数OutgoingTextMessage message
是否是某些类型,更新type的值,type值也是要入数据库的。
那么同样在接收到消息时也是要在insertMessageInbox方法中根据参数IncomingTextMessage message
是否是某些类型,更新type的值,然后消息入库:
protected Optional<InsertResult> insertMessageInbox(IncomingTextMessage message, long type) {
if (message.isJoined()) {
type = (type & (Types.TOTAL_MASK - Types.BASE_TYPE_MASK)) | Types.JOINED_TYPE;
} else if (message.isPreKeyBundle()) {
type |= Types.KEY_EXCHANGE_BIT | Types.KEY_EXCHANGE_BUNDLE_BIT;
} else if (message.isSecureMessage()) {
type |= Types.SECURE_MESSAGE_BIT;
} else if (message.isGroup()) {
IncomingGroupUpdateMessage incomingGroupUpdateMessage = (IncomingGroupUpdateMessage) message;
type |= Types.SECURE_MESSAGE_BIT;
if (incomingGroupUpdateMessage.isGroupV2()) type |= Types.GROUP_V2_BIT | Types.GROUP_UPDATE_BIT;
else if (incomingGroupUpdateMessage.isUpdate()) type |= Types.GROUP_UPDATE_BIT;
else if (incomingGroupUpdateMessage.isQuit()) type |= Types.GROUP_QUIT_BIT;
} else if (message.isEndSession()) {
type |= Types.SECURE_MESSAGE_BIT;
type |= Types.END_SESSION_BIT;
}
...
SQLiteDatabase db = databaseHelper.getWritableDatabase();
//消息入库
long messageId = db.insert(TABLE_NAME, null, values);
(2).然后设置的SmsDatabase.InsertListener是() -> fragment.releaseOutgoingMessage(id)
,即插入数据库成功后会执行:fragment.releaseOutgoingMessage(id)
这句代码
2.然后真正的将消息发送到服务器(通过websocket)
sendTextMessage(context, recipient, forceSms, keyExchange, messageId);
传递的是messageId,
看下MessageSender.sendTextMessage()方法,真正发送消息的是MessageSender.sendTextMessage方法:
//MessageSender.java
private static void sendTextMessage(Context context, Recipient recipient,
boolean forceSms, boolean keyExchange,
long messageId)
{
if (isLocalSelfSend(context, recipient, forceSms)) {
sendLocalTextSelf(context, messageId);
} else if (!forceSms && isPushTextSend(context, recipient, keyExchange)) {
sendTextPush(recipient, messageId);
} else {
sendSms(recipient, messageId);
}
}
这里会判断是发送给自己(走本地),还是发送TextPush,还是发送Sms。
//MessageSender.java
private static void sendTextPush(Recipient recipient, long messageId) {
JobManager jobManager = ApplicationDependencies.getJobManager();
jobManager.add(new PushTextSendJob(messageId, recipient));
}
添加一个PushTextSendJob到JobManager中。
发送消息的SendJob(比如PushTextSendJob),会根据messageId又从数据库读取数据,然后从新组装成MessageRecord(比如:SmsMessageRecord、MediaMmsMessageRecord等):
看下PushTextSendJob:
//PushTextSendJob.java
@Override
public void onPushSend() throws NoSuchMessageException, RetryLaterException {
log(TAG, "onPushSend()" );
ExpiringMessageManager expirationManager = ApplicationContext.getInstance(context).getExpiringMessageManager();
SmsDatabase database = DatabaseFactory.getSmsDatabase(context);
//根据messageId从数据库中读取数据并组装成SmsMessageRecord
SmsMessageRecord record = database.getMessage(messageId);
if (!record.isPending() && !record.isFailed()) {
warn(TAG, "Message " + messageId + " was already sent. Ignoring.");
return;
}
try {
log(TAG, "Sending message: " + messageId);
RecipientUtil.shareProfileIfFirstSecureMessage(context, record.getRecipient());
Recipient recipient = record.getRecipient().fresh();
byte[] profileKey = recipient.getProfileKey();
UnidentifiedAccessMode accessMode = recipient.getUnidentifiedAccessMode();
boolean unidentified = deliver(record);
...
}
调用deliver方法进行发送消息:
//PushTextSendJob.java
private boolean deliver(SmsMessageRecord message)
throws UntrustedIdentityException, InsecureFallbackApprovalException, RetryLaterException
{
try {
rotateSenderCertificateIfNecessary();
Recipient messageRecipient = message.getIndividualRecipient().fresh();
SignalServiceMessageSender messageSender = ApplicationDependencies.getSignalServiceMessageSender();
SignalServiceAddress address = getPushAddress(messageRecipient);
Optional<byte[]> profileKey = getProfileKey(messageRecipient);
Optional<UnidentifiedAccessPair> unidentifiedAccess = UnidentifiedAccessUtil.getAccessFor(context, messageRecipient);
log(TAG, "Have access key to use: " + unidentifiedAccess.isPresent());
//先将SmsMessageRecord转为SignalServiceDataMessage
//SmsMessageRecord是用于app业务层的页面展示以及数据库存储相关的,
//而SignalServiceDataMessage是用于消息传输层的消息发送的
SignalServiceDataMessage textSecureMessage = SignalServiceDataMessage.newBuilder()
.withTimestamp(message.getDateSent()) //发送的消息的发送时的时间戳
.withBody(message.getBody())//发送的消息的消息体
.withExpiration((int)(message.getExpiresIn() / 1000)) //发送的消息的过期时间
.withProfileKey(profileKey.orNull())
.asEndSessionMessage(message.isEndSession()) //发送的消息是否是终止会话的消息
.build();
//一、如果是发送给自己
if (Util.equals(TextSecurePreferences.getLocalUuid(context), address.getUuid().orNull())) {
Optional<UnidentifiedAccessPair> syncAccess = UnidentifiedAccessUtil.getAccessForSync(context);
//1.先转化为SignalServiceSyncMessage类型的消息
SignalServiceSyncMessage syncMessage = buildSelfSendSyncMessage(context, textSecureMessage, syncAccess);
//2.然后发送消息
messageSender.sendMessage(syncMessage, syncAccess);
return syncAccess.isPresent();
} else { //二、如果是发送给别人
return messageSender.sendMessage(address, unidentifiedAccess, textSecureMessage).getSuccess().isUnidentified();
}
} catch (UnregisteredUserException e) {
warn(TAG, "Failure", e);
throw new InsecureFallbackApprovalException(e);
} catch (IOException e) {
warn(TAG, "Failure", e);
throw new RetryLaterException(e);
}
}
先将SmsMessageRecord转为SignalServiceDataMessage,SmsMessageRecord是用于app业务层的页面展示以及数据库存储相关的,而SignalServiceDataMessage是用于消息传输层的消息发送的。
看下SignalServiceDataMessage这个类:
//SignalServiceDataMessage.java
/**
* Represents a decrypted Signal Service data message.
*/
public class SignalServiceDataMessage {
private final long timestamp;发送的消息时间戳
private final Optional<List<SignalServiceAttachment>> attachments;
private final Optional<String> body;//发送的消息消息体,比如发送的消息是123,那么body这个字段的值就是123
private final Optional<SignalServiceGroupContext> group;
private final Optional<byte[]> profileKey;
private final boolean endSession;//是否是终止会话的消息
private final boolean expirationUpdate;
private final int expiresInSeconds;
private final boolean profileKeyUpdate;
private final Optional<Quote> quote;
private final Optional<List<SharedContact>> contacts;
private final Optional<List<Preview>> previews;
private final Optional<Sticker> sticker;
private final boolean viewOnce;
private final Optional<Reaction> reaction;
private final Optional<RemoteDelete> remoteDelete;
...
注意SignalServiceDataMessage是没有消息类型type的字段的…
SmsMessageRecord转为SignalServiceDataMessage的过程中,SmsMessageRecord的type字段并没有被写入到SignalServiceDataMessage中,而是被丢弃了。
然后通过if (Util.equals(TextSecurePreferences.getLocalUuid(context), address.getUuid().orNull()))
判断消息是发送给自己还是发送给别人。
一、如果是发送给自己
会先转化为SignalServiceSyncMessage
类型的消息,然后再调用SignalServiceMessageSender的sendMessage方法进行发送消息,SignalServiceMessageSender是依赖包libsignal
中的代码:
//SignalServiceMessageSender.java
/**
* Send a message to a single recipient.
*
* @param recipient The message's destination.
* @param message The message.
* @throws UntrustedIdentityException
* @throws IOException
*/
public SendMessageResult sendMessage(SignalServiceAddress recipient,
Optional<UnidentifiedAccessPair> unidentifiedAccess,
SignalServiceDataMessage message)
throws UntrustedIdentityException, IOException
{
byte[] content = createMessageContent(message);
long timestamp = message.getTimestamp();
SendMessageResult result = sendMessage(recipient, getTargetUnidentifiedAccess(unidentifiedAccess), timestamp, content, false, null);
if (result.getSuccess() != null && result.getSuccess().isNeedsSync()) {
byte[] syncMessage = createMultiDeviceSentTranscriptContent(content, Optional.of(recipient), timestamp, Collections.singletonList(result), false);
sendMessage(localAddress, Optional.<UnidentifiedAccess>absent(), timestamp, syncMessage, false, null);
}
if (message.isEndSession()) {
if (recipient.getUuid().isPresent()) {
store.deleteAllSessions(recipient.getUuid().get().toString());
}
if (recipient.getNumber().isPresent()) {
store.deleteAllSessions(recipient.getNumber().get());
}
if (eventListener.isPresent()) {
eventListener.get().onSecurityEvent(recipient);
}
}
return result;
}
会调用sendMessage()方法:
//SignalServiceMessageSender.java
private SendMessageResult sendMessage(SignalServiceAddress recipient,
Optional<UnidentifiedAccess> unidentifiedAccess,
long timestamp,
byte[] content,
boolean online,
CancelationSignal cancelationSignal)
throws UntrustedIdentityException, IOException
{
long startTime = System.currentTimeMillis();
for (int i = 0; i < RETRY_COUNT; i++) {
if (cancelationSignal != null && cancelationSignal.isCanceled()) {
throw new CancelationException();
}
try {
//加密发送的消息
OutgoingPushMessageList messages = getEncryptedMessages(socket, recipient, unidentifiedAccess, timestamp, content, online);
if (cancelationSignal != null && cancelationSignal.isCanceled()) {
throw new CancelationException();
}
Optional<SignalServiceMessagePipe> pipe = this.pipe.get();
Optional<SignalServiceMessagePipe> unidentifiedPipe = this.unidentifiedPipe.get();
if (pipe.isPresent() && !unidentifiedAccess.isPresent()) {
try {
SendMessageResponse response = pipe.get().send(messages, Optional.absent()).get(10, TimeUnit.SECONDS);
Log.d(TAG, "[sendMessage] Completed over pipe in " + (System.currentTimeMillis() - startTime) + " ms and " + (i + 1) + " attempt(s)");
//若发送成功,则组装成结果,并返回
return SendMessageResult.success(recipient, false, response.getNeedsSync() || isMultiDevice.get());
} catch (IOException | ExecutionException | InterruptedException | TimeoutException e) {
Log.w(TAG, e);
Log.w(TAG, "[sendMessage] Pipe failed, falling back...");
}
} else if (unidentifiedPipe.isPresent() && unidentifiedAccess.isPresent()) {
try {
SendMessageResponse response = unidentifiedPipe.get().send(messages, unidentifiedAccess).get(10, TimeUnit.SECONDS);
Log.d(TAG, "[sendMessage] Completed over unidentified pipe in " + (System.currentTimeMillis() - startTime) + " ms and " + (i + 1) + " attempt(s)");
return SendMessageResult.success(recipient, true, response.getNeedsSync() || isMultiDevice.get());
} catch (IOException | ExecutionException | InterruptedException | TimeoutException e) {
Log.w(TAG, e);
Log.w(TAG, "[sendMessage] Unidentified pipe failed, falling back...");
}
}
if (cancelationSignal != null && cancelationSignal.isCanceled()) {
throw new CancelationException();
}
//真正的发送消息
SendMessageResponse response = socket.sendMessage(messages, unidentifiedAccess);
Log.d(TAG, "[sendMessage] Completed over REST in " + (System.currentTimeMillis() - startTime) + " ms and " + (i + 1) + " attempt(s)");
return SendMessageResult.success(recipient, unidentifiedAccess.isPresent(), response.getNeedsSync() || isMultiDevice.get());
} catch (InvalidKeyException ike) {
Log.w(TAG, ike);
unidentifiedAccess = Optional.absent();
} catch (AuthorizationFailedException afe) {
Log.w(TAG, afe);
if (unidentifiedAccess.isPresent()) {
unidentifiedAccess = Optional.absent();
} else {
throw afe;
}
} catch (MismatchedDevicesException mde) {
Log.w(TAG, mde);
handleMismatchedDevices(socket, recipient, mde.getMismatchedDevices());
} catch (StaleDevicesException ste) {
Log.w(TAG, ste);
handleStaleDevices(recipient, ste.getStaleDevices());
}
}
throw new IOException("Failed to resolve conflicts after 3 attempts!");
}
注意这里有失败重试机制,RETRY_COUNT
,默认值是4
//SignalServiceMessageSender.java
private static final int RETRY_COUNT = 4;
即:若发送失败会重试4次。
二、如果是发送给别人
执行messageSender.sendMessage(address, unidentifiedAccess, textSecureMessage).getSuccess().isUnidentified();
看下messageSender.sendMessage(address, unidentifiedAccess, textSecureMessage):
//SignalServiceMessageSender.java
/**
* Send a message to a single recipient.
*
* @param recipient The message's destination.
* @param message The message.
* @throws UntrustedIdentityException
* @throws IOException
*/
public SendMessageResult sendMessage(SignalServiceAddress recipient,
Optional<UnidentifiedAccessPair> unidentifiedAccess,
SignalServiceDataMessage message)
throws UntrustedIdentityException, IOException
{
//1.将消息序列化,并转为字节数组
byte[] content = createMessageContent(message);
long timestamp = message.getTimestamp();
//2.发送消息
SendMessageResult result = sendMessage(recipient, getTargetUnidentifiedAccess(unidentifiedAccess), timestamp, content, false, null);
//3.如果消息发送成功,并且需要多设备同步,则发送同步消息
if (result.getSuccess() != null && result.getSuccess().isNeedsSync()) {
byte[] syncMessage = createMultiDeviceSentTranscriptContent(content, Optional.of(recipient), timestamp, Collections.singletonList(result), false);
sendMessage(localAddress, Optional.<UnidentifiedAccess>absent(), timestamp, syncMessage, false, null);
}
//4.如果是断开会话的消息,则进行相关的处理
// endSession:Flag indicating whether this message should close a session.
// 更多相关信息可以查看SignalServiceDataMessage的构造方法
if (message.isEndSession()) {
if (recipient.getUuid().isPresent()) {
store.deleteAllSessions(recipient.getUuid().get().toString());
}
if (recipient.getNumber().isPresent()) {
store.deleteAllSessions(recipient.getNumber().get());
}
if (eventListener.isPresent()) {
eventListener.get().onSecurityEvent(recipient);
}
}
return result;
}
1.将消息序列化,并转为字节数组
重点分析将消息序列化,并转为字节数组的createMessageContent()方法:
private byte[] createMessageContent(SignalServiceDataMessage message) throws IOException {
//Content是protocol buffer生成的类,位于SignalServiceProtos.java中
Content.Builder container = Content.newBuilder();
//DataMessage是protocol buffer生成的类,位于SignalServiceProtos.java中
DataMessage.Builder builder = DataMessage.newBuilder();
List<AttachmentPointer> pointers = createAttachmentPointers(message.getAttachments());
if (!pointers.isEmpty()) {
builder.addAllAttachments(pointers);
for (AttachmentPointer pointer : pointers) {
if (pointer.getAttachmentIdentifierCase() == AttachmentPointer.AttachmentIdentifierCase.CDNKEY || pointer.getCdnNumber() != 0) {
builder.setRequiredProtocolVersion(Math.max(DataMessage.ProtocolVersion.CDN_SELECTOR_ATTACHMENTS_VALUE, builder.getRequiredProtocolVersion()));
break;
}
}
}
if (message.getBody().isPresent()) {
builder.setBody(message.getBody().get());
}
if (message.getGroupContext().isPresent()) {
SignalServiceGroupContext groupContext = message.getGroupContext().get();
if (groupContext.getGroupV1().isPresent()) {
builder.setGroup(createGroupContent(groupContext.getGroupV1().get()));
}
if (groupContext.getGroupV2().isPresent()) {
builder.setGroupV2(createGroupContent(groupContext.getGroupV2().get()));
}
}
if (message.isEndSession()) {
builder.setFlags(DataMessage.Flags.END_SESSION_VALUE);
}
if (message.isExpirationUpdate()) {
builder.setFlags(DataMessage.Flags.EXPIRATION_TIMER_UPDATE_VALUE);
}
if (message.isProfileKeyUpdate()) {
builder.setFlags(DataMessage.Flags.PROFILE_KEY_UPDATE_VALUE);
}
if (message.getExpiresInSeconds() > 0) {
builder.setExpireTimer(message.getExpiresInSeconds());
}
if (message.getProfileKey().isPresent()) {
builder.setProfileKey(ByteString.copyFrom(message.getProfileKey().get()));
}
if (message.getQuote().isPresent()) {
DataMessage.Quote.Builder quoteBuilder = DataMessage.Quote.newBuilder()
.setId(message.getQuote().get().getId())
.setText(message.getQuote().get().getText());
if (message.getQuote().get().getAuthor().getUuid().isPresent()) {
quoteBuilder = quoteBuilder.setAuthorUuid(message.getQuote().get().getAuthor().getUuid().get().toString());
}
if (message.getQuote().get().getAuthor().getNumber().isPresent()) {
quoteBuilder = quoteBuilder.setAuthorE164(message.getQuote().get().getAuthor().getNumber().get());
}
for (SignalServiceDataMessage.Quote.QuotedAttachment attachment : message.getQuote().get().getAttachments()) {
DataMessage.Quote.QuotedAttachment.Builder quotedAttachment = DataMessage.Quote.QuotedAttachment.newBuilder();
quotedAttachment.setContentType(attachment.getContentType());
if (attachment.getFileName() != null) {
quotedAttachment.setFileName(attachment.getFileName());
}
if (attachment.getThumbnail() != null) {
quotedAttachment.setThumbnail(createAttachmentPointer(attachment.getThumbnail().asStream()));
}
quoteBuilder.addAttachments(quotedAttachment);
}
builder.setQuote(quoteBuilder);
}
if (message.getSharedContacts().isPresent()) {
builder.addAllContact(createSharedContactContent(message.getSharedContacts().get()));
}
if (message.getPreviews().isPresent()) {
for (SignalServiceDataMessage.Preview preview : message.getPreviews().get()) {
DataMessage.Preview.Builder previewBuilder = DataMessage.Preview.newBuilder();
previewBuilder.setTitle(preview.getTitle());
previewBuilder.setUrl(preview.getUrl());
if (preview.getImage().isPresent()) {
if (preview.getImage().get().isStream()) {
previewBuilder.setImage(createAttachmentPointer(preview.getImage().get().asStream()));
} else {
previewBuilder.setImage(createAttachmentPointer(preview.getImage().get().asPointer()));
}
}
builder.addPreview(previewBuilder.build());
}
}
if (message.getSticker().isPresent()) {
DataMessage.Sticker.Builder stickerBuilder = DataMessage.Sticker.newBuilder();
stickerBuilder.setPackId(ByteString.copyFrom(message.getSticker().get().getPackId()));
stickerBuilder.setPackKey(ByteString.copyFrom(message.getSticker().get().getPackKey()));
stickerBuilder.setStickerId(message.getSticker().get().getStickerId());
if (message.getSticker().get().getAttachment().isStream()) {
stickerBuilder.setData(createAttachmentPointer(message.getSticker().get().getAttachment().asStream()));
} else {
stickerBuilder.setData(createAttachmentPointer(message.getSticker().get().getAttachment().asPointer()));
}
builder.setSticker(stickerBuilder.build());
}
if (message.isViewOnce()) {
builder.setIsViewOnce(message.isViewOnce());
builder.setRequiredProtocolVersion(Math.max(DataMessage.ProtocolVersion.VIEW_ONCE_VIDEO_VALUE, builder.getRequiredProtocolVersion()));
}
if (message.getReaction().isPresent()) {
DataMessage.Reaction.Builder reactionBuilder = DataMessage.Reaction.newBuilder()
.setEmoji(message.getReaction().get().getEmoji())
.setRemove(message.getReaction().get().isRemove())
.setTargetSentTimestamp(message.getReaction().get().getTargetSentTimestamp());
if (message.getReaction().get().getTargetAuthor().getNumber().isPresent()) {
reactionBuilder.setTargetAuthorE164(message.getReaction().get().getTargetAuthor().getNumber().get());
}
if (message.getReaction().get().getTargetAuthor().getUuid().isPresent()) {
reactionBuilder.setTargetAuthorUuid(message.getReaction().get().getTargetAuthor().getUuid().get().toString());
}
builder.setReaction(reactionBuilder.build());
builder.setRequiredProtocolVersion(Math.max(DataMessage.ProtocolVersion.REACTIONS_VALUE, builder.getRequiredProtocolVersion()));
}
if (message.getRemoteDelete().isPresent()) {
DataMessage.Delete delete = DataMessage.Delete.newBuilder()
.setTargetSentTimestamp(message.getRemoteDelete().get().getTargetSentTimestamp())
.build();
builder.setDelete(delete);
}
builder.setTimestamp(message.getTimestamp());
return container.setDataMessage(builder).build().toByteArray();
}
2.发送消息
即这段代码:
SendMessageResult result = sendMessage(recipient, getTargetUnidentifiedAccess(unidentifiedAccess), timestamp, content, false, null);
这个在 一、如果是发送给自己 这小节中已经分析过了。
3.消息发送完的回调
onMessageSent();
三、消息发送完成的页面更新工作
onPostExecute中调用sendComplete(result)
:
protected void sendComplete(long threadId) {
boolean refreshFragment = (threadId != this.threadId);
this.threadId = threadId;
if (fragment == null || !fragment.isVisible() || isFinishing()) {
return;
}
fragment.setLastSeen(0);
//根据threadId判断是否要重新加载fragment的数据
if (refreshFragment) {
fragment.reload(recipient.get(), threadId);
ApplicationDependencies.getMessageNotifier().setVisibleThread(threadId);
}
fragment.scrollToBottom();
attachmentManager.cleanup();
updateLinkPreviewState();
}
进行消息发送完成后的处理工作。
这里会根据发送完成后的threadId,判断是否要重新加载fragment的数据,因为有可能当前会话是一个新建的会话,那么点击发送按钮第一次在页面显示发送的消息时threadId=-1,发送消息时会会新建一个threadId:
//MessageSender.java
public static long send(final Context context,
final OutgoingTextMessage message,
final long threadId,
final boolean forceSms,
final SmsDatabase.InsertListener insertListener)
{
SmsDatabase database = DatabaseFactory.getSmsDatabase(context);
Recipient recipient = message.getRecipient();
boolean keyExchange = message.isKeyExchange();
long allocatedThreadId;
if (threadId == -1) {//如果threadId == -1,则新建一个threadId
allocatedThreadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipient);
} else {
allocatedThreadId = threadId;
}
long messageId = database.insertMessageOutbox(allocatedThreadId, message, forceSms, System.currentTimeMillis(), insertListener);
sendTextMessage(context, recipient, forceSms, keyExchange, messageId);
onMessageSent();
return allocatedThreadId;
}
那么发送完成后,调用sendComplete(result)
时threadId就不是-1了,而是新建的threadId,这时候会重新加载fragment的数据。
这时候消息列表中的MessageRecord的很多字段都有数据了。
接收消息
接收到消息后要根据不同消息的数据结构设置消息的type字段,是在PushProcessMessageJob的handleMessage方法中:
//PushProcessMessageJob.java
private void handleMessage(@Nullable SignalServiceContent content, @NonNull Optional<Long> smsMessageId)
throws VerificationFailedException, IOException, InvalidGroupStateException, GroupChangeBusyException
{
try {
GroupDatabase groupDatabase = DatabaseFactory.getGroupDatabase(context);
if (content == null || shouldIgnore(content)) {
Log.i(TAG, "Ignoring message.");
return;
}
if (content.getDataMessage().isPresent()) {
SignalServiceDataMessage message = content.getDataMessage().get();
boolean isMediaMessage = message.getAttachments().isPresent() || message.getQuote().isPresent() || message.getSharedContacts().isPresent() || message.getPreviews().isPresent() || message.getSticker().isPresent();
Optional<GroupId> groupId = GroupUtil.idFromGroupContext(message.getGroupContext());
boolean isGv2Message = groupId.isPresent() && groupId.get().isV2();
if (isGv2Message) {
GroupMasterKey groupMasterKey = message.getGroupContext().get().getGroupV2().get().getMasterKey();
if (!groupV2PreProcessMessage(content, groupMasterKey, message.getGroupContext().get().getGroupV2().get())) {
Log.i(TAG, "Ignoring GV2 message for group we are not currently in " + groupId);
return;
}
GroupId.V2 groupIdV2 = groupId.get().requireV2();
Recipient sender = Recipient.externalPush(context, content.getSender());
if (!groupDatabase.isCurrentMember(groupIdV2, sender.getId())) {
Log.i(TAG, "Ignoring GV2 message from member not in group " + groupId);
return;
}
}
if (isInvalidMessage(message)) handleInvalidMessage(content.getSender(), content.getSenderDevice(), groupId, content.getTimestamp(), smsMessageId);
else if (message.isEndSession()) handleEndSessionMessage(content, smsMessageId);
else if (message.isGroupV1Update()) handleGroupV1Message(content, message, smsMessageId, groupId.get().requireV1());
else if (message.isExpirationUpdate()) handleExpirationUpdate(content, message, smsMessageId, groupId);
else if (message.getReaction().isPresent()) handleReaction(content, message);
else if (message.getRemoteDelete().isPresent()) handleRemoteDelete(content, message);
else if (isMediaMessage) handleMediaMessage(content, message, smsMessageId);
else if (message.getBody().isPresent()) handleTextMessage(content, message, smsMessageId, groupId);
...
handleTextMessage方法是处理文本类型消息的:
private void handleTextMessage(@NonNull SignalServiceContent content,
@NonNull SignalServiceDataMessage message,
@NonNull Optional<Long> smsMessageId,
@NonNull Optional<GroupId> groupId)
throws StorageFailedException, BadGroupIdException
{
SmsDatabase database = DatabaseFactory.getSmsDatabase(context);
String body = message.getBody().isPresent() ? message.getBody().get() : "";
Recipient recipient = getMessageDestination(content, message);
if (message.getExpiresInSeconds() != recipient.getExpireMessages()) {
handleExpirationUpdate(content, message, Optional.absent(), groupId);
}
Long threadId;
if (smsMessageId.isPresent() && !message.getGroupContext().isPresent()) {
threadId = database.updateBundleMessageBody(smsMessageId.get(), body).second;
} else {
notifyTypingStoppedFromIncomingMessage(recipient, content.getSender(), content.getSenderDevice());
/* IncomingTextMessage textMessage = new IncomingTextMessage(Recipient.externalPush(context, content.getSender()).getId(),
content.getSenderDevice(),
message.getTimestamp(),
content.getServerReceivedTimestamp(),
body,
groupId,
message.getExpiresInSeconds() * 1000L,
content.isNeedsReceipt());*/
Log.i(TAG, "message.getTransferMessage().isPresent()=" +message.getTransferMessage().isPresent());
IncomingTextMessage textMessage = new IncomingTextMessage(Recipient.externalPush(context, content.getSender()).getId(),
content.getSenderDevice(),
message.getTimestamp(),
content.getServerReceivedTimestamp(),
body,
groupId,
message.getExpiresInSeconds() * 1000L,
content.isNeedsReceipt(),
message.getTransferMessage().isPresent() );
Log.i(TAG, "textMessage.isTransferMessage()=" + textMessage.isTransferMessage() );
textMessage = new IncomingEncryptedMessage(textMessage, body);
//1.消息入库。调用insertMessageInbox()将接收到的消息插入数据库。这个方法会根据参数textMessage是否是transfer message进行更新type值
Optional<InsertResult> insertResult = database.insertMessageInbox(textMessage);
if (insertResult.isPresent()) threadId = insertResult.get().getThreadId();
else threadId = null;
if (smsMessageId.isPresent()) database.deleteMessage(smsMessageId.get());
}
//2.更新通知
if (threadId != null) {
ApplicationDependencies.getMessageNotifier().updateNotification(context, threadId);
}
}
1.消息入库
//1.调用insertMessageInbox()将接收到的消息插入数据库
Optional<InsertResult> insertResult = database.insertMessageInbox(textMessage);
看下SmsDatabase的insertMessageInbox方法:
//SmsDatabase.java
protected Optional<InsertResult> insertMessageInbox(IncomingTextMessage message, long type) {
if (message.isJoined()) {
type = (type & (Types.TOTAL_MASK - Types.BASE_TYPE_MASK)) | Types.JOINED_TYPE;
} else if (message.isPreKeyBundle()) {
type |= Types.KEY_EXCHANGE_BIT | Types.KEY_EXCHANGE_BUNDLE_BIT;
} else if (message.isSecureMessage()) {
type |= Types.SECURE_MESSAGE_BIT;
} else if (message.isGroup()) {
IncomingGroupUpdateMessage incomingGroupUpdateMessage = (IncomingGroupUpdateMessage) message;
type |= Types.SECURE_MESSAGE_BIT;
if (incomingGroupUpdateMessage.isGroupV2()) type |= Types.GROUP_V2_BIT | Types.GROUP_UPDATE_BIT;
else if (incomingGroupUpdateMessage.isUpdate()) type |= Types.GROUP_UPDATE_BIT;
else if (incomingGroupUpdateMessage.isQuit()) type |= Types.GROUP_QUIT_BIT;
} else if (message.isEndSession()) {
type |= Types.SECURE_MESSAGE_BIT;
type |= Types.END_SESSION_BIT;
}
if (message.isPush()) type |= Types.PUSH_MESSAGE_BIT;
if (message.isIdentityUpdate()) type |= Types.KEY_EXCHANGE_IDENTITY_UPDATE_BIT;
if (message.isContentPreKeyBundle()) type |= Types.KEY_EXCHANGE_CONTENT_FORMAT;
if (message.isIdentityVerified()) type |= Types.KEY_EXCHANGE_IDENTITY_VERIFIED_BIT;
else if (message.isIdentityDefault()) type |= Types.KEY_EXCHANGE_IDENTITY_DEFAULT_BIT;
//transfer message
/* if(message.isTransferMessage()) {
type |= Types.TRANSFER_MESSAGE_BIT;
}*/
...
//更新数据库
if (!message.isIdentityUpdate() && !message.isIdentityVerified() && !message.isIdentityDefault()) {
DatabaseFactory.getThreadDatabase(context).update(threadId, true);
}
//ThreadDatabase.java
public boolean update(long threadId, boolean unarchive) {
MmsSmsDatabase mmsSmsDatabase = DatabaseFactory.getMmsSmsDatabase(context);
long count = mmsSmsDatabase.getConversationCount(threadId);
if (count == 0) {
deleteThread(threadId);
notifyConversationListListeners();
return true;
}
MmsSmsDatabase.Reader reader = null;
try {
//读取数据库,转为MessageRecord数据
reader = mmsSmsDatabase.readerFor(mmsSmsDatabase.getConversationSnippet(threadId));
MessageRecord record;
if (reader != null && (record = reader.getNext()) != null) {
//1.更新数据库的会话表(表名是thread)
updateThread(threadId, count, ThreadBodyUtil.getFormattedBodyFor(context, record), getAttachmentUriFor(record),
getContentTypeFor(record), getExtrasFor(record),
record.getTimestamp(), record.getDeliveryStatus(), record.getDeliveryReceiptCount(),
record.getType(), unarchive, record.getExpiresIn(), record.getReadReceiptCount());
//2.通知页面的会话列表更新
notifyConversationListListeners();
return false;
} else {
deleteThread(threadId);
notifyConversationListListeners();
return true;
}
} finally {
if (reader != null)
reader.close();
}
}
看下notifyConversationListListeners()方法:
//Database.java
protected void notifyConversationListListeners() {
context.getContentResolver().notifyChange(DatabaseContentProviders.ConversationList.CONTENT_URI, null);
}
是通过ContentResolver来进行数据更新通知的,看下ConversationListViewModel的构造方法:
//ConversationListViewModel.java
...
this.observer = new ContentObserver(new Handler()) {
@Override
public void onChange(boolean selfChange) {
if (!TextUtils.isEmpty(getLastQuery())) {
searchRepository.query(getLastQuery(), searchResult::postValue);
}
if (!isArchived) {
updateArchivedCount();
}
}
};
...
private ConversationListViewModel(@NonNull Application application, @NonNull SearchRepository searchRepository, boolean isArchived) {
...
application.getContentResolver().registerContentObserver(DatabaseContentProviders.ConversationList.CONTENT_URI, true, observer);
...
ConversationListViewModel在创建的时候注册了ContentObserver,Uri也是DatabaseContentProviders.ConversationList.CONTENT_URI
,因此可以接收到数据通知。
总结:
- 消息入库
- 更新页面消息
调用栈:
java.lang.Exception
at securesms.database.SmsDatabase.parseTransferMessageModel(SmsDatabase.java:1102)
at securesms.database.SmsDatabase$Reader.getCurrent(SmsDatabase.java:1051)
at securesms.database.MmsSmsDatabase$Reader.getCurrent(MmsSmsDatabase.java:601)
at securesms.database.MmsSmsDatabase$Reader.getNext(MmsSmsDatabase.java:594)
at securesms.database.ThreadDatabase.update(ThreadDatabase.java:798)
at securesms.database.SmsDatabase.insertMessageInbox(SmsDatabase.java:722)
at securesms.database.SmsDatabase.insertMessageInbox(SmsDatabase.java:740)
at securesms.jobs.PushProcessMessageJob.handleTextMessage(PushProcessMessageJob.java:1273)
at securesms.jobs.PushProcessMessageJob.handleMessage(PushProcessMessageJob.java:367)
at securesms.jobs.PushProcessMessageJob.onRun(PushProcessMessageJob.java:297)
at securesms.jobs.BaseJob.run(BaseJob.java:25)
at securesms.jobmanager.JobRunner.run(JobRunner.java:85)
at securesms.jobmanager.JobRunner.run(JobRunner.java:48)
2.更新通知
//2.更新通知
if (threadId != null) {
ApplicationDependencies.getMessageNotifier().updateNotification(context, threadId);
}
调用的是OptimizedMessageNotifier的updateNotification()方法:
//OptimizedMessageNotifier.java
...
@Override
public void updateNotification(@NonNull Context context, long threadId) {
limiter.run(() -> wrapped.updateNotification(context, threadId));
}
...
调用栈:
java.lang.Exception
at securesms.database.SmsDatabase.parseTransferMessageModel(SmsDatabase.java:1102)
at securesms.database.SmsDatabase$Reader.getCurrent(SmsDatabase.java:1051)
at securesms.database.MmsSmsDatabase$Reader.getCurrent(MmsSmsDatabase.java:601)
at securesms.database.MmsSmsDatabase$Reader.getNext(MmsSmsDatabase.java:594)
at securesms.notifications.DefaultMessageNotifier.constructNotificationState(DefaultMessageNotifier.java:496)
at securesms.notifications.DefaultMessageNotifier.updateNotification(DefaultMessageNotifier.java:290)
at securesms.notifications.DefaultMessageNotifier.updateNotification(DefaultMessageNotifier.java:264)
at securesms.notifications.DefaultMessageNotifier.updateNotification(DefaultMessageNotifier.java:235)
at securesms.notifications.OptimizedMessageNotifier.lambda$updateNotification$1$OptimizedMessageNotifier(OptimizedMessageNotifier.java:59)
at securesms.notifications.-$$Lambda$OptimizedMessageNotifier$H8U95QOp-FevnqlAaTesdmN52uY.run(Unknown Source:6)
at android.os.Handler.handleCallback(Handler.java:938)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:223)
at android.os.HandlerThread.run(HandlerThread.java:67)
会话页面的消息的数据
//ConversationViewModel.java
class ConversationViewModel extends ViewModel {
private static final String TAG = Log.tag(ConversationViewModel.class);
private final Application context;
private final MediaRepository mediaRepository;
private final ConversationRepository conversationRepository;
private final MutableLiveData<List<Media>> recentMedia;
private final MutableLiveData<Long> threadId;
private final LiveData<PagedList<MessageRecord>> messages; //消息数据
private final LiveData<ConversationData> conversationMetadata;
private final Invalidator invalidator;
private int jumpToPosition;
...
接收到已读消息
securesms I/Database: notifyConversationListeners()
java.lang.Exception
at securesms.database.Database.notifyConversationListeners(Database.java:47)
at securesms.database.SmsDatabase.incrementReceiptCount(SmsDatabase.java:445)
at securesms.database.MmsSmsDatabase.incrementReadReceiptCount(MmsSmsDatabase.java:303)
at securesms.jobs.PushProcessMessageJob.handleReadReceipt(PushProcessMessageJob.java:1502)
at securesms.jobs.PushProcessMessageJob.handleMessage(PushProcessMessageJob.java:416)
at securesms.jobs.PushProcessMessageJob.onRun(PushProcessMessageJob.java:297)
at securesms.jobs.BaseJob.run(BaseJob.java:25)
at securesms.jobmanager.JobRunner.run(JobRunner.java:85)
at securesms.jobmanager.JobRunner.run(JobRunner.java:48)
先调用incrementReceiptCount更新数据库。
然后通知到会话页面:
//Database.java
...
protected void notifyConversationListeners(long threadId) {
Log.i("Database", "notifyConversationListeners()",new Exception());
context.getContentResolver().notifyChange(DatabaseContentProviders.Conversation.getUriForThread(threadId), null);
notifyVerboseConversationListeners(threadId);
}
protected void notifyVerboseConversationListeners(long threadId) {
context.getContentResolver().notifyChange(DatabaseContentProviders.Conversation.getVerboseUriForThread(threadId), null);
}
...
是通过ContentResolver进行通知的,uri是CONTENT_URI_STRING,ConversationDataSource注册ContentObserver时使用也是这个uri:
//ConversationDataSource.java
...
private ConversationDataSource(@NonNull Context context,
long threadId,
@NonNull Invalidator invalidator)
{
this.context = context;
this.threadId = threadId;
ContentObserver contentObserver = new ContentObserver(null) {
@Override
public void onChange(boolean selfChange) {
invalidate();
context.getContentResolver().unregisterContentObserver(this);
}
};
invalidator.observe(() -> {
invalidate();
context.getContentResolver().unregisterContentObserver(contentObserver);
});
context.getContentResolver().registerContentObserver(DatabaseContentProviders.Conversation.getUriForThread(threadId), true, contentObserver);
}
...
因此可以收到数据。ConversationDataSource是会话页面的数据源。
调用栈:
2021-05-12 14:20:21.657 17743-18150/securesms D/ConversationDataSource: [Initial Load] 3 ms | thread: 2, start: 0, size: 50
2021-05-12 14:20:21.657 17743-17743/securesms I/ConversationFragment: submitList
2021-05-12 14:30:09.975 17743-18150/securesms I/ConversationDataSource: [Initial Load]
java.lang.Exception
at securesms.conversation.ConversationDataSource.loadInitial(ConversationDataSource.java:82)
at androidx.paging.PositionalDataSource.dispatchLoadInitial(PositionalDataSource.java:286)
at androidx.paging.TiledPagedList.<init>(TiledPagedList.java:107)
at androidx.paging.PagedList.create(PagedList.java:229)
at androidx.paging.PagedList$Builder.build(PagedList.java:388)
at androidx.paging.LivePagedListBuilder$1.compute(LivePagedListBuilder.java:206)
at androidx.paging.LivePagedListBuilder$1.compute(LivePagedListBuilder.java:171)
at androidx.lifecycle.ComputableLiveData$2.run(ComputableLiveData.java:101)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
at java.lang.Thread.run(Thread.java:923)
//ConversationDataSource.java
@Override
public void loadInitial(@NonNull LoadInitialParams params, @NonNull LoadInitialCallback<MessageRecord> callback) {
long start = System.currentTimeMillis();
MmsSmsDatabase db = DatabaseFactory.getMmsSmsDatabase(context);
List<MessageRecord> records = new ArrayList<>(params.requestedLoadSize);
int totalCount = db.getConversationCount(threadId);
int effectiveCount = params.requestedStartPosition;
//1.从数据库读取数据
try (MmsSmsDatabase.Reader reader = db.readerFor(db.getConversation(threadId, params.requestedStartPosition, params.requestedLoadSize))) {
MessageRecord record;
while ((record = reader.getNext()) != null && effectiveCount < totalCount && !isInvalid()) {
records.add(record);
effectiveCount++;
}
}
//2.将数据回调出去
if (!isInvalid()) {
//SizeFixResult<MessageRecord> result 就是本次会话页面上的所有消息
SizeFixResult<MessageRecord> result = SizeFixResult.ensureMultipleOfPageSize(records, params.requestedStartPosition, params.pageSize, totalCount);
callback.onResult(result.getItems(), params.requestedStartPosition, result.getTotal());
}
Log.d(TAG, "[Initial Load] " + (System.currentTimeMillis() - start) + " ms | thread: " + threadId + ", start: " + params.requestedStartPosition + ", size: " + params.requestedLoadSize + (isInvalid() ? " -- invalidated" : ""));
}
先从数据库读取数据,转化为 List<MessageRecord> records
,然后调用 callback.onResult
将数据回调出去。
回调会话页面ConversationFragment注册的观察者的回调方法:
//ConversationFragment.java
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle bundle) {
...
this.conversationViewModel = ViewModelProviders.of(requireActivity(), new ConversationViewModel.Factory()).get(ConversationViewModel.class);
conversationViewModel.getMessages().observe(this, list -> {
if (getListAdapter() != null && !list.getDataSource().isInvalid()) {
Log.i(TAG, "submitList");
getListAdapter().submitList(list);
} else if (list.getDataSource().isInvalid()) {
Log.i(TAG, "submitList skipped an invalid list");
}
});
...
执行getListAdapter().submitList(list);
更新RecycleView的数据。
总结:
1.先将消息入库
2.通知页面更新
3.页面的数据源会从数据库中读取数据,转为List<MessageRecord> records
4.将List<MessageRecord> records
数据通过回调通知出去
5.会话页面数据更新
注意:会话页面的数据ConversationViewModel.messages
是来自于ConversationDataSource
(继承自androidx.paging:paging-common的PositionalDataSource)的:
//ConversationViewModel.java
private ConversationViewModel() {
...
LiveData<Pair<Long, PagedList<MessageRecord>>> messagesForThreadId = Transformations.switchMap(metadata, data -> {
DataSource.Factory<Integer, MessageRecord> factory = new ConversationDataSource.Factory(context, data.getThreadId(), invalidator);
PagedList.Config config = new PagedList.Config.Builder()
.setPageSize(25)
.setInitialLoadSizeHint(25)
.build();
final int startPosition;
if (data.shouldJumpToMessage()) {
startPosition = data.getJumpToPosition();
} else if (data.isMessageRequestAccepted() && data.shouldScrollToLastSeen()) {
startPosition = data.getLastSeenPosition();
} else if (data.isMessageRequestAccepted()) {
startPosition = data.getLastScrolledPosition();
} else {
startPosition = data.getThreadSize();
}
Log.d(TAG, "Starting at position startPosition: " + startPosition + " jumpToPosition: " + jumpToPosition + " lastSeenPosition: " + data.getLastSeenPosition() + " lastScrolledPosition: " + data.getLastScrolledPosition());
return Transformations.map(new LivePagedListBuilder<>(factory, config).setFetchExecutor(ConversationDataSource.EXECUTOR)
.setInitialLoadKey(Math.max(startPosition, 0))
.build(),
input -> new Pair<>(data.getThreadId(), input));
});
this.messages = Transformations.map(messagesForThreadId, Pair::second);
...
消息发送过程中几个消息结构相关的类
1.OutgoingTextMessage:用于页面展示(展示时还是会将OutgoingTextMessage转为SmsMessageRecord),以及发送前的消息入库
页面展示,将消息放入聊天列表:
//ConversationFragment.java
public long stageOutgoingMessage(OutgoingTextMessage message) {
MessageRecord messageRecord = DatabaseFactory.getSmsDatabase(getContext()).readerFor(message, threadId).getCurrent();
if (getListAdapter() != null) {
clearHeaderIfNotTyping(getListAdapter());
setLastSeen(0);
getListAdapter().addFastRecord(messageRecord);
list.post(() -> list.scrollToPosition(0));
}
return messageRecord.getId();
}
发送前的消息入库:
//MessageSender.java
public static long send(final Context context,
final OutgoingTextMessage message,
final long threadId,
final boolean forceSms,
final SmsDatabase.InsertListener insertListener)
{
...
long messageId = database.insertMessageOutbox(allocatedThreadId, message, forceSms, System.currentTimeMillis(), insertListener);
...
}
2.SmsMessageRecord:发送消息的线程任务(比如PushTextSendJob)从数据库中取出消息数据重新组装成消息的数据结构
OutgoingTextMessage转为SmsMessageRecord是SmsDatabase的getCurrent()方法
//SmsDatabase.java
...
//读取OutgoingMessage,转为MessageRecord
public static class OutgoingMessageReader {
private final OutgoingTextMessage message;
private final long id;
private final long threadId;
public OutgoingMessageReader(OutgoingTextMessage message, long threadId) {
this.message = message;
this.threadId = threadId;
this.id = new SecureRandom().nextLong();
}
public MessageRecord getCurrent() {
return new SmsMessageRecord(id,
message.getMessageBody(),
message.getRecipient(),
message.getRecipient(),
1,
System.currentTimeMillis(),
System.currentTimeMillis(),
-1,
0,
message.isSecureMessage() ? MmsSmsColumns.Types.getOutgoingEncryptedMessageType() : MmsSmsColumns.Types.getOutgoingSmsMessageType(),
threadId,
0,
new LinkedList<>(),
message.getSubscriptionId(),
message.getExpiresIn(),
System.currentTimeMillis(),
0,
false,
Collections.emptyList(),
false, message.isTransferMessage()? message.parseTransferMessage(): null );
}
}
//SmsDatabase.java
...
//读取数据库数据,转为MessageRecord
public class Reader {
private final Cursor cursor;
public Reader(Cursor cursor) {
this.cursor = cursor;
}
public SmsMessageRecord getNext() {
if (cursor == null || !cursor.moveToNext())
return null;
return getCurrent();
}
public int getCount() {
if (cursor == null) return 0;
else return cursor.getCount();
}
public SmsMessageRecord getCurrent() {
long messageId = cursor.getLong(cursor.getColumnIndexOrThrow(SmsDatabase.ID));
long recipientId = cursor.getLong(cursor.getColumnIndexOrThrow(SmsDatabase.RECIPIENT_ID));
int addressDeviceId = cursor.getInt(cursor.getColumnIndexOrThrow(SmsDatabase.ADDRESS_DEVICE_ID));
long type = cursor.getLong(cursor.getColumnIndexOrThrow(SmsDatabase.TYPE));
long dateReceived = cursor.getLong(cursor.getColumnIndexOrThrow(SmsDatabase.NORMALIZED_DATE_RECEIVED));
long dateSent = cursor.getLong(cursor.getColumnIndexOrThrow(SmsDatabase.NORMALIZED_DATE_SENT));
long dateServer = cursor.getLong(cursor.getColumnIndexOrThrow(SmsDatabase.DATE_SERVER));
long threadId = cursor.getLong(cursor.getColumnIndexOrThrow(SmsDatabase.THREAD_ID));
int status = cursor.getInt(cursor.getColumnIndexOrThrow(SmsDatabase.STATUS));
int deliveryReceiptCount = cursor.getInt(cursor.getColumnIndexOrThrow(SmsDatabase.DELIVERY_RECEIPT_COUNT));
int readReceiptCount = cursor.getInt(cursor.getColumnIndexOrThrow(SmsDatabase.READ_RECEIPT_COUNT));
String mismatchDocument = cursor.getString(cursor.getColumnIndexOrThrow(SmsDatabase.MISMATCHED_IDENTITIES));
int subscriptionId = cursor.getInt(cursor.getColumnIndexOrThrow(SmsDatabase.SUBSCRIPTION_ID));
long expiresIn = cursor.getLong(cursor.getColumnIndexOrThrow(SmsDatabase.EXPIRES_IN));
long expireStarted = cursor.getLong(cursor.getColumnIndexOrThrow(SmsDatabase.EXPIRE_STARTED));
String body = cursor.getString(cursor.getColumnIndexOrThrow(SmsDatabase.BODY));
boolean unidentified = cursor.getInt(cursor.getColumnIndexOrThrow(SmsDatabase.UNIDENTIFIED)) == 1;
boolean remoteDelete = cursor.getInt(cursor.getColumnIndexOrThrow(SmsDatabase.REMOTE_DELETED)) == 1;
List<ReactionRecord> reactions = parseReactions(cursor);
TransferMessageModel transferMessageModel = parseTransferMessageModel(type, body);
if (!TextSecurePreferences.isReadReceiptsEnabled(context)) {
readReceiptCount = 0;
}
List<IdentityKeyMismatch> mismatches = getMismatches(mismatchDocument);
Recipient recipient = Recipient.live(RecipientId.from(recipientId)).get();
/* return new SmsMessageRecord(messageId, body, recipient,
recipient,
addressDeviceId,
dateSent, dateReceived, dateServer, deliveryReceiptCount, type,
threadId, status, mismatches, subscriptionId,
expiresIn, expireStarted,
readReceiptCount, unidentified, reactions, remoteDelete);*/
return new SmsMessageRecord(messageId, body, recipient,
recipient,
addressDeviceId,
dateSent, dateReceived, dateServer, deliveryReceiptCount, type,
threadId, status, mismatches, subscriptionId,
expiresIn, expireStarted,
readReceiptCount, unidentified, reactions, remoteDelete, transferMessageModel);
}
private List<IdentityKeyMismatch> getMismatches(String document) {
try {
if (!TextUtils.isEmpty(document)) {
return JsonUtils.fromJson(document, IdentityKeyMismatchList.class).getList();
}
} catch (IOException e) {
Log.w(TAG, e);
}
return new LinkedList<>();
}
public void close() {
cursor.close();
}
}
3.SignalServiceDataMessage:消息传输层(libsignal这个module)使用的消息的数据结构
SmsMessageRecord转为SignalServiceDataMessage是 PushTextSendJob 的deliver方法
注意SignalServiceDataMessage是没有消息类型type的字段的… 即type并不参与序列化过程。
//SignalServiceDataMessage.java
/**
* Represents a decrypted Signal Service data message.
*/
public class SignalServiceDataMessage {
private final long timestamp;发送的消息时间戳
private final Optional<List<SignalServiceAttachment>> attachments;
private final Optional<String> body;//发送的消息消息体,比如发送的消息是123,那么body这个字段的值就是123
private final Optional<SignalServiceGroupContext> group;
private final Optional<byte[]> profileKey;
private final boolean endSession;//是否是终止会话的消息
private final boolean expirationUpdate;
private final int expiresInSeconds;
private final boolean profileKeyUpdate;
private final Optional<Quote> quote;
private final Optional<List<SharedContact>> contacts;
private final Optional<List<Preview>> previews;
private final Optional<Sticker> sticker;
private final boolean viewOnce;
private final Optional<Reaction> reaction;
private final Optional<RemoteDelete> remoteDelete;
...
后续序列化过程中的数据类型也是没有type字段的,因为序列化后的数据并不是根据type字段来区分不同类型的数据的,而是根据SignalServiceDataMessage中是否有相应类型的结构数据来序列化为对应的SignalService.proto中定义的各种类型的结构数据。
4.SignalService.proto:这个文件中定义的各种类型的消息,是跨平台时序列化用的
发送消息前SignalServiceDataMessage类型的消息数据要转为SignalService.proto中定义的各种类型的消息,转化的方法是SignalServiceMessageSender的createMessageContent()方法:
//SignalServiceMessageSender.java
private byte[] createMessageContent(SignalServiceDataMessage message) throws IOException {
Content.Builder container = Content.newBuilder();
DataMessage.Builder builder = DataMessage.newBuilder();
List<AttachmentPointer> pointers = createAttachmentPointers(message.getAttachments());
if (!pointers.isEmpty()) {
builder.addAllAttachments(pointers);
...
可以看到这个方法并不是根据消息的type字段(且SignalServiceDataMessage中也没有type字段)来进行转化为不同的消息的,而是根据SignalServiceDataMessage中是否有相应结构的消息来进行序列化的。比如:如果SignalServiceDataMessage中有Reaction结构的数据,则将这个结构的数据序列化为SignalService.proto中Reaction类型的数据
//SignalServiceProtos.java
...
/**
* Protobuf type {@code signalservice.DataMessage.Reaction}
*/
public static final class Reaction extends
com.google.protobuf.GeneratedMessageLite<
Reaction, Reaction.Builder> implements
// @@protoc_insertion_point(message_implements:signalservice.DataMessage.Reaction)
ReactionOrBuilder {
...
SignalServiceProtos.java是SignalService.proto文件编译后的文件。
5.SignalServiceContent:
用于在接收到消息数据(接收到的消息数据是序列化的数据,protobuf格式的,发送的消息都是经过序列化后才发送的)后进行反序列化的,即将序列化数据进行反序列化为SignalServiceContent类型的结构数据。
转化的具体方法是:SignalServiceContent的createFromProto方法
//SignalServiceContent.java
public static SignalServiceContent deserialize(byte[] data) {
try {
if (data == null) return null;
//将字节数据进行反序列化为protobuf格式的数据
SignalServiceContentProto signalServiceContentProto = SignalServiceContentProto.parseFrom(data);
//将protobuf格式的数据转化为SignalServiceContent结构的数据
return createFromProto(signalServiceContentProto);
} catch (InvalidProtocolBufferException | ProtocolInvalidMessageException | ProtocolInvalidKeyException | UnsupportedDataMessageException e) {
// We do not expect any of these exceptions if this byte[] has come from serialize.
throw new AssertionError(e);
}
}
/**
* Takes internal protobuf serialization format and processes it into a {@link SignalServiceContent}.
*/
public static SignalServiceContent createFromProto(SignalServiceContentProto serviceContentProto)
throws ProtocolInvalidMessageException, ProtocolInvalidKeyException, UnsupportedDataMessageException
{
SignalServiceMetadata metadata = SignalServiceMetadataProtobufSerializer.fromProtobuf(serviceContentProto.getMetadata());
SignalServiceAddress localAddress = SignalServiceAddressProtobufSerializer.fromProtobuf(serviceContentProto.getLocalAddress());
if (serviceContentProto.getDataCase() == SignalServiceContentProto.DataCase.LEGACYDATAMESSAGE) {
SignalServiceProtos.DataMessage message = serviceContentProto.getLegacyDataMessage();
return new SignalServiceContent(createSignalServiceMessage(metadata, message),
metadata.getSender(),
metadata.getSenderDevice(),
metadata.getTimestamp(),
metadata.getServerReceivedTimestamp(),
metadata.getServerDeliveredTimestamp(),
metadata.isNeedsReceipt(),
serviceContentProto);
} else if (serviceContentProto.getDataCase() == SignalServiceContentProto.DataCase.CONTENT) {
SignalServiceProtos.Content message = serviceContentProto.getContent();
if (message.hasDataMessage()) {
return new SignalServiceContent(createSignalServiceMessage(metadata, message.getDataMessage()),
metadata.getSender(),
metadata.getSenderDevice(),
metadata.getTimestamp(),
metadata.getServerReceivedTimestamp(),
metadata.getServerDeliveredTimestamp(),
metadata.isNeedsReceipt(),
serviceContentProto);
} else if (message.hasSyncMessage() && localAddress.matches(metadata.getSender())) {
return new SignalServiceContent(createSynchronizeMessage(metadata, message.getSyncMessage()),
metadata.getSender(),
metadata.getSenderDevice(),
metadata.getTimestamp(),
metadata.getServerReceivedTimestamp(),
metadata.getServerDeliveredTimestamp(),
metadata.isNeedsReceipt(),
serviceContentProto);
} else if (message.hasCallMessage()) {
return new SignalServiceContent(createCallMessage(message.getCallMessage()),
metadata.getSender(),
metadata.getSenderDevice(),
metadata.getTimestamp(),
metadata.getServerReceivedTimestamp(),
metadata.getServerDeliveredTimestamp(),
metadata.isNeedsReceipt(),
serviceContentProto);
} else if (message.hasReceiptMessage()) {
return new SignalServiceContent(createReceiptMessage(metadata, message.getReceiptMessage()),
metadata.getSender(),
metadata.getSenderDevice(),
metadata.getTimestamp(),
metadata.getServerReceivedTimestamp(),
metadata.getServerDeliveredTimestamp(),
metadata.isNeedsReceipt(),
serviceContentProto);
} else if (message.hasTypingMessage()) {
return new SignalServiceContent(createTypingMessage(metadata, message.getTypingMessage()),
metadata.getSender(),
metadata.getSenderDevice(),
metadata.getTimestamp(),
metadata.getServerReceivedTimestamp(),
metadata.getServerDeliveredTimestamp(),
false,
serviceContentProto);
}
}
return null;
}
消息列表数据更新
当消息列表数据发生变化时会通知
//ConversationFragment.java
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle bundle) {
...
this.conversationViewModel = ViewModelProviders.of(requireActivity(), new ConversationViewModel.Factory()).get(ConversationViewModel.class);
conversationViewModel.getMessages().observe(this, list -> {
if (getListAdapter() != null && !list.getDataSource().isInvalid()) {
Log.i(TAG, "submitList");
getListAdapter().submitList(list);
} else if (list.getDataSource().isInvalid()) {
Log.i(TAG, "submitList skipped an invalid list");
}
});
conversationViewModel.getConversationMetadata().observe(this, this::presentConversationMetadata);
return view;
}
消息类型
页面展示时的消息类型
//ConversationAdapter.java
public class ConversationAdapter<V extends View & BindableConversationItem>
extends PagedListAdapter<MessageRecord, RecyclerView.ViewHolder>
implements StickyHeaderDecoration.StickyHeaderAdapter<ConversationAdapter.StickyHeaderViewHolder>
{
private static final String TAG = Log.tag(ConversationAdapter.class);
private static final int MESSAGE_TYPE_OUTGOING_MULTIMEDIA = 0;
private static final int MESSAGE_TYPE_OUTGOING_TEXT = 1;
private static final int MESSAGE_TYPE_INCOMING_MULTIMEDIA = 2;
private static final int MESSAGE_TYPE_INCOMING_TEXT = 3;
private static final int MESSAGE_TYPE_UPDATE = 4;
private static final int MESSAGE_TYPE_HEADER = 5;
private static final int MESSAGE_TYPE_FOOTER = 6;
private static final int MESSAGE_TYPE_PLACEHOLDER = 7;
...
public int getItemViewType(int position)
根据消息类型获取itemview的类型:
//ConversationAdapter.java
@Override
public int getItemViewType(int position) {
if (hasHeader() && position == 0) {
return MESSAGE_TYPE_HEADER;
}
if (hasFooter() && position == getItemCount() - 1) {
return MESSAGE_TYPE_FOOTER;
}
MessageRecord messageRecord = getItem(position);
if (messageRecord == null) {
return MESSAGE_TYPE_PLACEHOLDER;
} else if (messageRecord.isUpdate()) {
return MESSAGE_TYPE_UPDATE;
} else if (messageRecord.isOutgoing()) {
return messageRecord.isMms() ? MESSAGE_TYPE_OUTGOING_MULTIMEDIA : MESSAGE_TYPE_OUTGOING_TEXT;
} else {
return messageRecord.isMms() ? MESSAGE_TYPE_INCOMING_MULTIMEDIA : MESSAGE_TYPE_INCOMING_TEXT;
}
}
...
这样不同类型的消息在会话列表中展示的就是不同的View。
发送消息时是如何设置消息的type的?
发送消息前需要调用OutgoingMessageReader的getCurrent()方法进行组装消息:
//SmsDatabase.java
public static class OutgoingMessageReader {
private final OutgoingTextMessage message;
private final long id;
private final long threadId;
public OutgoingMessageReader(OutgoingTextMessage message, long threadId) {
this.message = message;
this.threadId = threadId;
this.id = new SecureRandom().nextLong();
}
public MessageRecord getCurrent() {
return new SmsMessageRecord(id,
message.getMessageBody(),
message.getRecipient(),
message.getRecipient(),
1,
System.currentTimeMillis(),
System.currentTimeMillis(),
-1,
0,
message.isSecureMessage() ? MmsSmsColumns.Types.getOutgoingEncryptedMessageType() : MmsSmsColumns.Types.getOutgoingSmsMessageType(),
threadId,
0,
new LinkedList<>(),
message.getSubscriptionId(),
message.getExpiresIn(),
System.currentTimeMillis(),
0,
false,
Collections.emptyList(),
false);
}
}
可以看到设置type的值就是这段代码:
message.isSecureMessage() ? MmsSmsColumns.Types.getOutgoingEncryptedMessageType() : MmsSmsColumns.Types.getOutgoingSmsMessageType(),
//MmsSmsColumns.java
public static long getOutgoingEncryptedMessageType() {
return Types.BASE_SENDING_TYPE | Types.SECURE_MESSAGE_BIT | Types.PUSH_MESSAGE_BIT;
}
public static long getOutgoingSmsMessageType() {
return Types.BASE_SENDING_TYPE;
}
即:
如果发送的是加密的文本消息,则类型是Types.BASE_SENDING_TYPE | Types.SECURE_MESSAGE_BIT | Types.PUSH_MESSAGE_BIT
如果发送的是未加密的sms文本消息,则类型是Types.BASE_SENDING_TYPE
long型的整数的最大值是9223372036854775807,写成十六进制是7FFF,FFFF,FFFF,FFFF
long是64bit位,1位16进制位等于4位二进制,所以long型整数的最大值转为16进制是最多64/4=16位十六进制。
isSecureText
点击下面的蓝色图标进行切换时
点击灰色按钮时的调用栈:
I/ConversationActivity: onModified(RecipientId::2) REGISTERED
I/ConversationActivity: updateDefaultSubscriptionId(null)
I/ConversationActivity: handleSecurityChange(true, false)
java.lang.Exception
at org.thoughtcrime.securesms.conversation.ConversationActivity.handleSecurityChange(ConversationActivity.java:1365)
at org.thoughtcrime.securesms.conversation.ConversationActivity.initializeSecurity(ConversationActivity.java:1521)
at org.thoughtcrime.securesms.conversation.ConversationActivity.onRecipientChanged(ConversationActivity.java:1968)
at org.thoughtcrime.securesms.conversation.ConversationActivity.lambda$crBk0C4wyR4C1jCwmixONsjZePo(Unknown Source:0)
at org.thoughtcrime.securesms.conversation.-$$Lambda$ConversationActivity$crBk0C4wyR4C1jCwmixONsjZePo.onChanged(Unknown Source:4)
at androidx.lifecycle.LiveData.considerNotify(LiveData.java:131)
at androidx.lifecycle.LiveData.dispatchingValue(LiveData.java:149)
at androidx.lifecycle.LiveData.setValue(LiveData.java:307)
at androidx.lifecycle.MutableLiveData.setValue(MutableLiveData.java:50)
at androidx.lifecycle.LiveData$1.run(LiveData.java:91)
at android.os.Handler.handleCallback(Handler.java:938)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:223)
at android.app.ActivityThread.main(ActivityThread.java:7656)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)
点击蓝色按钮时的调用栈:
I/ConversationActivity: onModified(RecipientId::2) REGISTERED
I/ConversationActivity: updateDefaultSubscriptionId(null)
I/ConversationActivity: handleSecurityChange(true, false)
java.lang.Exception
at org.thoughtcrime.securesms.conversation.ConversationActivity.handleSecurityChange(ConversationActivity.java:1365)
at org.thoughtcrime.securesms.conversation.ConversationActivity.initializeSecurity(ConversationActivity.java:1521)
at org.thoughtcrime.securesms.conversation.ConversationActivity.onRecipientChanged(ConversationActivity.java:1968)
at org.thoughtcrime.securesms.conversation.ConversationActivity.lambda$crBk0C4wyR4C1jCwmixONsjZePo(Unknown Source:0)
at org.thoughtcrime.securesms.conversation.-$$Lambda$ConversationActivity$crBk0C4wyR4C1jCwmixONsjZePo.onChanged(Unknown Source:4)
at androidx.lifecycle.LiveData.considerNotify(LiveData.java:131)
at androidx.lifecycle.LiveData.dispatchingValue(LiveData.java:149)
at androidx.lifecycle.LiveData.setValue(LiveData.java:307)
at androidx.lifecycle.MutableLiveData.setValue(MutableLiveData.java:50)
at androidx.lifecycle.LiveData$1.run(LiveData.java:91)
at android.os.Handler.handleCallback(Handler.java:938)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:223)
at android.app.ActivityThread.main(ActivityThread.java:7656)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)
但是并不会改变ConversationActivity的isSecureText变量的值。
由于初始化时:
//ConversationActivity.java
private void initializeResources() {
if (recipient != null) {
recipient.removeObservers(this);
}
recipient = Recipient.live(getIntent().getParcelableExtra(RECIPIENT_EXTRA));
threadId = getIntent().getLongExtra(THREAD_ID_EXTRA, -1);
distributionType = getIntent().getIntExtra(DISTRIBUTION_TYPE_EXTRA, ThreadDatabase.DistributionTypes.DEFAULT);
glideRequests = GlideApp.with(this);
recipient.observe(this, this::onRecipientChanged);
}
是个LiveData,所以执行的方法是onRecipientChanged:
//ConversationActivity.java
private void onRecipientChanged(@NonNull Recipient recipient) {
Log.i(TAG, "onModified(" + recipient.getId() + ") " + recipient.getRegistered());
titleView.setTitle(glideRequests, recipient);
titleView.setVerified(identityRecords.isVerified());
setBlockedUserState(recipient, isSecureText, isDefaultSms);
setActionBarColor(recipient.getColor());
updateReminders();
updateDefaultSubscriptionId(recipient.getDefaultSubscriptionId());
initializeSecurity(isSecureText, isDefaultSms);
...
会调用initializeSecurity()方法
private ListenableFuture<Boolean> initializeSecurity(final boolean currentSecureText,
final boolean currentIsDefaultSms)
{
final SettableFuture<Boolean> future = new SettableFuture<>();
handleSecurityChange(currentSecureText || isPushGroupConversation(), currentIsDefaultSms);
...
initializeSecurity()方法中会调用handleSecurityChange()方法:
private void handleSecurityChange(boolean isSecureText, boolean isDefaultSms) {
Log.i(TAG, "handleSecurityChange(" + isSecureText + ", " + isDefaultSms + ")", new Exception());
this.isSecureText = isSecureText;
this.isDefaultSms = isDefaultSms;
this.isSecurityInitialized = true;
...
handleSecurityChange()方法会进行更新isSecureText变量的值,但是
那么什么时候会更新isSecureText的值?
Enable Signal for SMS
这个按钮是:private Button makeDefaultSmsButton;
出现这个页面是对方的账号没有在服务器上注册过,
//ConversationActivity.java
initializeSecurity(recipient.get().isRegistered(), isDefaultSms).addListener(new AssertedSuccessListener<Boolean>() {
如果接收者未注册,则isSecureText参数是false,此时会出现图中的Enable Signal for SMS
按钮,即对方未注册时只能通过发送sms短信,不能通过app发送消息了。
此时是通过android的SMSManager发送的未加密的SMS短信。
消息存储
消息存储在本地,本地的消息并没有加密,但是发送出去的消息是加密的。
SignalServiceContent
数据库
相关类:
SQLCipherOpenHelper
DatabaseSecretProvider
数据库是加密的,使用的是org.signal:android-database-sqlcipher:3.5.9-S3
,数据库在创建的时候就会
生成秘钥,然后保存在本地:
//DatabaseFactory.java
private DatabaseFactory(@NonNull Context context) {
SQLiteDatabase.loadLibs(context);
DatabaseSecret databaseSecret = new DatabaseSecretProvider(context).getOrCreateDatabaseSecret();
AttachmentSecret attachmentSecret = AttachmentSecretProvider.getInstance(context).getOrCreateAttachmentSecret();
this.databaseHelper = new SQLCipherOpenHelper(context, databaseSecret);
...
//DatabaseSecretProvider.java
public DatabaseSecret getOrCreateDatabaseSecret() {
String unencryptedSecret = TextSecurePreferences.getDatabaseUnencryptedSecret(context);
String encryptedSecret = TextSecurePreferences.getDatabaseEncryptedSecret(context);
if (unencryptedSecret != null) return getUnencryptedDatabaseSecret(context, unencryptedSecret);
else if (encryptedSecret != null) return getEncryptedDatabaseSecret(encryptedSecret);
else return createAndStoreDatabaseSecret(context);
}
...
private DatabaseSecret createAndStoreDatabaseSecret(@NonNull Context context) {
SecureRandom random = new SecureRandom();
byte[] secret = new byte[32];
random.nextBytes(secret);
DatabaseSecret databaseSecret = new DatabaseSecret(secret);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
KeyStoreHelper.SealedData encryptedSecret = KeyStoreHelper.seal(databaseSecret.asBytes());
TextSecurePreferences.setDatabaseEncryptedSecret(context, encryptedSecret.serialize());
} else {
TextSecurePreferences.setDatabaseUnencryptedSecret(context, databaseSecret.asString());
}
return databaseSecret;
}
下次读取数据库的时候用秘钥解密。
数据库是signal.db
文本消息的数据表是sms,相关建表语句在SmsDatabase类中
多媒体消息的数据表是mms,相关建表语句在MmsDatabase类中
联系人存储在哪
存储在数据库signal.db的数据表recipient 中。
RecipientDatabase.java负责管理,RecipientDatabase#getSignalContacts()方法获取联系人:
public @Nullable Cursor getSignalContacts(boolean includeSelf) {
String selection = BLOCKED + " = ? AND " +
REGISTERED + " = ? AND " +
GROUP_ID + " IS NULL AND " +
"(" + SYSTEM_DISPLAY_NAME + " NOT NULL OR " + PROFILE_SHARING + " = ?) AND " +
"(" + SORT_NAME + " NOT NULL OR " + USERNAME + " NOT NULL)";
String[] args;
if (includeSelf) {
args = new String[] { "0", String.valueOf(RegisteredState.REGISTERED.getId()), "1" };
} else {
selection += " AND " + ID + " != ?";
args = new String[] { "0", String.valueOf(RegisteredState.REGISTERED.getId()), "1", Recipient.self().getId().serialize() };
}
String orderBy = SORT_NAME + ", " + SYSTEM_DISPLAY_NAME + ", " + SEARCH_PROFILE_NAME + ", " + USERNAME + ", " + PHONE;
return databaseHelper.getReadableDatabase().query(TABLE_NAME, SEARCH_PROJECTION, selection, args, null, null, orderBy);
}
ContactRepository.java是负责对RecipientDatabase查询出来的数据进行过滤和处理的。
使用cursor后要clsoe,否则会报错:
E/Cursor: Finalizing a Cursor that has not been deactivated or closed. database = /data/user/0/org.thoughtcrime.securesms/databases/signal.db, table = recipient, query = SELECT _id, system_display_name, phone, email, system_phone_label, system_phone_type, registered, CO
net.sqlcipher.database.DatabaseObjectNotClosedException: Application did not close the cursor or database object that was opened here
at net.sqlcipher.database.SQLiteCursor.<init>(SQLiteCursor.java:237)
at net.sqlcipher.database.SQLiteDirectCursorDriver.query(SQLiteDirectCursorDriver.java:71)
at net.sqlcipher.database.SQLiteDatabase.rawQueryWithFactory(SQLiteDatabase.java:1965)
at net.sqlcipher.database.SQLiteDatabase.queryWithFactory(SQLiteDatabase.java:1740)
at net.sqlcipher.database.SQLiteDatabase.query(SQLiteDatabase.java:1692)
at net.sqlcipher.database.SQLiteDatabase.query(SQLiteDatabase.java:1781)
at org.thoughtcrime.securesms.database.RecipientDatabase.querySignalContacts(RecipientDatabase.java:1544)
at org.thoughtcrime.securesms.contacts.ContactRepository.querySignalContacts(ContactRepository.java:94)
at org.thoughtcrime.securesms.contacts.ContactsCursorLoader.getContactsCursors(ContactsCursorLoader.java:295)
at org.thoughtcrime.securesms.contacts.ContactsCursorLoader.addContactsSection(ContactsCursorLoader.java:154)
at org.thoughtcrime.securesms.contacts.ContactsCursorLoader.getFilteredResults(ContactsCursorLoader.java:132)
at org.thoughtcrime.securesms.contacts.ContactsCursorLoader.loadInBackground(ContactsCursorLoader.java:98)
at org.thoughtcrime.securesms.contacts.ContactsCursorLoader.loadInBackground(ContactsCursorLoader.java:53)
at androidx.loader.content.AsyncTaskLoader.onLoadInBackground(AsyncTaskLoader.java:307)
at androidx.loader.content.AsyncTaskLoader$LoadTask.doInBackground(AsyncTaskLoader.java:60)
at androidx.loader.content.AsyncTaskLoader$LoadTask.doInBackground(AsyncTaskLoader.java:48)
at androidx.loader.content.ModernAsyncTask$2.call(ModernAsyncTask.java:141)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
at java.lang.Thread.run(Thread.java:923)