聊天-发送消息的流程

会话页面

发送消息

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)
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值