Making the address
QQDemo
Project introduction
This project is an example project of instant messaging. It uses MVP mode, integrates huanxing SDK and Bmob back-end cloud, and shows the realization of basic functions of instant messaging, including registering and logging in, logging out, contact list, adding friends, deleting friends, sending and receiving messages, message reminding and other functions.
Open source projects used
- BottomBar
- EventBus
- greenDAO
- butterknife
Learning goals
- Integration and use of HUANxin SDK
- MVP mode
- Integration and use of ORM database
- The use of modular ideas
Instant Messaging
Allows two or more people to use the Internet for instant text messaging, file, voice and video communication.
Related products
- Doyen ICQ
- Domestic mainstream QQ, Wechat, momo, YY, etc
- Facebook Messenger WhatsApp Skype Instagram Line
Third Party Service Platform
- Ring letter
- Integrating cloud
- Netease cloud letter
- The aurora IM
- Tencent Cloud communication IM
- Love ‘
- Ali Wukong (officially offline on October 31, 2016)
- Ali is prosperous
Ring letter
Im Cloud 3.x documentation
easemob_business.png
Ring letter integration
- Register and create an application
- Download the SDK
- The introduction of the SDK
- Initialize the SDK
.so the folder
- On jniLibs
-
You can also put it in the libs directory, but you need to configure it in the configuration file under the module
android { sourceSets { main { jniLibs.srcDirs = ['libs'] } } }Copy the code
crater
Run error: Didn ‘t find the class “com. Hyphenate. Chat. Adapter. EMACallSession”, the reason is hyphenatechat_3. 2.0 the jar package without the class.
Solution: Import Demo source EaseUI library hyphenatechat_3.2.0.jar replacement.
Software architecture
MVC
MVC is used in Ruby on Rails, Spring Framework, iOS development and ASP.NET.
- Model: Business logic to obtain data, network operation, database operation
- View: UI
- Controller: Manipulate the Model layer to fetch data to pass to the UI
mvc.png
Server-side MVC
mvcpattern.png
Android in MVC
Android does not have a clear MVC framework. If an Activity is regarded as a Controller, according to our actual development experience, there will be a lot of UI operations in it, so it will be silly to distinguish V from C.
- Model:Java Bean, NetworkManager, DataBaseHelper
- View: xml res
- Controller: Activity Fragment
- ArrayList-ListView-Adapter(MVC)
MVP
MVP is mainly used in ASP.NET. The main difference between MVP and MVC is that the View and Model are no longer coupled.
- Model: Business logic to obtain data, network operation, database operation
- View: UI
- Presenter: Manipulate the Model layer to get data to pass to the UI
mvp.png
MVVM
MVVM is mainly used in WPF, Silverlight, Caliburn, nRoute, etc.
- Model: Business logic to obtain data, network operation, database operation
- View: UI
- ViewModel: Bind the View to the Model
mvvm.png
The Android MVVM
Data Binding Library
The core ideas of software architecture
Stratified module
architecture.png
reference
android architecture
How to choose between MVC,MVP and MVVM patterns
Understanding MVC, MVP and MVVM Design Patterns
Learn about MVC, MVP and MVVM
Android Data Binding
Clean Architecture
Are you ready? Start driving!!
The creation of a package
- Adapter storage adapter
- App holds constant classes, Application classes, and some global classes at the app level
- Database Database related classes
- Event EventBus Event class
- The factory factory class
- Model Data model
- Presenter Class in the PRESENTER MVP model
- UI stores activities and fragments
- Utils tools
- View class in the VIEW MVP model
- Widget Custom controls
Base class creation
- BaseActivity
- BaseFragment
Git initialization
Splash screen
splash.png
The functional requirements
- If you do not log in, the login page is displayed after 2 seconds
- If you have logged in, the main window is displayed
MVP implementation
- SplashView
- SplashPresenter
Login screen
login.jpg
The functional requirements
- You can initiate a login operation either by clicking the login button or by clicking the Action key on the virtual keyboard.
- Click New User to jump to the registration interface.
IME Options
Note that the imeOptions property of the EditText must be configured with inputType to take effect.
Android :imeOptions="actionNext"// Next Android :imeOptions="actionGo"// Start Android :imeOptions="actionDone"// done Android :imeOptions="actionPrevious"// Previous Android :imeOptions="actionSearch"// Search android:imeOptions="actionSend"// SendCopy the code
MVP implementation
An adapter for EMCallBack
public class EMCallBackAdapter implements EMCallBack{
@Override
public void onSuccess() {
}
@Override
public void onError(int i, String s) {
}
@Override
public void onProgress(int i, String s) {
}
}Copy the code
Android6.0 Dynamic permission management
For example: Autonavi Map baidu Map and so on
/ * * * if there is a write disk access * / private Boolean hasWriteExternalStoragePermission () {int result = ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE); return result == PermissionChecker.PERMISSION_GRANTED; } / apply for permission to * * * * / private void applyPermission () {String [] permissions = {Manifest. Permission. WRITE_EXTERNAL_STORAGE}; ActivityCompat.requestPermissions(this, permissions, REQUEST_WRITE_EXTERNAL_STORAGE); } / / @ * * * to apply for permission to callback * Override public void onRequestPermissionsResult (int requestCode, @ NonNull String [] permissions, @NonNull int[] grantResults) { switch (requestCode) { case REQUEST_WRITE_EXTERNAL_STORAGE: if (grantResults[0] == PermissionChecker.PERMISSION_GRANTED) { login(); } else { toast(getString(R.string.not_get_permission)); } break; }}Copy the code
The registration screen
register.jpg
The functional requirements
- The user name must be 3 to 20 characters in length. It must start with an English character and contain digits or underscores (_).
- The password must be 3-20 digits.
- The password is the same as the confirm password
Regular expression
Regular expression – metacharacters
Private static final String USER_NAME_REGEX = "^[a-za-z]\\w{2,19}$"; Private static final String PASSWORD_REGEX = "^[0-9]{3,20}$"; private static final String PASSWORD_REGEX = "^[0-9]{3,20}$";Copy the code
- \w matches any word character that includes underscores. Equivalent to ‘[a-za-z0-9_]’.
MVP implementation
- RegisterView
- RegisterPresenter
The registration process
- In a real project, the registration would register the user name and password with the APP server, which would then register with the ring server through the REST API.
- Since there is no APP server in this project, the user data will be registered with the third-party cloud database Bmob. After successful registration, a request will be sent on the client side to register with the ring mail server.
register_logic.png
Cloud database
- LeanCloud
- Bmob
- Parse(closed January 28, 2017)
Bmob integration
Development of the document
- Registering and creating an Application
- Download the SDK
- Import the SDK
- To initialize the SDk
Hidden soft keyboard
protected void hideSoftKeyboard() {
if (mInputMethodManager == null) {
mInputMethodManager = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE);
}
mInputMethodManager.hideSoftInputFromWindow(getCurrentFocus().getWindowToken(), 0);
}Copy the code
Soft keyboard Action processing
private TextView.OnEditorActionListener mOnEditorActionListener = new TextView.OnEditorActionListener() { @Override public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { if (actionId == EditorInfo.IME_ACTION_GO) { reigister(); // register return true; } return false; }};Copy the code
Processing of registered user names
Bmob error code
The main interface
main.jpg
Bottom navigation bar
RadioGroup, TabHost, FragmentTabHost, custom
Third party bottom bar
BottomBar
AHBottomNavigation
BottomNavigation
Fragments of the switch
A dynamic interface
logout.jpg
Contact screen
contact1.jpg
contact2.jpg
MVP implementation
- ContactView
- ContactPresenter
The use of RecyclerView
Creating Lists and Cards
Whether the contacts are in the same group
private boolean itemInSameGroup(int i, ContactItem item) {
return i > 0 && (item.getFirstLetter() == mContactItems.get(i - 1).getFirstLetter());
}Copy the code
The use of the CardView
The use of SwipeRefreshLayout
mSwipeRefreshLayout.setColorSchemeResources(R.color.qq_blue, R.color.qq_red);
mSwipeRefreshLayout.setOnRefreshListener(mOnRefreshListener);Copy the code
Custom control SlideBar
Class character array (for copy)
private static final String[] SECTIONS = {"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L"
, "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"};Copy the code
Draws centered text
www.cnblogs.com/tianzhijiex…
Listen for SlideBar events in the ContactFragment
private SlideBar.OnSlideBarChangeListener mOnSlideBarChangeListener = new SlideBar.OnSlideBarChangeListener() { @Override public void onSectionChange(int index, String section) { mSection.setVisibility(View.VISIBLE); mSection.setText(section); scrollToSection(section); } @Override public void onSlidingFinish() { mSection.setVisibility(View.GONE); }}; Private void scrollToSection(String section) {int; private void scrollToSection(String section) {private void scrollToSection(String section) {private void scrollToSection(String section) {int sectionPosition = getSectionPosition(section); if (sectionPosition ! = POSITION_NOT_FOUND) { mRecyclerView.smoothScrollToPosition(sectionPosition); }} /** ** @param Section first character * @return The first character in the contact list is the position of the section's first contact in the contact list */ private int getSectionPosition(String) section) { List contactItems = mContactListAdapter.getContactItems(); for (int i = 0; i < contactItems.size(); i++) { if (section.equals(contactItems.get(i).getFirstLetterString())) { return i; } } return POSITION_NOT_FOUND; }Copy the code
Contact click event
private ContactListAdapter.OnItemClickListener mOnItemClickListener = new ContactListAdapter.OnItemClickListener() { /** @override public void onItemClick(String name) {@override public void onItemClick(String name) { startActivity(ChatActivity.class, Constant.Extra.USER_NAME, name); @override public void onItemLongClick(final String name) {/** * public void onItemLongClick(final String name) { AlertDialog.Builder builder = new AlertDialog.Builder(getContext()); String message = String.format(getString(R.string.delete_friend_message), name); builder.setTitle(getString(R.string.delete_friend)) .setMessage(message) .setNegativeButton(getString(R.string.cancel), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); } }) .setPositiveButton(getString(R.string.confirm), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); showProgress(getString(R.string.deleting_friend)); mContactPresenter.deleteFriend(name); }}); builder.show(); }};Copy the code
Add Friends screen
add_friend.jpg
Search the user
Query data
@Override public void searchFriend(final String keyword) { mAddFriendView.onStartSearch(); // Note: Fuzzy query is only available to paid users. BmobQuery query = new BmobQuery(); query.addWhereContains("username", keyword).addWhereNotEqualTo("username", EMClient.getInstance().getCurrentUser()); query.findObjects(new FindListener() { @Override public void done(List list, BmobException e) { processResult(list, e); }}); }Copy the code
greenDAO
GreenDAO is a kind of ORM framework for Android SQLite database. ORM stands for Object/Relational Mapping, which maps Java objects into database tables.
Other ORM frameworks
- DBFlow
- Ormlite
- Sugar
- ActiveAndroid
- Sprinkles
- Ollie
reference
- Github
- website
- AppBrain
- Using document
- Chinese Usage Document
Creating an entity Class
@Entity
public class Contact {
@Id
public Long id;
public String userName;
}Copy the code
Initialize the
public void init(Context context) {
DaoMaster.DevOpenHelper devOpenHelper = new DaoMaster.DevOpenHelper(context, Constant.Database.DATABASE_NAME, null);
SQLiteDatabase writableDatabase = devOpenHelper.getWritableDatabase();
DaoMaster daoMaster = new DaoMaster(writableDatabase);
mDaoSession = daoMaster.newSession();
}Copy the code
Save contacts
public void saveContact(String userName) {
Contact contact = new Contact();
contact.setUsername(userName);
mDaoSession.getContactDao().save(contact);
}Copy the code
Querying Contacts
public List queryAllContacts() {
List list = mDaoSession.getContactDao().queryBuilder().list();
ArrayList contacts = new ArrayList();
for (int i = 0; i < list.size(); i++) {
String contact = list.get(i).getUsername();
contacts.add(contact);
}
return contacts;
}Copy the code
Deleting contacts
public void deleteAllContacts() {
ContactDao contactDao = mDaoSession.getContactDao();
contactDao.deleteAll();
}Copy the code
Send a Friend Request
AddFriendItemView handles click events
@OnClick(R.id.add)
public void onClick() {
String friendName = mUserName.getText().toString().trim();
String addFriendReason = getContext().getString(R.string.add_friend_reason);
AddFriendEvent event = new AddFriendEvent(friendName, addFriendReason);
EventBus.getDefault().post(event);
}Copy the code
AddFriendPresenterImpl enables you to send a friend request
@Subscribe(threadMode = ThreadMode.BACKGROUND) public void addFriend(AddFriendEvent event) { try { EMClient.getInstance().contactManager().addContact(event.getFriendName(), event.getReason()); ThreadUtils.runOnUiThread(new Runnable() { @Override public void run() { mAddFriendView.onAddFriendSuccess(); }}); } catch (HyphenateException e) { e.printStackTrace(); ThreadUtils.runOnUiThread(new Runnable() { @Override public void run() { mAddFriendView.onAddFriendFailed(); }}); }Copy the code
Listen for contact changes in the contact list
private EMContactListenerAdapter mEMContactListener = new EMContactListenerAdapter() { @Override public void onContactAdded(String s) { mContactPresenter.refreshContactList(); } @Override public void onContactDeleted(String s) { mContactPresenter.refreshContactList(); }};Copy the code
Chat screen
chat1.jpg
chat2.jpg
Listen for state changes for the send button
mEdit.addTextChangedListener(mTextWatcher);
private TextWatcherAdapter mTextWatcher = new TextWatcherAdapter() {
@Override
public void afterTextChanged(Editable s) {
mSend.setEnabled(s.length() != 0);
}
};Copy the code
Animation files
- Anim folder: Store tween animations
- Animator folder: Holds property animations
- Drawable folder: Holds frame animations
Send a progress animation of the message
Copy the code
9 File Making
The official instructions
ninepatch_raw.png
ninepatch_examples.png
Send a message
The type of message returned
@Override
public int getItemViewType(int position) {
EMMessage message = mMessages.get(position);
return message.direct() == EMMessage.Direct.SEND ? ITEM_TYPE_SEND_MESSAGE : ITEM_TYPE_RECEIVE_MESSAGE;
}Copy the code
Determine whether to display a timestamp
/** * If the time between two messages is too close, */ private Boolean shouldShowTimeStamp(int position) {long currentItemTimestamp = mMessages.get(position).getMsgTime(); long preItemTimestamp = mMessages.get(position - 1).getMsgTime(); boolean closeEnough = DateUtils.isCloseEnough(currentItemTimestamp, preItemTimestamp); return ! closeEnough; }Copy the code
Update the status of the message
private void updateSendingStatus(EMMessage emMessage) { switch (emMessage.status()) { case INPROGRESS: mSendMessageProgress.setVisibility(VISIBLE); mSendMessageProgress.setImageResource(R.drawable.send_message_progress); AnimationDrawable drawable = (AnimationDrawable) mSendMessageProgress.getDrawable(); drawable.start(); break; case SUCCESS: mSendMessageProgress.setVisibility(GONE); break; case FAIL: mSendMessageProgress.setImageResource(R.mipmap.msg_error); break; }}Copy the code
Receive a message
private EMMessageListenerAdapter mEMMessageListener = new EMMessageListenerAdapter() { @Override public void onMessageReceived(final List list) { ThreadUtils.runOnUiThread(new Runnable() { @Override public void run() { final EMMessage emMessage = list.get(0); mChatPresenter.makeMessageRead(mUserName); mMessageListAdapter.addNewMessage(emMessage); smoothScrollToBottom(); }}); }};Copy the code
Initialize the chat history
The official documentation
Communication process and chat record saving
@Override public void loadMessages(final String userName) { ThreadUtils.runOnBackgroundThread(new Runnable() { @Override public void run() { EMConversation conversation = EMClient.getInstance().chatManager().getConversation(userName); if (conversation ! = null) {/ / get all the messages of this session List messages = conversation. GetAllMessages (); mEMMessageList.addAll(messages); / / the specified session message without reading reset conversation. MarkAllMessagesAsRead (); } ThreadUtils.runOnUiThread(new Runnable() { @Override public void run() { mChatView.onMessagesLoaded(); }}); }}); }Copy the code
Load more chats
@Override public void loadMoreMessages(final String userName) { if (hasMoreData) { ThreadUtils.runOnBackgroundThread(new Runnable() { @Override public void run() { EMConversation conversation = EMClient.getInstance().chatManager().getConversation(userName); EMMessage firstMessage = mEMMessageList.get(0); // Get the pagesize message before startMsgId. The messages obtained by this method will be automatically saved into this session. In the APP without access to messages added to the conversation again final List messages = conversation. LoadMoreMsgFromDB (firstMessage. GetMsgId (), DEFAULT_PAGE_SIZE); hasMoreData = (messages.size() == DEFAULT_PAGE_SIZE); mEMMessageList.addAll(0, messages); ThreadUtils.runOnUiThread(new Runnable() { @Override public void run() { mChatView.onMoreMessagesLoaded(messages.size()); }}); }}); } else { mChatView.onNoMoreData(); }}Copy the code
The session interface
message.jpg
MVP implementation
- ConversationView
- ConversationPresenter
Loading all sessions
@Override public void loadAllConversations() { ThreadUtils.runOnBackgroundThread(new Runnable() { @Override public void run() { Map conversations = EMClient.getInstance().chatManager().getAllConversations(); mEMConversations.addAll(conversations.values()); Collections.sort(mEMConversations, new Comparator() { @Override public int compare(EMConversation o1, EMConversation o2) { return (int) (o2.getLastMessage().getMsgTime() - o1.getLastMessage().getMsgTime()); }}); ThreadUtils.runOnUiThread(new Runnable() { @Override public void run() { mConversationView.onAllConversationsLoaded(); }}); }}); }Copy the code
Unread message count updated
The official documentation
Updates of unread messages in the session list
private EMMessageListenerAdapter mEMMessageListenerAdapter = new EMMessageListenerAdapter() { @Override public void onMessageReceived(List list) { ThreadUtils.runOnUiThread(new Runnable() { @Override public void run() { toast(getString(R.string.receive_new_message)); mConversationPresenter.loadAllConversations(); }}); }};Copy the code
Update to BottomBar Bardge
Private EMMessageListenerAdapter mEMMessageListenerAdapter = new EMMessageListenerAdapter () {/ / the callback call @ Override in the child thread public void onMessageReceived(List list) { updateUnreadCount(); }}; private void updateUnreadCount() { ThreadUtils.runOnUiThread(new Runnable() { @Override public void run() { BottomBarTab bottomBar = mBottomBar.getTabWithId(R.id.conversations); int count = EMClient.getInstance().chatManager().getUnreadMsgsCount(); bottomBar.setBadgeCount(count); }}); }Copy the code
Marks the message read
Marked read while loading chat data
/ / the specified session message without reading reset conversation. MarkAllMessagesAsRead ();Copy the code
Marks a new message as read when it is received in chat
Chat interface returns when updated without reading badge
@Override
protected void onResume() {
super.onResume();
updateUnreadCount();
}Copy the code
alerts
notice
Determine if the app is in the foreground
public boolean isForeground() {
ActivityManager am = (ActivityManager) getSystemService(ACTIVITY_SERVICE);
List runningAppProcesses = am.getRunningAppProcesses();
if (runningAppProcesses == null) {
return false;
}
for (ActivityManager.RunningAppProcessInfo info :runningAppProcesses) {
if (info.processName.equals(getPackageName()) && info.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND) {
return true;
}
}
return false;
}Copy the code
Notification pops up if the app is in the background
private void showNotification(EMMessage emMessage) {
String contentText = "";
if (emMessage.getBody() instanceof EMTextMessageBody) {
contentText = ((EMTextMessageBody) emMessage.getBody()).getMessage();
}
Intent chat = new Intent(this, ChatActivity.class);
chat.putExtra(Constant.Extra.USER_NAME, emMessage.getUserName());
PendingIntent pendingIntent = PendingIntent.getActivity(this, 1, chat, PendingIntent.FLAG_UPDATE_CURRENT);
NotificationManager notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
Notification notification = new Notification.Builder(this)
.setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.avatar1))
.setSmallIcon(R.mipmap.ic_contact_selected_2)
.setContentTitle(getString(R.string.receive_new_message))
.setContentText(contentText)
.setPriority(Notification.PRIORITY_MAX)
.setContentIntent(pendingIntent)
.setAutoCancel(true)
.build();
notificationManager.notify(1, notification);
}Copy the code
voice
Initialize the SoundPool
private void initSoundPool() {
mSoundPool = new SoundPool(2, AudioManager.STREAM_MUSIC, 0);
mDuanSound = mSoundPool.load(this, R.raw.duan, 1);
mYuluSound = mSoundPool.load(this, R.raw.yulu, 1);
}Copy the code
Play sound
private EMMessageListenerAdapter mEMMessageListenerAdapter = new EMMessageListenerAdapter() { @Override public void onMessageReceived(List list) { if (isForeground()) { mSoundPool.play(mDuanSound, 1, 1, 0, 0, 1); } else { mSoundPool.play(mYuluSound, 1, 1, 0, 0, 1); showNotification(list.get(0)); }}};Copy the code
Multi-device Login
The official documentation
private EMConnectionListener mEMConnectionListener = new EMConnectionListener() { @Override public void onConnected() { } @Override public void onDisconnected(int i) { if (i == EMError.USER_LOGIN_ANOTHER_DEVICE) { ThreadUtils.runOnUiThread(new Runnable() { @Override public void run() { startActivity(LoginActivity.class); toast(getString(R.string.user_login_another_device)); }}); }}};Copy the code