-
- ijkPlayer
- ExoPlayer
- EventBus
- Glide
- Retrofit
- Okhttp
- 云信IM
- ffmpeg
- uni SDK
- mui SDK
- 阿里SDK
- 腾讯SDK
- 音频转码 ffmpeg com.github.microshow:RxFFmpeg:4.8.0-lite
- [组件化 io.github.prototypez:app-joint-core:1.6.1](#组件化 io.github.prototypez:app-joint-core:1.6.1)
- 权限请求 pub.devrel:easypermissions:0.3.0
- butterknife com.jakewharton:butterknife-compiler:10.2.1
- 流式布局 com.hyman:flowlayout-lib:1.1.2
-
- 折叠屏
- [uni app底部白条解决方案](#uni app底部白条解决方案)
主题色:
链信公开课:
- 主题色: #FF8048
- 深主题色:#FB5E19
叽喳:
- 主题色: #39A6FF
- 深主题色:#308CD8
打包:
叽喳:
- idea左侧 Build Variants 切换到 gchat
- 更新 app/src/gchat/assets/apps/com.wggs.gchat/www(h5路径) 下的h5代码
- 配置h5路径下的 js/config 正式或测试环境,h5路径下的manifest.json 版本号
- 配置根目录下 gradle.properties 正式或测试环境
- 还原叽喳代码SDK.startWebApp 全局搜:叽喳需要打开此注释
链信公开课:
- idea左侧 Build Variants 切换到 lianxinClass
- 将打包后uni包放到 app/src/lianxinClass/assets/apps/__UNI__xxx/www(h5路径) 下
- 配置根目录下 gradle.properties 正式或测试环境
- dcloud_control设为
<hbuilder version="1.9.9.80110" debug="false" syncDebug ="false">
- 注释叽喳代码SDK.startWebApp 全局搜:叽喳需要打开此注释
ChainChat Class:
- 切换到 lianxinMP 分支
原生项目地址:
uni项目注意事项:
- App.vue 的 appSwitch:true
- request.js 的 改成
let baseUrl = process.env.NODE_ENV
- uni已添加jar包:
- uniapp-release
- payment-weixin-release 微信支付
- share-weixin-release 微信分享
- auth-weixin-release 微信授权
- media-release 视频
apk:
减小apk:全局搜:减小apk,已经减掉(由于音视频用到,已还原)
- faceunity已减 assets 文件
云信
点播视频:
- 添加vcloudnosupload依赖
- vcloudnosupload 添加nos-android-sdk-allin-2.0.5jar包
uni SDK:
- audio-mp3aac-release.aar (在app)
- gallery-dmcBig-release.aar (在app 已废弃,只在叽喳使用)
- lib.5plus.base-release.aar (在app)
- media-release.aar (在app)
- msa_mdid_1.0.13.aar (或者 miit_mdid_1.0.10.aar)(在app)
- oauth-weixin-release.aar (在app)
- payment-weixin-release.aar (在app)
- share-weixin-release.aar (在app)
- uniapp-v8-release.aar (在uikit)
ThirdPart
腾讯X5webview:
tbs_sdk_thirdapp_v4.3.0.3_43903_sharewithdownloadwithfile_withoutGame_obfs_20200402_121309.jar
折叠屏
已删除今日头条屏幕适配方案
implementation 'me.jessyan:autosize:1.1.2'
<meta-data
android:name="design_width_in_dp"
android:value="375" />
<meta-data
android:name="design_height_in_dp"
android:value="667" />
华为折叠屏适配方案:
1.在每个activity里加:
android:configChanges="screenSize|smallestScreenSize|screenLayout"
2.在 manifest 文件的 < application > 节点中增加 < meta-data > 数据:
<meta-data
android:name="android.min_aspect"
android:value="1.0" />
国际化多语言
- 获取本地国家
Locale.getDefault().getLanguage()
- 获取本地区域(如:香港 HK、台湾 TW)
Locale.getDefault().getCountry()
- 适配聊天列表(全局搜:适配多语言需要用到)
- Attachment
@Override
protected void parseData(JSONObject data) {
try {
msgJson = getLanguage(data);
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
protected JSONObject packData() {
JSONObject data = new JSONObject();
try {
if (null != msgJson) {
data.put(MSG_JSON, msgJson);
}
} catch (Exception e) {
e.printStackTrace();
}
return data;
}
private LanguageBean msgJson;
public String getMsgJson() {
return TextUtil.getMsgJson(msgJson);
}
2. MsgViewHolder
Attachment attachment = (Attachment) message.getAttachment();
String text = "";
String msgJson = attachment.getMsgJson();
if (!TextUtils.isEmpty(msgJson)) {
text = msgJson;
} else if (!TextUtils.isEmpty(attachment.getMsg())) {
text = attachment.getMsg();
}
textView.setText(text);
-
适配链信公开课无多语言,叽喳需多语言
- 中文多语言包放入
common的src\main\res\values\strings.xml
- 链信公开课
- 在
app的src\lianxinClass\res\values\strings.xml
放入区分链信公开课和叽喳的文字
- 在
- 叽喳
- 在
app的src\gchat\res\values\strings.xml
放入区分链信公开课和叽喳的文字 - 在
app的src\gchat\res\values-en\strings.xml
放入区叽喳英文文字 - 在
app的src\gchat\res\values-ja-rJP\strings.xml
放入区叽喳日文文字 - 在
app的src\gchat\res\values-ko-rKR\strings.xml
放入区叽喳韩文文字 - 在
app的src\gchat\res\values-zh-rHK\strings.xml
放入区叽喳繁体中文 - 在
app的src\gchat\res\values-zh-rTW\strings.xml
放入区叽喳繁体中文
- 在
- 中文多语言包放入
-
获取文字:
- activity获取文字
this.getContext().getResources().getString(R.string.ok)
- xml获取文字
android:text="@string/ok"
- 跨module获取文字
AppJoint.service(ImService.class).getString(R.string.ok)
- 静态类获取文字
BaseApp.getContext().getResources().getString(R.string.ok)
- activity获取文字
EventBus
-
先
EventBus.getDefault()
生成一个EventBus()单例,并初始化EventBus所需数据 -
注册,注销EventBus
a.EventBus.getDefault().register(this)
注册EventBus
b.EventBus.getDefault().unregister(this)
注销EventBus
c.subscriber.getClass()
获取每个注册的Class 放入 subscriptionsByEventType -
EventBus.getDefault().post("hello")
调用post(Object event)方法,post方法执行:-
放入队列
用ThreadLocal<PostingThreadState>
创建currentPostingThreadState
,
PostingThreadState postingState = currentPostingThreadState.get(); List<Object> eventQueue = postingState.eventQueue; eventQueue.add(event);
把event装入eventQueue队列 -
获取event的类型
获取event的event放入eventTypes
获取event的Class和event的类型(Object、String、Comparable、com.hande.common.event.Events$ClearMessageInList等类型)
放入
Map<Class<?>, List<Class<?>>> eventTypesCache
-
遍历eventTypes 得到 eventType 再遍历 subscriptions
-
subscription.subscriberMethod.method.invoke(subscription.subscriber, event)
发送事件 -
EmoticonPickerView 接收到事件
threadMode = ThreadMode.MAIN
就是主线程处理事件
-
ThirdPart
腾讯X5webview:
tbs_sdk_thirdapp_v4.3.0.3_43903_sharewithdownloadwithfile_withoutGame_obfs_20200402_121309.jar
折叠屏
如有今日头条屏幕适配方案需去除
implementation 'me.jessyan:autosize:1.1.2'
<meta-data
android:name="design_width_in_dp"
android:value="375" />
<meta-data
android:name="design_height_in_dp"
android:value="667" />
华为折叠屏适配方案:
-
在每个activity里加:
android:configChanges="screenSize|smallestScreenSize|screenLayout"
-
在 manifest 文件的 < application > 节点中增加 < meta-data > 数据:
<meta-data
android:name="android.min_aspect"
android:value="1.0" />
国际化多语言
-
获取本地国家
Locale.getDefault().getLanguage()
-
获取本地区域(如:香港 HK、台湾 TW)
Locale.getDefault().getCountry()
多渠道(马甲包)开发
- 使用gradle进行动态配置
- 通过productFlavors配置打包渠道
在app/build.gradle里: productFlavors { anzhi { applicationId "com.muitlchannelpack.anzhi.jks" manifestPlaceholders = [ UMENG_APPKEY : UM_ANZHI, UMENG_CHANNEL: ANZHI, ] } wandoujia { applicationId "com.muitlchannelpack.wandoujia" manifestPlaceholders = [ UMENG_APPKEY : UM_HUAWEI, UMENG_CHANNEL: WANDOUJIA, ] } huawei { applicationId "com.muitlchannelpack.huawei" manifestPlaceholders = [ UMENG_APPKEY : UM_WANDOUJIA, UMENG_CHANNEL: HUAWEI, ] } }
- 资源文件的替换
- 在app/src目录下面创建productFlavors对应的文件夹
- 分别在不同的渠道配置不同的AndroidManifest.xml启动项,在main下的AndroidManifest.xml可以让如公共项
- 用
BuildConfig.FLAVOR.equals("wandoujia")
可以判断当前的渠道
解决notifyDataSetChanged时导致图片闪烁
- 调用方:
recyclerView.Adapter.setHasStableIds(true);
- recyclerView.Adapter里:
@Override
public long getItemId(int position) {
return position;
}
Material Design
- 普通背景:添加水纹:
有边界
android:foreground="?android:attr/selectableItemBackground"
android:background="?android:attr/selectableItemBackground"
无边界
android:foreground="?android:attr/selectableItemBackgroundBorderless"
android:background="?android:attr/selectableItemBackgroundBorderless"
- 按钮
<Button
android:id="@+id/bt_reject"
android:layout_width="60dp"
android:layout_height="26dp"
android:layout_margin="8dp"
android:background="@drawable/shape_color_e5e8eb_radius_16"
android:text="拒绝"
android:textColor="@color/color_black_ff666666" />
- cardView
xmlns:card_view="http://schemas.android.com/apk/res-auto"
<androidx.cardview.widget.CardView
android:id="@+id/cv_save"
android:layout_width="116dp"
android:layout_height="42dp"
android:layout_gravity="center"
android:foreground="?android:attr/selectableItemBackground"
card_view:cardCornerRadius="16dp"
card_view:cardElevation="2dp"
card_view:cardBackgroundColor="@color/color_ff3e3e"
card_view:cardUseCompatPadding="true">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="保存至相册"
android:textColor="@color/color_111111"
android:textSize="12sp" />
</androidx.cardview.widget.CardView>
- 阴影 elevation
android:elevation="8dp"
或 android:translationZ="20dp"
View
- 获取view 宽高
View view = findViewById(R.id.view);
view.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
Log.e("view", view.getHeight() + "");
view.getViewTreeObserver().removeOnPreDrawListener(this);
return false;
}
});
Java反射
- 反射工具类 ReflectUtils
import android.text.TextUtils;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;
public class ReflectUtils {
/**
* 调用Class的静态方法
*/
public static <T> T invokeClassMethod(String classPath, String methodName, Class[] paramClasses, Object[] params) {
return (T) executeClassLoad(getClass(classPath), methodName, paramClasses, params);
}
/**
* 调用Class的无参静态方法
*/
public static Object invokeMethod(Object obj, String methodName, Object[] params) {
return invokeMethod(obj, methodName, null, params);
}
/**
* 通过类对象,运行指定方法
*
* @param obj 类对象
* @param methodName 方法名
* @param paramTypes 参数对应的类型(如果不指定,那么从params来判断,可能会判断不准确,例如把CharSequence 判断成String,导致反射时方法找不到)
* @param params 参数值
* @return 失败返回null
*/
public static Object invokeMethod(Object obj, String methodName, Class<?>[] paramTypes, Object[] params) {
if (obj == null || TextUtils.isEmpty(methodName)) {
return null;
}
Class<?> clazz = obj.getClass();
try {
if (paramTypes == null) {
if (params != null) {
paramTypes = new Class[params.length];
for (int i = 0; i < params.length; ++i) {
paramTypes[i] = params[i].getClass();
}
}
}
Method method = clazz.getMethod(methodName, paramTypes);
method.setAccessible(true);
return method.invoke(obj, params);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 反射获取对象属性值
*/
public static Object getFieldValue(Object obj, String fieldName) {
if (obj == null || TextUtils.isEmpty(fieldName)) {
return null;
}
Class<?> clazz = obj.getClass();
while (clazz != Object.class) {
try {
Field field = clazz.getDeclaredField(fieldName);
field.setAccessible(true);
return field.get(obj);
} catch (Exception e) {
e.printStackTrace();
}
clazz = clazz.getSuperclass();
}
return null;
}
/**
* 反射修改对象属性值
*/
public static void setFieldValue(Object obj, String fieldName, Object value) {
if (obj == null || TextUtils.isEmpty(fieldName)) {
return;
}
Class<?> clazz = obj.getClass();
while (clazz != Object.class) {
try {
Field field = clazz.getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
return;
} catch (Exception e) {
e.printStackTrace();
}
clazz = clazz.getSuperclass();
}
}
/**
* 获取Class所有的静态成员
*/
public static List<Field> getClassStaticField(Class clazz) {
Field[] fields = clazz.getFields();
List<Field> ret = new ArrayList<>();
for (Field field : fields) {
String m = Modifier.toString(field.getModifiers());
if (m.contains("static")) {
ret.add(field);
}
}
return ret;
}
/**
* 获取Class所有静态字段的值
*/
public static List<Object> getClassStaticFieldValue(Class clazz) {
Field[] fields = clazz.getFields();
List<Object> ret = new ArrayList<>();
for (Field field : fields) {
String m = Modifier.toString(field.getModifiers());
if (m.contains("static")) {
try {
ret.add(field.get(null));
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
return ret;
}
/**
* 获取某个字段的@annotation
*/
public static <A extends Annotation> A getFieldAnnotationsByType(Field field, Class<A> annotationType) {
return field.getAnnotation(annotationType);
}
/**
* 判断某个类是否包含某个方法,前提是这个类也存在
*/
public static boolean hasMethod(String classPath, String methodName, Class[] paramClasses) {
return getMethod(getClass(classPath), methodName, paramClasses) != null;
}
/**
* ****************************** basic ******************************
*/
private static Class getClass(String str) {
Class cls = null;
try {
cls = Class.forName(str);
} catch (ClassNotFoundException ignored) {
ignored.printStackTrace();
}
return cls;
}
private static Object executeClassLoad(Class cls, String str, Class[] clsArr, Object[] objArr) {
Object obj = null;
if (!(cls == null || checkObjExists(str))) {
Method method = getMethod(cls, str, clsArr);
if (method != null) {
method.setAccessible(true);
try {
obj = method.invoke(null, objArr);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
return obj;
}
private static Method getMethod(Class cls, String str, Class[] clsArr) {
Method method = null;
if (cls == null || checkObjExists(str)) {
return null;
}
try {
cls.getMethods();
cls.getDeclaredMethods();
return cls.getDeclaredMethod(str, clsArr);
} catch (Exception e) {
try {
return cls.getMethod(str, clsArr);
} catch (Exception e2) {
return cls.getSuperclass() != null ? getMethod(cls.getSuperclass(), str, clsArr) : method;
}
}
}
private static boolean checkObjExists(Object obj) {
return obj == null || obj.toString().equals("") || obj.toString().trim().equals("null");
}
}