打造一款高性能的IM+直播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 全局搜:叽喳需要打开此注释

链信公开课:

  1. idea左侧 Build Variants 切换到 lianxinClass
  2. 将打包后uni包放到 app/src/lianxinClass/assets/apps/__UNI__xxx/www(h5路径) 下
  3. 配置根目录下 gradle.properties 正式或测试环境
  4. dcloud_control设为 <hbuilder version="1.9.9.80110" debug="false" syncDebug ="false">
  5. 注释叽喳代码SDK.startWebApp 全局搜:叽喳需要打开此注释

ChainChat Class:

  1. 切换到 lianxinMP 分支

原生项目地址:

uni项目注意事项:

  1. App.vue 的 appSwitch:true
  2. request.js 的 改成
let baseUrl = process.env.NODE_ENV
  1. uni已添加jar包:
    1. uniapp-release
    2. payment-weixin-release 微信支付
    3. share-weixin-release 微信分享
    4. auth-weixin-release 微信授权
    5. media-release 视频

apk:

减小apk:全局搜:减小apk,已经减掉(由于音视频用到,已还原)

  1. faceunity已减 assets 文件

云信

点播视频
  1. 添加vcloudnosupload依赖
  2. vcloudnosupload 添加nos-android-sdk-allin-2.0.5jar包

uni SDK:

  1. audio-mp3aac-release.aar (在app)
  2. gallery-dmcBig-release.aar (在app 已废弃,只在叽喳使用)
  3. lib.5plus.base-release.aar (在app)
  4. media-release.aar (在app)
  5. msa_mdid_1.0.13.aar (或者 miit_mdid_1.0.10.aar)(在app)
  6. oauth-weixin-release.aar (在app)
  7. payment-weixin-release.aar (在app)
  8. share-weixin-release.aar (在app)
  9. 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" />

国际化多语言

  1. 获取本地国家 Locale.getDefault().getLanguage()
  2. 获取本地区域(如:香港 HK、台湾 TW) Locale.getDefault().getCountry()
  3. 适配聊天列表(全局搜:适配多语言需要用到)
    1. 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);
  1. 适配链信公开课无多语言,叽喳需多语言

    1. 中文多语言包放入 common的src\main\res\values\strings.xml
    2. 链信公开课
      1. app的src\lianxinClass\res\values\strings.xml放入区分链信公开课和叽喳的文字
    3. 叽喳
      1. app的src\gchat\res\values\strings.xml放入区分链信公开课和叽喳的文字
      2. app的src\gchat\res\values-en\strings.xml放入区叽喳英文文字
      3. app的src\gchat\res\values-ja-rJP\strings.xml放入区叽喳日文文字
      4. app的src\gchat\res\values-ko-rKR\strings.xml放入区叽喳韩文文字
      5. app的src\gchat\res\values-zh-rHK\strings.xml放入区叽喳繁体中文
      6. app的src\gchat\res\values-zh-rTW\strings.xml放入区叽喳繁体中文
  2. 获取文字:

    1. activity获取文字 this.getContext().getResources().getString(R.string.ok)
    2. xml获取文字 android:text="@string/ok"
    3. 跨module获取文字AppJoint.service(ImService.class).getString(R.string.ok)
    4. 静态类获取文字BaseApp.getContext().getResources().getString(R.string.ok)

EventBus

  1. EventBus.getDefault() 生成一个EventBus()单例,并初始化EventBus所需数据

  2. 注册,注销EventBus
    a. EventBus.getDefault().register(this) 注册EventBus
    b. EventBus.getDefault().unregister(this) 注销EventBus
    c. subscriber.getClass() 获取每个注册的Class 放入 subscriptionsByEventType

  3. EventBus.getDefault().post("hello") 调用post(Object event)方法,post方法执行:

    1. 放入队列
      ThreadLocal<PostingThreadState>创建currentPostingThreadState
      PostingThreadState postingState = currentPostingThreadState.get(); List<Object> eventQueue = postingState.eventQueue; eventQueue.add(event);把event装入eventQueue队列

    2. 获取event的类型
      获取event的event放入eventTypes
      获取event的Class和event的类型(Object、String、Comparable、com.hande.common.event.Events$ClearMessageInList等类型)放入
      Map<Class<?>, List<Class<?>>> eventTypesCache

    3. 遍历eventTypes 得到 eventType 再遍历 subscriptions

    4. subscription.subscriberMethod.method.invoke(subscription.subscriber, event) 发送事件

    5. 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" />
华为折叠屏适配方案:
  1. 在每个activity里加:
    android:configChanges="screenSize|smallestScreenSize|screenLayout"

  2. 在 manifest 文件的 < application > 节点中增加 < meta-data > 数据:

<meta-data
    android:name="android.min_aspect"
    android:value="1.0" />

国际化多语言

  1. 获取本地国家 Locale.getDefault().getLanguage()

  2. 获取本地区域(如:香港 HK、台湾 TW) Locale.getDefault().getCountry()

多渠道(马甲包)开发

前往Android多渠道打包

  1. 使用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,
            ]
        }
    }
    
  2. 资源文件的替换
    • 在app/src目录下面创建productFlavors对应的文件夹
    • 分别在不同的渠道配置不同的AndroidManifest.xml启动项,在main下的AndroidManifest.xml可以让如公共项
    • BuildConfig.FLAVOR.equals("wandoujia")可以判断当前的渠道

解决notifyDataSetChanged时导致图片闪烁

  1. 调用方:
recyclerView.Adapter.setHasStableIds(true);
  1. recyclerView.Adapter里:
    @Override
    public long getItemId(int position) {
        return position;
    }

Material Design

  1. 普通背景:添加水纹:
    有边界
        android:foreground="?android:attr/selectableItemBackground"
        android:background="?android:attr/selectableItemBackground"

无边界

        android:foreground="?android:attr/selectableItemBackgroundBorderless"
        android:background="?android:attr/selectableItemBackgroundBorderless"
  1. 按钮
      <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" />
  1. 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>
  1. 阴影 elevation
        android:elevation="8dp"
        或  android:translationZ="20dp"

View

  1. 获取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反射

  1. 反射工具类 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");
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值