最近看到有许多人在说根据环信官方文档导入 EaseUI 会出现各种错误,由于官方(此处和谐省略一万字), 顺便学学新的 add dependencies 方式,记录一下, Android Studio 3.3 add dependencies 方式又改了。
Android Studio 版本 : 3.3 Canary 5
环信 SDK 版本 : easemob-sdk-3.5.0
先新建个项目
下载 easemob-sdk 里面带有 EaseUI
导入这个 easeui
File -> New -> Import Module
选择 easeui 所在的路径
点击 Finish
导入之后会有红色的提示,四个都只是 WARNING,把四个 WANRNING 解决掉
第一个 WANRNING,是 Build Tools 的问题,Android Studio 3.3.0-alpha05 最低支持的 Build Tools 版本是 27.0.3 ,把 easeui 的 build.gradle 的 buildToolsVersion 改为 27.0.3 顺便把 compileSdkVersion 和 targetSdkVersion 也改为 27,同步一下 gradle,第一个 WANRNING 已经没了
后三个 WANRNING,是 AndroidStudio 3.0 之后的问题,
compile 改为 implementation 或 api,
implementation 和 api 是有区别的,implementation 是指这个导入的 Library 是 Module 内部使用的,别的 Module 导入当前这个 Module 就用不了当前 Module 导入的 Library 的东西,api 是指别的 Module 导入当前这个 Module 后仍然能够使用当前 Module 导入的 Library 的东西,
testCompile 改为 testImplementation,
androidTestCompile 改为 androidTestImplementation,
由于 easeui 的 libs 文件夹里有环信的 hyphenatechat_3.5.0.jar ,由于 app 里要用到 hyphenatechat 里的东西,所以 fileTree() 要用 api 而不是 implementation,改完后同步一下 gradle,剩下的三个 WANRNING 都没了
接下来 app 添加上 easeui 这个 Module ,在项目右键 Open Module Settings
选择 Dependencies,Modules 选择 app,Declared dependencies 点击 + 号
上面 step 1 的 easeui 打上勾,Step 2 选 implementation,点击 OK,再点 Apply 或 OK
由于 AndroidStudio 3.2 起开始推荐使用 androidx, 但 easeui 里用的是 android, 所以会报错,要么把 easeui 里的改为 androidx 要么不使用 androidx,改 easeui 的话那就太多要改的了,不使用 androidx 了,
打开 gradle.properties 注释掉 android.useAndroidX=true 和 android.enableJetifier=true
打开 app 的 build.gradle 也要改,compileSdkVersion, buildToolsVersion, targetSdkVersion 那些改和 easeui 一样的,dependencies androidx 的改为 android 的,改完同步一下 gradle, 由于去掉了 androidx, Activity 里的 AppCompatActivity 和 布局里的 ConstraintLayout 也要改为 android 的而不是 androidx 的
看起来好像没什么问题了,先运行一下,
出问题了,support v4 包没有 AsyncTaskCompat 这个类了,打开 EaseChatRowImage.java 这个类
去掉 AsyncTaskCompat,改直接执行 AsyncTask ,
再运行一下,可以运行起来
虽然导入了 easeui, 但 easeui 还没在 app 里初始化和用起来,先初始化 easeui
新建个 App 类继承 Application,然后在 AndroidManifest 里设置
public class App extends Application {
@Override
public void onCreate() {
super.onCreate();
initEaseUi();
}
private void initEaseUi() {
EMOptions emOptions = new EMOptions();
emOptions.setAcceptInvitationAlways(false);
EaseUI.getInstance().init(this, new EMOptions());
}
}
再运行一次 , 报错了,看下 Log
说没有在 AndroidManifest 里设置 APPKEY,那就设置一下了,可参考官方的 环信官方配置工程
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.ce.easeuitest">
<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:name=".App"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme"
tools:ignore="GoogleAppIndexingWarning">
<meta-data android:name="EASEMOB_APPKEY" android:value="1111180606228105#easeuitest" />
<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"
/>
<receiver android:name="com.hyphenate.chat.EMMonitorReceiver">
<intent-filter>
<action android:name="android.intent.action.PACKAGE_REMOVED"/>
<data android:scheme="package"/>
intent-filter>
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/>
<action android:name="android.intent.action.USER_PRESENT" />
intent-filter>
receiver>
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
intent-filter>
activity>
application>
manifest>
再运行一次, 可以运行起来,再看 log 没有问题
做个登录功能测试一下,新建个 LoginActivity
修改一下 LoginActivity 的代码逻辑并实现登录操作,为了直接点,这里就在 Activity 里去开启线程做登录操作了
/**
* A login screen that offers login via email/password.
*/
public class LoginActivity extends AppCompatActivity{
// UI references.
private AutoCompleteTextView mEmailView;
private EditText mPasswordView;
private View mProgressView;
private View mLoginFormView;
private Handler mHandler = new Handler();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
// Set up the login form.
mEmailView = (AutoCompleteTextView) findViewById(R.id.email);
mPasswordView = (EditText) findViewById(R.id.password);
mPasswordView.setOnEditorActionListener(new TextView.OnEditorActionListener() {
@Override
public boolean onEditorAction(TextView textView, int id, KeyEvent keyEvent) {
if (id == EditorInfo.IME_ACTION_DONE || id == EditorInfo.IME_NULL) {
attemptLogin();
return true;
}
return false;
}
});
Button mEmailSignInButton = (Button) findViewById(R.id.email_sign_in_button);
mEmailSignInButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
attemptLogin();
}
});
mLoginFormView = findViewById(R.id.login_form);
mProgressView = findViewById(R.id.login_progress);
}
@Override
protected void onDestroy() {
mHandler.removeCallbacksAndMessages(null);
super.onDestroy();
}
/**
* Attempts to sign in or register the account specified by the login form.
* If there are form errors (invalid email, missing fields, etc.), the
* errors are presented and no actual login attempt is made.
*/
private void attemptLogin() {
// Reset errors.
mEmailView.setError(null);
mPasswordView.setError(null);
// Store values at the time of the login attempt.
String email = mEmailView.getText().toString();
String password = mPasswordView.getText().toString();
boolean cancel = false;
View focusView = null;
// Check for a valid password, if the user entered one.
if (!TextUtils.isEmpty(password) && !isPasswordValid(password)) {
mPasswordView.setError(getString(R.string.error_invalid_password));
focusView = mPasswordView;
cancel = true;
}
// Check for a valid email address.
if (TextUtils.isEmpty(email)) {
mEmailView.setError(getString(R.string.error_field_required));
focusView = mEmailView;
cancel = true;
} else if (!isEmailValid(email)) {
mEmailView.setError(getString(R.string.error_invalid_email));
focusView = mEmailView;
cancel = true;
}
if (cancel) {
// There was an error; don't attempt login and focus the first
// form field with an error.
focusView.requestFocus();
} else {
// Show a progress spinner, and kick off a background task to
// perform the user login attempt.
showProgress(true);
// 开启线程去做登录操作
new LoginThread(email, password).start();
}
}
private boolean isEmailValid(String email) {
return email.length() > 4;
}
private boolean isPasswordValid(String password) {
return password.length() > 4;
}
/**
* Shows the progress UI and hides the login form.
*/
@TargetApi(Build.VERSION_CODES.HONEYCOMB_MR2)
private void showProgress(final boolean show) {
// On Honeycomb MR2 we have the ViewPropertyAnimator APIs, which allow
// for very easy animations. If available, use these APIs to fade-in
// the progress spinner.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2) {
int shortAnimTime = getResources().getInteger(android.R.integer.config_shortAnimTime);
mLoginFormView.setVisibility(show ? View.GONE : View.VISIBLE);
mLoginFormView.animate().setDuration(shortAnimTime).alpha(
show ? 0 : 1).setListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mLoginFormView.setVisibility(show ? View.GONE : View.VISIBLE);
}
});
mProgressView.setVisibility(show ? View.VISIBLE : View.GONE);
mProgressView.animate().setDuration(shortAnimTime).alpha(
show ? 1 : 0).setListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mProgressView.setVisibility(show ? View.VISIBLE : View.GONE);
}
});
} else {
// The ViewPropertyAnimator APIs are not available, so simply show
// and hide the relevant UI components.
mProgressView.setVisibility(show ? View.VISIBLE : View.GONE);
mLoginFormView.setVisibility(show ? View.GONE : View.VISIBLE);
}
}
private class LoginThread extends Thread {
String username;
String password;
public LoginThread(String username, String password) {
this.username = username;
this.password = password;
}
@Override
public void run() {
EMClient.getInstance().login(username, password, new EMCallBack() {
@Override
public void onSuccess() {
mHandler.post(new Runnable() {
@Override
public void run() {
showProgress(false);
// 登录成功跳转去 MainActivity
Intent intent = new Intent(LoginActivity.this, MainActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
startActivity(intent);
finish();
}
});
}
@Override
public void onError(int i, String s) {
mHandler.post(new Runnable() {
@Override
public void run() {
// 登录失败
showProgress(false);
mPasswordView.setError(getString(R.string.error_invalid_password));
View focusView = mPasswordView;
focusView.requestFocus();
}
});
}
@Override
public void onProgress(int i, String s) {
}
});
}
}
}
修改下 Manifest,让 LoginActivity 先运行
<activity
android:name=".LoginActivity"
android:label="@string/title_activity_login">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
intent-filter>
activity>
由于登录后跳转到 MainActivity ,那就简单地在 MainActivity 做个联系人列表显示,
新建个 ContactFragment 类继承 EaseContactListFragment,这里也简单点,用个 AsyncTask 去获取所有的联系人然后显示出来,
public class ContactFragment extends EaseContactListFragment {
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
new GetContact().execute();
}
private class GetContact extends AsyncTask<Void, Void, Map<String, EaseUser>> {
@Override
protected Map doInBackground(Void... voids) {
try {
Map contactMap = new HashMap<>();
List contacts = EMClient.getInstance().contactManager().getAllContactsFromServer();
for (String contact : contacts) {
EaseUser easeUser = new EaseUser(contact);
contactMap.put(contact, easeUser);
}
return contactMap;
} catch (HyphenateException e) {
e.printStackTrace();
}
return null;
}
@Override
protected void onPostExecute(Map easeUserMap) {
if (easeUserMap != null && !easeUserMap.isEmpty()) {
setContactsMap(easeUserMap);
refresh();
}
}
}
}
把 ContactFragment 放到 MainActivity 去显示,
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
FragmentManager fragmentManager = getSupportFragmentManager();
fragmentManager.beginTransaction()
.replace(R.id.lay_content, new ContactFragment())
.commitNowAllowingStateLoss();
}
}
一切准备就绪,等等,还有账号还没准备,先注册几个账号,
添加几个好友进去
运行起来看看吧,可以登录并获取到所有联系人
导入 easeui 确实有几个坑的地方,不过没多坑,Android Studio 3.3 的 add dependencies 方式感觉更加友好了。
本文源码