本项目是即时通讯的示例项目,使用了MVP模式,集成了环信SDK和Bmob后端云,展示了即时通讯基本的功能的实现,包括注册登录,退出登录,联系人列表,添加好友,删除好友,收发消息,消息提醒等功能。
- 环信SDK的集成与使用
- MVP模式的运用
- ORM数据库的集成与使用
- 模块化**的运用
允许两人或多人使用网路即时的传递文字讯息、档案、语音与视频交流。
- 鼻祖 ICQ
- 国内主流 QQ 微信 陌陌 YY等
- 国外主流 Facebook Messenger WhatsApp Skype Instagram Line
-
放在jniLibs
-
也可以放在libs目录下,不过需要在模块下的配置文件中配置
android { sourceSets { main { jniLibs.srcDirs = ['libs'] } } }
运行出错:Didn't find class "com.hyphenate.chat.adapter.EMACallSession",原因是hyphenatechat_3.2.0.jar包内没有该类。
解决办法:导入Demo源码中EaseUI库里面的hyphenatechat_3.2.0.jar替换。
MVC应用于Ruby on Rails, Spring Framework, iOS开发和 ASP.NET等。
- Model: 获取数据的业务逻辑,网络操作,数据库操作
- View: UI
- Controller: 操作Model层获取数据传递给UI
Android中并没有清晰的MVC框架,如果把Activity当做Controller,根据我们实际开发经验,里面会有大量的UI操作,所以V和C就傻傻分不清了。
- Model:Java Bean, NetworkManager, DataBaseHelper
- View: xml res
- Controller: Activity Fragment
- ArrayList-ListView-Adapter(MVC)
MVP主要应用于ASP.NET等。MVP与MVC主要区别是View和Model不再耦合。
MVVM主要应用于WPF, Silverlight, Caliburn, nRoute等。
- Model: 获取数据的业务逻辑,网络操作,数据库操作
- View: UI
- ViewModel: 将View和Model绑定
分层分模块
Understanding MVC, MVP and MVVM Design Patterns
- adapter 存放适配器
- app 存放常量类,Application类以及一些app层级的全局类
- database 数据库相关类
- event EventBus使用的事件类
- factory 工厂类
- model 数据模型
- presenter MVP模型中的Presenter类
- ui 存放activity和fragment
- utils 工具类
- view MVP模型中的View类
- widget 自定义控件
- BaseActivity
- BaseFragment
- 如果没有登录,延时2s, 跳转到登录界面
- 如果已经登录,则跳转到主界面
- SplashView
- SplashPresenter
- 有两种情况都可以发起登录操作,一是点击登录按钮,而是点击虚拟键盘上的Action键。
- 点击新用户,跳转到注册界面。
注意配置EditText的imeOptions属性时,需要配合inputType才能起作用。
android:imeOptions="actionNext"//下一个 android:imeOptions="actionGo"//启动 android:imeOptions="actionDone"//完成 android:imeOptions="actionPrevious"//上一个 android:imeOptions="actionSearch"//搜索 android:imeOptions="actionSend"//发送
- LoginView
- LoginPresenter
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) { } }
举个栗子:高德地图 百度地图等
/** * 是否有写磁盘权限 */ private boolean hasWriteExternalStoragePermission() { int result = ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE); return result == PermissionChecker.PERMISSION_GRANTED; } /** * 申请权限 */ private void applyPermission() { String[] permissions = {Manifest.permission.WRITE_EXTERNAL_STORAGE}; ActivityCompat.requestPermissions(this, permissions, REQUEST_WRITE_EXTERNAL_STORAGE); } /** * 申请权限回调 */ @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; } }
- 用户名的长度必须是3-20位,首字母必须为英文字符,其他字符则除了英文外还可以是数字或者下划线。
- 密码必须是3-20位的数字。
- 密码和确认密码一致
private static final String USER_NAME_REGEX = "^[a-zA-Z]\\w{2,19}$"; private static final String PASSWORD_REGEX = "^[0-9]{3,20}$";
- \w 匹配包括下划线的任何单词字符。等价于'[A-Za-z0-9_]'。
- RegisterView
- RegisterPresenter
- 实际项目中,注册会将用户名和密码注册到APP的服务器,然后APP的服务器再通过REST API方式注册到环信服务器。
- 由于本项目没有APP服务器,会将用户数据注册到第三方云数据库Bmob,注册成功后,在客户端发送请求注册到环信服务器。
- 注册创建应用
- 下载SDK
- 导入SDK
- 初始化SDk
protected void hideSoftKeyboard() { if (mInputMethodManager == null) { mInputMethodManager = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE); } mInputMethodManager.hideSoftInputFromWindow(getCurrentFocus().getWindowToken(), 0); }
private TextView.OnEditorActionListener mOnEditorActionListener = new TextView.OnEditorActionListener() { @Override public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { if (actionId == EditorInfo.IME_ACTION_GO) { reigister();//注册 return true; } return false; } };
RadioGroup, TabHost, FragmentTabHost, 自定义
- ContactView
- ContactPresenter
private boolean itemInSameGroup(int i, ContactItem item) { return i > 0 && (item.getFirstLetter() == mContactItems.get(i - 1).getFirstLetter()); }
mSwipeRefreshLayout.setColorSchemeResources(R.color.qq_blue, R.color.qq_red); mSwipeRefreshLayout.setOnRefreshListener(mOnRefreshListener);
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"};
http://www.cnblogs.com/tianzhijiexian/p/4297664.html
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); } }; /** * RecyclerView滚动直到界面出现对应section的联系人 * * @param section 首字符 */ private void scrollToSection(String section) { int sectionPosition = getSectionPosition(section); if (sectionPosition != POSITION_NOT_FOUND) { mRecyclerView.smoothScrollToPosition(sectionPosition); } } /** * * @param section 首字符 * @return 在联系人列表中首字符是section的第一个联系人在联系人列表中的位置 */ private int getSectionPosition(String section) { List<ContactItem> contactItems = mContactListAdapter.getContactItems(); for (int i = 0; i < contactItems.size(); i++) { if (section.equals(contactItems.get(i).getFirstLetterString())) { return i; } } return POSITION_NOT_FOUND; }
private ContactListAdapter.OnItemClickListener mOnItemClickListener = new ContactListAdapter.OnItemClickListener() { /** * 单击跳转到聊天界面 * @param name 点击item的联系人名字 */ @Override public void onItemClick(String name) { startActivity(ChatActivity.class, Constant.Extra.USER_NAME, name); } /** * 长按删除好友 * @param name 点击item的联系人名字 */ @Override 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(); } };
@Override public void searchFriend(final String keyword) { mAddFriendView.onStartSearch(); //注:模糊查询只对付费用户开放,付费后可直接使用。 BmobQuery<User> query = new BmobQuery<User>(); query.addWhereContains("username", keyword).addWhereNotEqualTo("username", EMClient.getInstance().getCurrentUser()); query.findObjects(new FindListener<User>() { @Override public void done(List<User> list, BmobException e) { processResult(list, e); } }); }
greenDAO是Android SQLite数据库ORM框架的一种。ORM即对象关系映射, object/relational mapping, 将Java对象映射成数据库的表。
@Entity public class Contact { @Id public Long id; public String userName; }
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(); }
public void saveContact(String userName) { Contact contact = new Contact(); contact.setUsername(userName); mDaoSession.getContactDao().save(contact); }
public List<String> queryAllContacts() { List<Contact> list = mDaoSession.getContactDao().queryBuilder().list(); ArrayList<String> contacts = new ArrayList<String>(); for (int i = 0; i < list.size(); i++) { String contact = list.get(i).getUsername(); contacts.add(contact); } return contacts; }
public void deleteAllContacts() { ContactDao contactDao = mDaoSession.getContactDao(); contactDao.deleteAll(); }
@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); }
@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(); } }); }
private EMContactListenerAdapter mEMContactListener = new EMContactListenerAdapter() { @Override public void onContactAdded(String s) { mContactPresenter.refreshContactList(); } @Override public void onContactDeleted(String s) { mContactPresenter.refreshContactList(); } };
mEdit.addTextChangedListener(mTextWatcher); private TextWatcherAdapter mTextWatcher = new TextWatcherAdapter() { @Override public void afterTextChanged(Editable s) { mSend.setEnabled(s.length() != 0); } };
- anim文件夹:存放补间动画
- animator文件夹:存放属性动画
- drawable文件夹:存放帧动画
<?xml version="1.0" encoding="utf-8"?> <animation-list xmlns:android="http://schemas.android.com/apk/res/android"> <item android:drawable="@mipmap/loading1" android:duration="100"/> <item android:drawable="@mipmap/loading2" android:duration="100"/> <item android:drawable="@mipmap/loading3" android:duration="100"/> <item android:drawable="@mipmap/loading4" android:duration="100"/> <item android:drawable="@mipmap/loading5" android:duration="100"/> <item android:drawable="@mipmap/loading6" android:duration="100"/> <item android:drawable="@mipmap/loading7" android:duration="100"/> <item android:drawable="@mipmap/loading8" android:duration="100"/> </animation-list>
@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; }
/** * 如果两个消息之间的时间太近,就不显示时间戳 */ 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; }
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; } }
private EMMessageListenerAdapter mEMMessageListener = new EMMessageListenerAdapter() { @Override public void onMessageReceived(final List<EMMessage> list) { ThreadUtils.runOnUiThread(new Runnable() { @Override public void run() { final EMMessage emMessage = list.get(0); mChatPresenter.makeMessageRead(mUserName); mMessageListAdapter.addNewMessage(emMessage); smoothScrollToBottom(); } }); } };
@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) { //获取此会话的所有消息 List<EMMessage> messages = conversation.getAllMessages(); mEMMessageList.addAll(messages); //指定会话消息未读数清零 conversation.markAllMessagesAsRead(); } ThreadUtils.runOnUiThread(new Runnable() { @Override public void run() { mChatView.onMessagesLoaded(); } }); } }); }
@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); //SDK初始化加载的聊天记录为20条,到顶时需要去DB里获取更多 //获取startMsgId之前的pagesize条消息,此方法获取的messages SDK会自动存入到此会话中,APP中无需再次把获取到的messages添加到会话中 final List<EMMessage> 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(); } }
- ConversationView
- ConversationPresenter
@Override public void loadAllConversations() { ThreadUtils.runOnBackgroundThread(new Runnable() { @Override public void run() { Map<String, EMConversation> conversations = EMClient.getInstance().chatManager().getAllConversations(); mEMConversations.addAll(conversations.values()); Collections.sort(mEMConversations, new Comparator<EMConversation>() { @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(); } }); } }); }
private EMMessageListenerAdapter mEMMessageListenerAdapter = new EMMessageListenerAdapter() { @Override public void onMessageReceived(List<EMMessage> list) { ThreadUtils.runOnUiThread(new Runnable() { @Override public void run() { toast(getString(R.string.receive_new_message)); mConversationPresenter.loadAllConversations(); } }); } };
private EMMessageListenerAdapter mEMMessageListenerAdapter = new EMMessageListenerAdapter() { //该回调在子线程中调用 @Override public void onMessageReceived(List<EMMessage> 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); } }); }
//指定会话消息未读数清零 conversation.markAllMessagesAsRead();
@Override protected void onResume() { super.onResume(); updateUnreadCount(); }
public boolean isForeground() { ActivityManager am = (ActivityManager) getSystemService(ACTIVITY_SERVICE); List<ActivityManager.RunningAppProcessInfo> 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; }
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); }
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); }
private EMMessageListenerAdapter mEMMessageListenerAdapter = new EMMessageListenerAdapter() { @Override public void onMessageReceived(List<EMMessage> list) { if (isForeground()) { mSoundPool.play(mDuanSound, 1, 1, 0, 0, 1); } else { mSoundPool.play(mYuluSound, 1, 1, 0, 0, 1); showNotification(list.get(0)); } } };
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)); } }); } } };
QQDemo Copyright (c) 2016 Leon Fan (https://github.com/uncleleonfan). Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
qqdemo's People
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
Jobs
Jooble