基於環信的仿QQ即時通信的簡單實現

代碼地址以下:
http://www.demodashi.com/demo/11645.htmlhtml

個人博客地址android

以前一直想實現聊天的功能,可是感受有點困難,今天看了環信的API,就利用下午的時間動手試了試,而後作了一個小Demo。

由於沒有刻意去作聊天軟件,花的時間也很少,而後界面就很簡單,都是一些基本知識,若是以爲功能簡單,能夠自行添加,我這就很少介紹了。

照例先來一波動態演示:
環信及時聊天.gifgit

功能很簡單,註冊用戶 --> 用戶登陸 --> 選擇聊天對象 --> 開始聊天github

使用到的知識點:apache

  1. RecyclerView
  2. CardView
  3. 環信的API的簡單使用

依賴的庫api

compile 'com.android.support:appcompat-v7:24.2.1'
compile 'com.android.support:cardview-v7:24.1.1'
compile 'com.android.support:recyclerview-v7:24.0.0'

一、聊天頁面

首先是看了郭神的《第二行代碼》作了聊天界面,用的是RecyclerView服務器

#### a. 消息類的封裝 ####

public class MSG {
    public static final int TYPE_RECEIVED = 0;//消息的類型:接收
    public static final int TYPE_SEND = 1;    //消息的類型:發送

    private String content;//消息的內容
    private int type;      //消息的類型

    public MSG(String content, int type) {
        this.content = content;
        this.type = type;
    }

    public String getContent() {
        return content;
    }

    public int getType() {
        return type;
    }
}

#### b. RecyclerView子項的佈局 ####

<LinearLayout
    android:id="@+id/ll_msg_left"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    <!-- 設置點擊效果爲水波紋(5.0以上) -->
    android:background="?android:attr/selectableItemBackground"
    android:clickable="true"
    android:focusable="true"
    android:orientation="horizontal"
    android:padding="2dp">

    <android.support.v7.widget.CardView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:cardCornerRadius="20dp"
        app:cardPreventCornerOverlap="false"
        app:cardUseCompatPadding="true">

        <ImageView
            android:layout_width="50dp"
            android:layout_height="50dp"
            android:scaleType="centerCrop"
            android:src="@mipmap/man" />
    </android.support.v7.widget.CardView>

    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@drawable/message_left"
        android:orientation="horizontal">

        <TextView
            android:id="@+id/tv_msg_left"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:layout_margin="10dp"
            android:textColor="#fff" />
    </LinearLayout>

</LinearLayout>

這是左邊的部分,至於右邊應該也就簡單了。我用CardView把ImageView包裹起來,這樣比較好看。效果以下:app

item佈局.png

#### c. RecyclerView適配器 ####

public class MsgAdapter extends RecyclerView.Adapter<MsgAdapter.MyViewHolder> {

    private List<MSG> mMsgList;

    public MsgAdapter(List<MSG> mMsgList) {
        this.mMsgList = mMsgList;
    }

    @Override
    public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view = View.inflate(parent.getContext(), R.layout.item_msg, null);
        MyViewHolder holder = new MyViewHolder(view);
        return holder;
    }

    @Override
    public void onBindViewHolder(MyViewHolder holder, int position) {
        MSG msg = mMsgList.get(position);
        if (msg.getType() == MSG.TYPE_RECEIVED){
            //若是是收到的消息,顯示左邊佈局,隱藏右邊佈局
            holder.llLeft.setVisibility(View.VISIBLE);
            holder.llRight.setVisibility(View.GONE);
            holder.tv_Left.setText(msg.getContent());
        } else if (msg.getType() == MSG.TYPE_SEND){
            //若是是發送的消息,顯示右邊佈局,隱藏左邊佈局
            holder.llLeft.setVisibility(View.GONE);
            holder.llRight.setVisibility(View.VISIBLE);
            holder.tv_Right.setText(msg.getContent());
        }
    }

    @Override
    public int getItemCount() {
        return mMsgList.size();
    }

    static class MyViewHolder extends RecyclerView.ViewHolder{

        LinearLayout llLeft;
        LinearLayout llRight;

        TextView tv_Left;
        TextView tv_Right;

        public MyViewHolder(View itemView) {
            super(itemView);

            llLeft = (LinearLayout) itemView.findViewById(R.id.ll_msg_left);
            llRight = (LinearLayout) itemView.findViewById(R.id.ll_msg_right);

            tv_Left = (TextView) itemView.findViewById(R.id.tv_msg_left);
            tv_Right = (TextView) itemView.findViewById(R.id.tv_msg_right);

        }
    }
}

這部分應該也沒什麼問題,就是適配器的建立,我以前的文章也講過 傳送門:簡單粗暴----RecyclerViewmaven

#### d. RecyclerView初始化 ####

就是一些基本的初始化,我就不贅述了,講一下添加數據的細節處理ide

btSend.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            String content = etInput.getText().toString().trim();
            if (!TextUtils.isEmpty(content)){

                ...//環信部分的發送消息

                MSG msg = new MSG(content, MSG.TYPE_SEND);
                mList.add(msg);
                //當有新消息時,刷新RecyclerView中的顯示
                mAdapter.notifyItemInserted(mList.size() - 1);
                //將RecyclerView定位到最後一行
                mRecyclerView.scrollToPosition(mList.size() - 1);
                etInput.setText("");
            }
        }
    });

至此界面已經結束了,接下來就是數據的讀取

2. 環信API的簡單應用

官網有詳細的API介紹 環信及時通信V3.0,我這裏就簡單介紹如何簡單集成

#### a. 環信開發帳號的註冊 ####

環信官網

建立應用獲得Appkey後面要用
環信註冊.PNG

#### b. SDK導入 ####

你能夠直接下載而後拷貝工程的libs目錄下

Android Studio能夠直接添加依賴

將如下代碼放到項目根目錄的build.gradle文件裏

repositories {
     maven { url "https://raw.githubusercontent.com/HyphenateInc/Hyphenate-SDK-Android/master/repository" }
}

在你的module的build.gradle里加入如下代碼

android {
    //use legacy for android 6.0
    useLibrary 'org.apache.http.legacy'
}
dependencies {
    compile 'com.android.support:appcompat-v7:23.4.0'
    //Optional compile for GCM (Google Cloud Messaging).
    compile 'com.google.android.gms:play-services-gcm:9.4.0'
    compile 'com.hyphenate:hyphenate-sdk:3.2.3'
}

若是想使用不包含音視頻通話的sdk,用compile 'com.hyphenate:hyphenate-sdk-lite:3.2.3'

#### c. 清單文件配置 ####

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="Your Package"
    android:versionCode="100"
    android:versionName="1.0.0">

    <!-- Required -->
    <uses-permission android:name="android.permission.VIBRATE" />
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.RECORD_AUDIO" />
    <uses-permission android:name="android.permission.CAMERA" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.ACCESS_MOCK_LOCATION" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>  
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <uses-permission android:name="android.permission.GET_TASKS" />
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
    <uses-permission android:name="android.permission.WAKE_LOCK" />
    <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
  
    <application
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:name="Your Application">
   
    <!-- 設置環信應用的AppKey -->
        <meta-data android:name="EASEMOB_APPKEY"  android:value="Your AppKey" />
        <!-- 聲明SDK所需的service SDK核心功能-->
        <service android:name="com.hyphenate.chat.EMChatService" android:exported="true"/>
        <service android:name="com.hyphenate.chat.EMJobService"
            android:permission="android.permission.BIND_JOB_SERVICE"
            android:exported="true"
            />
        <!-- 聲明SDK所需的receiver -->
        <receiver android:name="com.hyphenate.chat.EMMonitorReceiver">
            <intent-filter>
                <action android:name="android.intent.action.PACKAGE_REMOVED"/>
                <data android:scheme="package"/>
            </intent-filter>
            <!-- 可選filter -->
            <intent-filter>
                <action android:name="android.intent.action.BOOT_COMPLETED"/>
                <action android:name="android.intent.action.USER_PRESENT" />
            </intent-filter>
        </receiver>
    </application>
</manifest>

APP打包混淆

-keep class com.hyphenate.** {*;}
-dontwarn  com.hyphenate.**

#### d. 初始化SDK ####

在自定義Application的onCreate中初始化

public class MyApplication extends Application {

    private Context appContext;

    @Override
    public void onCreate() {
        super.onCreate();
        EMOptions options = new EMOptions();
        options.setAcceptInvitationAlways(false);

        appContext = this;
        int pid = android.os.Process.myPid();
        String processAppName = getAppName(pid);
        // 若是APP啓用了遠程的service,此application:onCreate會被調用2次
        // 爲了防止環信SDK被初始化2次,加此判斷會保證SDK被初始化1次
        // 默認的APP會在以包名爲默認的process name下運行,若是查到的process name不是APP的process name就當即返回

        if (processAppName == null || !processAppName.equalsIgnoreCase(appContext.getPackageName())) {
            Log.e("--->", "enter the service process!");

            // 則此application::onCreate 是被service 調用的,直接返回
            return;
        }

        //初始化
        EMClient.getInstance().init(getApplicationContext(), options);
        //在作打包混淆時,關閉debug模式,避免消耗沒必要要的資源
        EMClient.getInstance().setDebugMode(true);
    }

    private String getAppName(int pID) {
        String processName = null;
        ActivityManager am = (ActivityManager) this.getSystemService(ACTIVITY_SERVICE);
        List l = am.getRunningAppProcesses();
        Iterator i = l.iterator();
        PackageManager pm = this.getPackageManager();
        while (i.hasNext()) {
            ActivityManager.RunningAppProcessInfo info = (ActivityManager.RunningAppProcessInfo) (i.next());
            try {
                if (info.pid == pID) {
                    processName = info.processName;
                    return processName;
                }
            } catch (Exception e) {
                // Log.d("Process", "Error>> :"+ e.toString());
            }
        }
        return processName;
    }
}

#### e. 註冊和登錄 ####

註冊要在子線程中執行

//註冊失敗會拋出HyphenateException
EMClient.getInstance().createAccount(username, pwd);//同步方法

EMClient.getInstance().login(userName,password,new EMCallBack() {//回調
    @Override
    public void onSuccess() {
        EMClient.getInstance().groupManager().loadAllGroups();
        EMClient.getInstance().chatManager().loadAllConversations();
            Log.d("main", "登陸聊天服務器成功!");        
    }
 
    @Override
    public void onProgress(int progress, String status) {
 
    }
 
    @Override
    public void onError(int code, String message) {
        Log.d("main", "登陸聊天服務器失敗!");
    }
});

#### f. 發送消息 ####

//建立一條文本消息,content爲消息文字內容,toChatUsername爲對方用戶或者羣聊的id,後文皆是如此
EMMessage message = EMMessage.createTxtSendMessage(content, toChatUsername);
//發送消息
EMClient.getInstance().chatManager().sendMessage(message);

#### g. 接收消息 ####

msgListener = new EMMessageListener() {

        @Override
        public void onMessageReceived(List<EMMessage> messages) {
            //收到消息
            String result = messages.get(0).getBody().toString();
            String msgReceived = result.substring(5, result.length() - 1);

            Log.i(TAG, "onMessageReceived: " + msgReceived);
            final MSG msg = new MSG(msgReceived, MSG.TYPE_RECEIVED);
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    mList.add(msg);
                    mAdapter.notifyDataSetChanged();
                    mRecyclerView.scrollToPosition(mList.size() - 1);
                }
            });
        }

        @Override
        public void onCmdMessageReceived(List<EMMessage> messages) {
            //收到透傳消息
        }

        @Override
        public void onMessageRead(List<EMMessage> list) {

        }

        @Override
        public void onMessageDelivered(List<EMMessage> list) {

        }

        @Override
        public void onMessageChanged(EMMessage message, Object change) {
            //消息狀態變更
        }
    };

接收消息的監聽器分別須要在OnResume()和OnDestory()方法中註冊和取消註冊

EMClient.getInstance().chatManager().addMessageListener(msgListener);//註冊

EMClient.getInstance().chatManager().removeMessageListener(msgListener);//取消註冊

須要注意的是,當接收到消息,須要在主線程中更新適配器,不然會不能及時刷新出來

項目文件截圖:


到此,一個簡單的及時聊天Demo已經完成,功能很簡單,若是須要添加額外功能的話,能夠自行參考官網,官網給出的教程仍是很不錯的!

最後但願你們能多多支持我,須要大家的支持喜歡!!
基於環信的仿QQ即時通信的簡單實現

代碼地址以下:
http://www.demodashi.com/demo/11645.html

注:本文著做權歸做者,由demo大師代發,拒絕轉載,轉載須要做者受權

相關文章
相關標籤/搜索