APP

APP

融云 IM SDK 集成 —- 刷新会话界面和会话列表界面

IM即时通讯大兴 发表了文章 • 0 个评论 • 414 次浏览 • 2020-12-02 16:28 • 来自相关话题

最近集成融云 IMkit 发现, 融云 IMkit 提供的 ConversationListFragment 和 ConversationFragment 都没有提供刷新方法.我们有个需求是, 使用 Message 的 extra 修改值后, 但是界面没有进行... ...查看全部

最近集成融云 IMkit 发现, 融云 IMkit 提供的 ConversationListFragment 和 ConversationFragment 都没有提供刷新方法.

微信截图_20201202162201.png

我们有个需求是, 使用 Message 的 extra 修改值后, 但是界面没有进行刷新, 需要自己调用代码进行刷新, 但是, 融云没有提供对应的刷新界面的方法, 所以只能看代码.

刷新 ListView 是需要获取 Adapter , 然后使用 Adapter 进行刷新, 但是我们无法从 ConversationListFragment 中直接获取, 那怎么办呢?

我们知道, ListView 中有 getAdapter 的方法. 那我们能不能先获取 ListView 控件呢? 答案是可以的. 我们通过 findById 方法即可获取 ListView. 通过查找, 我们可以知道, 融云会话列表的 listView 的 id 为 R.id.rc_list.

所以代码如下.

ListView mList = (ListView)findViewById(R.id.rc_list);
HeaderViewListAdapter adapter = (HeaderViewListAdapter)mList.getAdapter();
ConversationListAdapter wrappedAdapter = (ConversationListAdapter)adapter.getWrappedAdapter();
if (adapter != null) {
    wrappedAdapter.notifyDataSetChanged();
}


Android 端如何添加自定义表情

IM即时通讯大兴 发表了文章 • 0 个评论 • 372 次浏览 • 2020-12-02 16:28 • 来自相关话题

实现步骤1.新建 RongEmoticonTab 类继承 IEmoticonTab 。public class RongEmoticonTab implements IEmoticonTab {  &... ...查看全部

微信截图_20201202162412.png

实现步骤

1.新建 RongEmoticonTab 类继承 IEmoticonTab 。

public class RongEmoticonTab implements IEmoticonTab {
    public RongEmoticonTab() {
    }
    @Override
    public Drawable obtainTabDrawable(final Context context) {
        return context.getResources().getDrawable(R.drawable.u1f603);
    }
    @Override
    public View obtainTabPager(Context context) {
        return view;
    }
    @Override
    public void onTableSelected(int i) {
    }
}

2.在 obtainTabPager 中添加您想要展示在表情面板上的 view 。

 @Override
    public View obtainTabPager(Context context) {
        View view = LayoutInflater.from(context).inflate(R.layout.view_emoji, null);
        RecyclerView rv = view.findViewById(R.id.recycler_view);
        //LinearLayoutManager是用来做列表布局,也就是单列的列表
        GridLayoutManager mLayoutManager = new GridLayoutManager(context, 5, OrientationHelper.VERTICAL, false);
        rv.setLayoutManager(mLayoutManager);
        //谷歌提供了一个默认的item删除添加的动画
        rv.setItemAnimator(new DefaultItemAnimator());
        rv.setHasFixedSize(true);
        //模拟列表数据
        ArrayList newsList = new ArrayList<>();
        TypedArray array = context.getResources().obtainTypedArray(context.getResources().getIdentifier("rc_emoji_res", "array", context.getPackageName()));
        int i = -1;
        while (++i < array.length()) {
            newsList.add(array.getResourceId(i, -1));
        }
        rv.setAdapter(new NewsAdapter(newsList));
        return view;
    }

3.Adapter 和布局文件可以参考GitHub
4.自定义一个 ExtensionModule 继承自 DefaultExtensionModule,复写其中的 getEmoticonTabs() 方法,返回需要展示的 EmoticonTab 列表。

public class MyExtensionModule extends DefaultExtensionModule {
    private RongEmoticonTab rongEmoticon;
    @Override
    public List<IEmoticonTab> getEmoticonTabs() {
        List<IEmoticonTab> emoticonTabs =  super.getEmoticonTabs();
        RongEmoticonTab emojiTab=new RongEmoticonTab();
        emoticonTabs.add(myEmoticon);
        return emoticonTabs;
    }
}

5.在初始化之后,取消 SDK 默认的 ExtensionModule,注册自定义的 ExtensionModule, 如下:

public void setMyExtensionModule() {
    List<IExtensionModule> moduleList = RongExtensionManager.getInstance().getExtensionModules();
    IExtensionModule defaultModule = null;
    if (moduleList != null) {
        for (IExtensionModule module : moduleList) {
            if (module instanceof DefaultExtensionModule) {
                defaultModule = module;
                break;
            }
        }
        if (defaultModule != null) {
            RongExtensionManager.getInstance().unregisterExtensionModule(defaultModule);
            RongExtensionManager.getInstance().registerExtensionModule(new MyExtensionModule());
        }
    }
}

6.如果需要网络下载表情需要下载并持久化表情数据,需要在添加 EmoticonTab 前下载好表情数据。

融云即时通讯SDK集成 — 通知检查

IM即时通讯大兴 发表了文章 • 2 个评论 • 444 次浏览 • 2020-12-02 16:28 • 来自相关话题

背景:最近公司新上的app要加上即时通讯的功能, 自己快速实现一个当然是不可能的了(项目deadline也顶不住哇).就从各家成熟的SDK厂商选来选去的, 各有各的好也各有各的不足.最后点兵点将,选了融云家的SDK(老板说了算hhhh).他家的官网和文档地址:... ...查看全部

微信截图_20201202162632.png背景:

最近公司新上的app要加上即时通讯的功能, 自己快速实现一个当然是不可能的了(项目deadline也顶不住哇).就从各家成熟的SDK厂商选来选去的, 各有各的好也各有各的不足.最后点兵点将,选了融云家的SDK(老板说了算hhhh).
他家的官网和文档地址:
官网:https://www.rongcloud.cn/
文档:https://docs.rongcloud.cn/v4
这个任务当然还是落在我的头上. 集成完毕后, 也踩了不少坑. 所以这篇文章给大家总结下排查融云消息的本地通知和远端推送的办法. 希望可以帮助到正在看这篇文章的你.

什么是本地通知:

当我的App接入了融云的即时通讯sdk后, 便拥有了即时通讯的能力. sdk与融云服务器建立长连接, 当消息发出后, 先走到融云的服务器, 再转发给相应的用户. 这里移动端到服务端, 服务端到移动端, 走的通道都是长连接. 无论你的app是在前台还是在后台, 只要没有被杀死, 那么长连接是一直在的. 所以消息可以即时的发送到达给接收者. 融云把这种走长连接到达的消息, 在通知栏展示的通知叫做本地通知. 也就是消息是顺利发送到接收者端了, 逻辑可以走到消息接收监听那里. 融云sdk内部实现了消息到达后的本地通知, 也赋予了开发者自行实现消息到达后进行本地通知的权利.

本地通知的检查:

这里我总结了一下接入融云sdk后, 关于本地通知接收不顺利的排查. 大致可以分为这么几条:

1.是否设置了 setOnReceiveMessageListener 监听, 并且 onReceived 方法返回的为 true。

 RongIM.setOnReceiveMessageListener(new RongIMClient.OnReceiveMessageListener() {
              @Override
              public boolean onReceived(Message message, 
                  return true;
              }
          });

如果 onReceived 方法返回值为 true 则是监听做了拦截, 则不会走通知逻辑。 2.发送的消息是否是自定义消息。 如果是自定义消息, 则请查看自定义消息的 MessageTag 的注解是否设置了 flag 的值为 MessageTag.ISCOUNTED 或 MessageTag.ISPERSISTED) 。 以下面为例。

@MessageTag(value = "RC:TxtMsg", flag = MessageTag.ISCOUNTED)
  @DestructionTag
  public class TextMessage extends MessageContent {
      ...
  }

如果没有设置其中之一,则不会走通知逻辑。

3.是否设置消息拦截器

设置代码如下:

/**
   * 设置接收消息时的拦截器
   *
   * @param messageInterceptor 拦截器
   */
  public void setMessageInterceptor(new RongIM.getInstance().MessageInterceptor() {
          @Override
          public boolean intercept(final Message message) {
                 return true;
          }
 });

如果设置了消息拦截器, 并且在 intercept() 方法中返回 true, 表示拦截此消息。 则不会走通知逻辑。

4.检查接收到的消息的 senderUserId 是否和当前用户的 id 相同。
如果 id 相同, 则不会走通知逻辑。 自己不可发给自己。

5.查看当前接收的消息是否是属于聊天室会话类型的
聊天室的消息目前不支持本地通知。

6.如果是自定义消息,请检查是否设置了自定义消息的 MessageProvider 。
如果没有,则不会走通知逻辑。

7.接收方本地是否有接收方的用户信息。 如果没有设置, 则不会走通知逻辑。

8.检查应用权限设置,看是否打开应用信任权限, 通知栏权限或者声音提示权限等。如果没有打开,请打开再试。

9.接收方没有发送方的用户信息,接收端不弹通知。用户信息通过用户信息提供者方式获取。
参考链接:https://docs.rongcloud.cn/v3/views/im/ui/guide/private/user/set/android.html

什么是远端推送:

集成了即时通讯的SDK, 我们的app不就能拥有像微信一样随时随地收到消息的即时通讯能力了? 说实话我一开始也是这么认为的. 可惜做开发也要按基本法来, Android平台回收app的这一关咱都过不了, app都给你杀死得透透的了你拿啥收消息呢? 咱又不是微信hhhh. 所以一番急赤白脸地阅读他家文档之后, 才发现app如果活着, 他融云能用自己的通道给你把消息推送到. 如果app被杀死了, 这个消息就在他家的服务端直接交给三方厂商了(也就是五大厂商蓝绿大厂华为小米FCM), 让这条消息走人家厂商的推送通道给送到你手机上.

远端推送的检查:

这里我总结了一下接入融云sdk后, 关于本地通知接收不顺利的排查. 大致可以分为这么几条:

  1. 退出应用的时候,只能调用融云的 disconnect() 方法,而不是 logout()。这样退出后融云才会启动push进程。

  2. push 进程的名字不能更改,必须是默认的名字,既 io.rong.push.
    (2.6.0dev之后的版本,此进程名字可以修改)

  3. 通过 ddms 或者终端里敲入 adb shell ps|grep rong 查看终端里是否存在 io.rong.push 这个进程。

有些手机厂家做了特殊限制,不允许第三方后台进程启动,所以融云的后台进程起不来,导致收不到 push 消息。这种情况可以换个手机测试(如三星,大部分三星手机没有做权限限制)。另外大部分国产手机,有权限设置的菜单,比如小米,华为等,可以手动去安全中心,设置应用的自启动权限,后台运行权限等,就可以收到Push消息了。

不过 vivo 和 oppo 有些型号的手机,一旦应用退到后台,系统会很快把它杀死,这种没有办法解决。目前市面上所有推送都存在这个问题,除非系统把该应用加入白名单。

  1. push 进程存在,仍然收不到 push 消息。

如果你的应用有消息免打扰功能,那么请确认当前登录账号之前是否设置过消息免打扰。如果不太确定,那最好去你的应用设定里重新设置下消息免打扰时间。 这里要注意的是: 如果这个账号之前在别的手机上设置过消息免打扰,换一台手机登录或者卸载重装的时候,融云服务端记录的仍然是之前设置的消息免打扰状态,所以这种情况下是收不到push消息的。


解决融云 SDK 4.0 版本配置 https 导航报 SSLHandshakeException

IM即时通讯大兴 发表了文章 • 0 个评论 • 375 次浏览 • 2020-12-02 16:28 • 来自相关话题

我们公司最近使用融云 IM 进行集成开发. 我们是私有云部署, 导航是通过接口进行设置的. 是 Https 的.之前我们是使用的2.x 版本, 使用下面接口配置一下即可.    RongIMClient.getInsta... ...查看全部

微信截图_20201202162800.png

我们公司最近使用融云 IM 进行集成开发. 我们是私有云部署, 导航是通过接口进行设置的. 是 Https 的.

之前我们是使用的2.x 版本, 使用下面接口配置一下即可.

    RongIMClient.getInstance().enableHttpsSelfCertificate(false);

但是最近我们升级融云到 4.0 版本. 突然发现连接不上了. 日志中报

javax.net.ssl.SSLHandshakeException: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.

这个错误.

于是咨询融云技术支持, 得知 4.0 需要自己进行证书验证.

需在 application 中添加即可.

 try {
    //使用X509TrustManager代替CertificateTrustManager跳过证书验证。
    TrustManager tm[] =  new X509TrustManager() {
                @Override
                public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
                }
                @Override
                public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
                }
                @Override
                public X509Certificate[] getAcceptedIssuers() {
                    return new X509Certificate[0];
                }
            }
        };
        SSLContext context = SSLContext.getInstance("TLS");   
        context.init(null, tm, null);
        SSLUtils.setSSLContext(context);
        SSLUtils.setHostnameVerifier(new HostnameVerifier() {
            @Override
            public boolean verify(String hostname, SSLSession session) {
                return true;
             }
            });
} catch (Throwable e) {
    throw new IllegalStateException(e);
}

注意的是, 由于导航请求是在融云的 ipc 进程中的, 所以在 application 中设置上面代码的时候, 千万不要设置在主进程中. 否则无效.

在融云 IMkit 会话界面基础上添加消息已读未读.

WebRTC大兴 发表了文章 • 0 个评论 • 442 次浏览 • 2020-11-06 15:37 • 来自相关话题

使用过融云的同学们可能知道. 融云 IMkit 的会话界面,  发送玩消息后, 如果对方已读, 发送端则会显示小对号的图片. 但是更具需求要把小对号改为已读未读. 接下来我们就一块实现这个功能.打开回执功能首先, 要确定打开融云的消息回执功能. 这个... ...查看全部

使用过融云的同学们可能知道. 融云 IMkit 的会话界面,  发送玩消息后, 如果对方已读, 发送端则会显示小对号的图片. 但是更具需求要把小对号改为已读未读. 接下来我们就一块实现这个功能.

打开回执功能

首先, 要确定打开融云的消息回执功能. 这个很简单, 就是在 rc_config.xml 中把下面属性配置为 true 即可.

<!-- 设置是否开启消息回执, true 为打开, false 为关闭--><bool name="rc_read_receipt">true</bool>

这样发送消息后, 对方已读, 发送端就会出现融云默认的小对号了.

自定义 Adapter

出现小对号后, 下一步就是要进行对融云适配器的改造了.

第一步
创建 CustomMessageListAdapter 继承 MessageListAdapter. 根据自己的需求复写 newView() 或者 bindView() 方法.

class CustomMessageListAdapter extends MessageListAdapter {    @Override    protected void bindView(View v, final int position, final UIMessage data) {        // 此方法中操作控件    }}

第二步

创建 CustomConversationFragment 继承于 ConversationFragment, 并复写父类中的 onResolveAdapter(), 返回 CustomMessageListAdapter 对象.

 class CustomMessageListAdapter extends ConversationFragment {        @Override        public MessageListAdapter onResolveAdapter() {            return new CustomMessageListAdapter();        }    }

第三步

使用 CustomConversationListFragment 代替 ConversationListFragment 进行配置使用即可.

u=2322698630,4022941591&fm=26&gp=0.jpg

添加已读未读

自定了了 Adapter, 我们就可以在原先逻辑的基础上进行扩展了. 现在主要的任务就是找到小对号的控件.

我们先看一下默认的 item 布局 rc_item_message.xml. 在布局中我们可找到下面的控件

    <TextView        android:id="@id/rc_read_receipt"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:layout_gravity="bottom"        android:layout_marginRight="4dp"        android:drawableLeft="@drawable/rc_read_receipt"        android:drawableStart="@drawable/rc_read_receipt"        android:textColor="@color/rc_read_receipt_status"        android:textSize="12sp"        android:visibility="gone" />

这个就是显示对号的控件了.  是一个 TextView 的控件, 由于我们是添加 “已读” 、“未读” 字符串, 正好可以直接使用这个 TextView 控件.

我们通过控件的 id 在 Adapter 中找到控件的名称.

class CustomMessageListAdapter extends MessageListAdapter {    @Override    protected void bindView(View v, final int position, final UIMessage data) {         super(v, position, data);       // 此方法中操作控件       final ViewHolder holder = (ViewHolder) v.getTag();       if (data.getMessageDirection() == Message.MessageDirection.SEND) {            if (readRec && data.getSentStatus() == Message.SentStatus.READ) {                if (data.getConversationType().equals(Conversation.ConversationType.PRIVATE) && tag.showReadState()) {                    holder.readReceipt.setVisibility(View.VISIBLE);                    holder.readReceipt.setText(已读);                    holder.readReceipt.setBackground(null);                } else {                      holder.readReceipt.setText(未读);                      holder.readReceipt.setBackground(null);                      holder.readReceipt.setVisibility(View.VISIBLE);            }        }    }}

这样就会显示就会显示已读未读的字样了.


融云集成之避坑指南-Android推送篇

WebRTC大兴 发表了文章 • 0 个评论 • 451 次浏览 • 2020-11-06 15:37 • 来自相关话题

在集成推送时候,需要先了解融云针对推送的定义: Push 通知是当接收方的主进程被杀死或者回收,或者您主动调用 disconnect() 的操作, 导致 IM 长连接通道与服务器断开后。 在这种情况下,会收到Push 服务;说白了,就是应用进程被杀掉,才会收到... ...查看全部

在集成推送时候,需要先了解融云针对推送的定义: Push 通知是当接收方的主进程被杀死或者回收,或者您主动调用 disconnect() 的操作, 导致 IM 长连接通道与服务器断开后。 在这种情况下,会收到Push 服务;

说白了,就是应用进程被杀掉,才会收到push 的,否则的话,是会走融云内部的长链接通道的;若是连应用进程都不了解的话,我要拿起我四十米的大刀了。
avatar

好了,前提介绍结束,接下来,我们正式开始避坑。

融云的推送分为俩大类:RongPush 以及其他厂商推送;至于为什么会有厂商推送 ,主要书由于众说周知的原因,各个手机厂商针对系统的room 太深,导致融云的push 进程服务无法自启动以及无法常存与服务后台,所以为了满足离线时候,push 的到达率,所以接入了主流的厂商推送服务,至于你说,为什么不接入统一推送联盟,估计等推送联盟孵化出来,可能得等到2045年了;哈哈,估计我再不开始介绍接入,大家就要发飙了。
avatar

各位客官,稍安勿躁,马上开始。本篇我们先介绍厂商推送之华为推送过程中遇到的坑;

首先,按照融云官网的描述,融云SDK 目前是有俩个版本,2.x 版本的SDK 以及4.x 版本的SDK ,分别对应的是华为的2.x 的jar 以及4.x 的jar ,可以分别参考 2.x版本 以及4.x版本,按照文档进行集成即可,具体集成过程按照文档集成即可,此处无需多说;

接下来,先介绍我在集成2.x 版本时候遇到的问题:

1.出现的问题一,没有填写对应的应用标识;(此处是我的锅,没有细致的看文档);

按照融云文档的描述,
avatar 此处要填写对应的华为的appkey 等数据的,而愚蠢的我竟然没有填写对应的 AppIDAppSecret,希望各位 大大在集成过程中不要跟我一样犯傻;

2.出现的问题二,按照文档配置完成了,结果,还是收不到推送,不会进行排查;

似乎这个问题,很多小伙伴都遇到了,所以,我在融云知识库中找到了一篇官方提供的排查手段,仅供大家参考 https://support.rongcloud.cn/ks/ODg0 ,若是大家还是搞不定,可以直接找融云进行技术支持的;

以上是在集成2.x 版本时候遇到的问题,不算特别困难,也是比较轻松的解决了;

但是,事情万万没有想到的那么简单,时间过去一个月,项目要升级4.x 的融云SDK ,于是,华为推送也要进行对应的升级,于是我开开心心准备认真参谋一下4.x文档,还好,文档比较简单;

于是,对应的问题,也出现了:

问题1:在升级过程中,并没有找见 agconnect-servics.json  文件的下载地址。

经过与融云技术人员的沟通,在华为提供5.0版本的推送服务之后,agconnect-servics.json  下载位置改变了,所以我将我知道的下载路径提供给大家,在华为开发者平台-我的项目中-对应的项目下载即可。

问题2: 由于我是从2.x 升级到4.x 的,所以,眼瞎的我没有看清楚文档中AndroidMainfest 中的配置是要删除的,所以导致找不到文件了,所以请大家一定要看清楚标题,是删除配置,不是添加配置;

问题3: 在EMUI 10以上 是可以收到推送的,但是在EMUI 10以下,收不到推送。

这个问题经过与融云技术人员以及华为技术人员的沟通可以确定,由于华为不再继续维护2.0推送服务,改推4.0 以上的服务,但是4.0的推送服务,又对HMSCore 是由要求的,所以只能升级用户的 HMSCore服务,不过融云对外提供了升级监听,具体可以参考融云文档


iOS 性能优化:优化 App 启动速度

IM即时通讯大神庵 发表了文章 • 0 个评论 • 152 次浏览 • 2020-10-26 12:00 • 来自相关话题

作者:Damonwong,iOS 开发者来源公众号丨老司机技术周报(ID:LSJCoding)Sessions: https://developer.apple.com/videos/play/wwdc2019/423/苹果是一家特别注重用户体验的公司,过去几... ...查看全部

作者:Damonwong,iOS 开发者

来源公众号丨老司机技术周报(ID:LSJCoding)

Sessions: https://developer.apple.com/videos/play/wwdc2019/423/

苹果是一家特别注重用户体验的公司,过去几年一直在优化 App 的启动时间,特别是去年的 WWDC 2019 keynote[1] 上提到,在过去一年苹果开发团队对启动时间提升了 200%

虽然说是提升了 200%,但是有些问题还是没有说清楚,比如:

  • 为什么优化了这么多时间?

  • 作为开发者的我们,我们还可以做哪些针对启动速度的优化?

所以我们今天结合 WWDC2019 - 423 - Optimizing App Launch[2] 聊一下和启动相关的东西

名词解释

先介绍一些和启动相关的名词。

Mach-O

Mach-O 是 iOS 系统不同运行时期可执行的文件的文件类型统称。主要分以下三类:

  • Executable - 可执行文件,是 App 中的主要二进制文件

  • Dylib - 动态库,在其他平台也叫 DSO 或者 DLL

  • Bundle - 苹果平台特有的类型,是无法被连接的 Dylib。只能在运行时通过 dlopen() 加载

Mach-O 的基本结构如下图所示,分为三个部分:

  • Header 包含了 Mach-O 文件的基本信息,如 CPU 架构,文件类型,加载指令数量等

  • Load Commands 是跟在 Header 后面的加载命令区,包含文件的组织架构和在虚拟内存中的布局方式,在调用的时候知道如何设置和加载二进制数据

  • Data 包含 Load Commands 中需要的各个 Segment 的数据。

绝大多数 Mach-O 文件包括以下三种 Segment

  • __TEXT - 代码段,包括头文件、代码和常量。只读不可修改。

  • __DATA - 数据段,包括全局变量, 静态变量等。可读可写。

  • __LINKEDIT - 如何加载程序, 包含了方法和变量的元数据(位置,偏移量),以及代码签名等信息。只读不可修改。

Image

指的是 Executable,Dylib 或者 Bundle 的一种。

Framework

有很多东西都叫做 Framework,但在本文中,Framework 指的是一个 dylib,它周围有一个特殊的目录结构来保存该 dylib 所需的文件。

虚拟内存(Virtual Memory)

虚拟内存是建立在物理内存和进程之间的中间层。是一个连续的逻辑地址空间,而且逻辑地址可以没有对应的实际物理内存地址,也可以让多个逻辑地址对应到一个物理内存地址上。

Page Fault

当进程访问一个没有对应物理地址的逻辑地址时,会发生 Page Fault

Lazy Reading

某个想要读取的页没有在内存中就会触发 Page Fault,系统通过调用 mmap() 函数读取指定页,这个过程叫做 Lazy Reading

COW(Copy-On-Write)

当进程需要对某一页内容进行修改时,内核会把需要修改的部分先复制一份,然后再修改,并把逻辑地址重新映射到新的物理内存去。这个过程叫做 Copy-On-Write

Dirty Page & Clean Page

Image 加载后,被修改过内容的 Page 叫做 Dirty Page,会包含着进程特定的信息。与之相对的叫 Clean Page,可以从磁盘重新生成。

共享内存(Share RAM)

当多个 Mach-O 都依赖同一个 Dylib(eg. UIKit)时,系统会让这几个 Mach-O 的调用 Dylib 的逻辑地址都指向同一块物理内存区域,从而实现内存共享。Dirty Page 为进程独有,不能被共享。

地址空间布局随机化(ASLR)

当 Image 加载到逻辑地址空间的时候,系统会利用 ASLR 技术,使得 Image 的起始地址总是随机的,以避免黑客通过起始地址+偏移量找到函数的地址

当系统利用 ASLR 分配了随机地址后,从 0 到该地址的整个区间会被标记为不可访问,意味着不可读,不可写,不可被执行。这个区域就是 __PAGEZERO 段,它的大小在 32 位系统是 4KB+,而在 64 位系统是 4GB+

代码签名(Code Sign)

代码签名可以让 iOS 系统确保要被加载的 Image 的安全性,用 Code Sign 设置签名时,每页内容都会生成一个单独的加密散列值,并存储到 __LINKEDIT 中去,系统在加载时会校验每页内容确保没有被篡改。

dyld(dynamic loader)

dyld 是 iOS 上的二进制加载器,用于加载 Image。有不少人认为 dyld 只负责加载应用依赖的所有动态链接库,这个理解是错误的。dyld 工作的具体流程如下:参考:dyld启动流程[3]

Load dylibs

dyld 在加载 Mach-O 之前会先解析 Header 和 Load Commands, 然后就知道了这个 Mach-O 所依赖的 dylibs,以此类推,通过递归的方式把全部需要的 dylib 都加载进来。

一般来说,一个 App 所依赖的 dylib 在 100 - 400 左右,其中大多数都是系统的 dylib,因为有缓存和共享的缘故,读取速度比较高。

Fix-ups

因为 ASLR 和 Code Sign 的原因,刚被加载进来的 dylib 都处于相对独立的状态,为了把它们绑定起来,需要经过一个 Fix-ups 过程。Fix-ups 主要有两种类型:Rebase 和 Bind。

PIC(Position Independent Code)

因为代码签名的原因,dyld 无法直接修改指令,但是为了实现在运行时可以 Fix-ups,在 code gen 时,通过动态 PIC(Position Independent Code)技术,使本来因为代码签名限制不能再修改的代码,可以被加载到间接地址上。当要调用一个方法时,会先在 __DATA 段中建立一个指针指向这个方法,再通过这个指针实现间接调用。

Rebase

Rebase 就是针对“因为 ASLR 导致 Mach-O 在加载到内存中是一个随机的首地址”这一个问题做一个数据修正的过程。会将内部指针地址都加上一个偏移量,偏移量的计算方法如下:

  Slide = actual_address - preferred_address

所有需要 Rebase 的指针信息已经被编码到 __LINKEDIT 里。然后就是不断重复地对 __DATA 中需要 Rebase 的指针加上这个偏移量。这个过程中可能会不断发生 Page Fault 和 COW,从而导致 I/0 的性能损耗问题,不过因为 Rebase 处理的是连续地址,所以内核会预先读取数据,减少 I/O 的消耗。

Binding

Binding 就是对调用的外部符号进行绑定的过程。比如我们要使用到 UITableView,即符号 _OBJC_CLASS_$_UITableView, 但这个符号又不在 Mach-O 中,需要从 UIKit.framework 中获取,因此需要通过 Binding 把这个对应关系绑定到一起。

在运行时,dyld 需要找到符号名对应的实现。而这需要很多计算,包括去符号表里找。找到后就会将对应的值记录到 __DATA 的那个指针里。Binding 的计算量虽然比 Rebasing 更多,但实际需要的 I/O 操作很少,因为之前 Rebasing 已经做过了。

dyld2 & dyld3

 

在 iOS 13 之前,所有的第三方 App 都是通过 dyld 2 来启动 App 的,主要过程如下:

  • 解析 Mach-O 的 Header 和 Load Commands,找到其依赖的库,并递归找到所有依赖的库

  • 加载 Mach-O 文件

  • 进行符号查找

  • 绑定和变基

  • 运行初始化程序

上面的所有过程都发生在 App 启动时,包含了大量的计算和I/O,所以苹果开发团队为了加快启动速度,在 WWDC2017 - 413 - App Startup Time: Past, Present, and Future[4] 上正式提出了 dyld3。

dyld3 被分为了三个组件:

  • 一个进程外的 MachO 解析器


    • 预先处理了所有可能影响启动速度的 search path、@rpaths 和环境变量

    • 然后分析 Mach-O 的 Header 和依赖,并完成了所有符号查找的工作

    • 最后将这些结果创建成了一个启动闭包

    • 这是一个普通的 daemon 进程,可以使用通常的测试架构

  • 一个进程内的引擎,用来运行启动闭包


    • 这部分在进程中处理

    • 验证启动闭包的安全性,然后映射到 dylib 之中,再跳转到 main 函数

    • 不需要解析 Mach-O 的 Header 和依赖,也不需要符号查找。

  • 一个启动闭包缓存服务


    • 系统 App 的启动闭包被构建在一个 Shared Cache 中, 我们甚至不需要打开一个单独的文件

    • 对于第三方的 App,我们会在 App 安装或者升级的时候构建这个启动闭包。

    • 在 iOS、tvOS、watchOS中,这这一切都是 App 启动之前完成的。在 macOS 上,由于有 Side Load App,进程内引擎会在首次启动的时候启动一个 daemon 进程,之后就可以使用启动闭包启动了。

dyld 3 把很多耗时的查找、计算和 I/O 的事前都预先处理好了,这使得启动速度有了很大的提升。

App 启动

介绍完上面这一大堆名词之后,我们正式进入主题。

App 启动为什么这么重要?

  • App 启动是和用户的第一个交互过程,所以要尽量缩短这个过程的时间,给用户一个良好的第一印象

  • 启动代表了你的代码的整体性能,如果启动的性能不好,其他部分的性能可能也不会太好

  • 启动会占用 CPU 和内存,从而影响系统性能和电池

所以我们要好好优化启动时间。

启动类型

App 的启动类型分为三类

  • Cold Launch 也就是冷启动,冷启动需要满足以下几个条件:


    • 重启之后

    • App 不在内存中

    • 没有相关的进程存在

  • Warm Launch 也就是热启动,热启动需要满足以下几个条件:


    • App 刚被终止

    • App 还没完全从内存中移除

    • 没有相关的进程存在

  • Resume Launch 指的是被挂起的 App 继续的过程,需要满足以下几个条件:


    • App 被挂起

    • App 还全部都在内存中

    • 还存在相关的进程

App 启动阶段

App 启动分为三个阶段

  • 初始化 App 的准备工作

  • 绘制第一帧 App 的准备工作及绘制(这里的第一帧并不是获取到数据之后的第一帧,可以是一张占位视图),这时候用户与App已经可以交互了,比如 tabbar 切换

  • 获取到页面的所有数据之后的完整的绘制第一帧页面

在这个地方,苹果再次强调了一下,建议「用户从点击 App 图标到可以再次交互,也就是第二阶段结束」的时间最好在 400ms 以内。目前来看,大部分 App 都没有达到这个目标。

下面,我们把上面三个阶段分成下面这 6 个部分,讲一下这几个阶段做了什么以及有什么可以优化的地方。

 

System Interface

初始化 App 的准备工作,系统主要做了两个事情:Load dylibs 和 libSystem init

在 2017 年苹果介绍过 dyld3 给系统 App 带来了多少优化,今年 dyld3 正式开发给开发者使用,这意味着 iOS 系统会将你热启动的运行时依赖给缓存起来。以达到减少启动时间的目的。这也就是提升 200% 的原因之一。

视频中只说优化了热启动时间,理论上对于 iOS 系统来说 dyld3 应该还可以优化冷启动时间,所以不知道是因为给 iPad 增加了多任务功能的原因,还是没有把所有功能开放的原因,作者只提了热启动这个原因暂时还不太清楚。

除此之外,在 Load dylibs 阶段,开发者还可以做以下优化:

  • 避免链接无用的 frameworks,在 Xcode 中检查一下项目中的「Linked Frameworks and Librares」部分是否有无用的链接。

  • 避免在启动时加载动态库,将项目的 Pods 以静态编译的方式打包,尤其是 Swift 项目,这地方的时间损耗是很大的。

  • 硬链接你的依赖项,这里做了缓存优化。

也许有人会困惑是不是使用了 dyld3 了,我们就不需要做 Static Link 了,其实还是需要的,感兴趣的可以看一下 Static linking vs dyld3[5] 这篇文章,里面有一个详细的数据对比。

libSystem init 部分,主要是加载一些优先级比较低的系统组件,这部分时间是一个固定的成本,所以我们开发人员不需要关心。

Static Runtime Initializaiton

这个阶段主要是 Objective-C 和 Swift Runtime 的初始化,会调用所有的 +load 方法,将类的信息注册到 runtime 中。

在这个阶段,原则上不建议开发者做任何事情,所以为了避免一些启动时间的损耗,你可以做以下几个事情:

  • 在 framework 开发时,公开专有的初始化 API

  • 减少在 +load 中做的事情

  • 使用 initialize 进行懒加载初始化工作

UIKit Initializaiton

这个阶段主要做了两个事情:

  • 实例化 UIApplication 和 UIApplicationDelegate

  • 开始事件处理和系统集成

所以这个阶段的优化也比较简单,你需要做两个事情:

  • 最大限度的减少 UIApplication 子类初始化时候的工作,更甚至与不子类化 UIApplication

  • 减少 UIApplicationDelegate 的初始化工作

Application Initializaiton

这个阶段主要是生命周期方法的回调,也正是开发者最熟悉的部分。

调用 UIApplicationDelegate 的 App 生命周期方法:

  application:willFinishLaunchingWithOptions: 
  application:didFinishLaunchingWithOptions:

和 UIApplicationDelegate 的 UI 生命周期方法:

  applicationDidBecomeActive:

同时,iOS 13 针对 UISceneDelegate 增加了新的回调:

  scene:willConnectToSession:options:
  sceneWillEnterForeground:
  sceneDidBecomeActive:

也会在这个阶段调用。感兴趣的可以关注一下 Getting the Most out of Multitasking 这个 Session,暂时没有视频资源,怀疑是现场演示翻车了,所以没有把视频资源放出来。

在这个阶段,开发者可以做的优化:

  • 推迟和启动时无关的工作

  • Senens 之间共享资源

Fisrt Frame Render

这个阶段主要做了创建、布局和绘制视图的工作,并把准备好的第一帧提交给渲染层渲染。会频繁调用以下几个函数:

 loadView
 viewDidLoad 
 layoutSubviews

在这个阶段,开发者可以做的优化:

  • 减少视图层级,懒加载一些不需要的视图

  • 优化布局,减少约束

更多细节可以从 WWDC2018 - 220 - High Performance Auto Layout[6] 中了解

Extend

大部分 App 都会通过异步的方式获取数据,并最终呈现给用户。我们把这一部分称为 Extend。

因为这一部分每个 App 的表现都不一样,所以苹果建议开发者使用 os_signpost 进行测量然后慢慢分析慢慢优化。

测量 App 启动时间

要找到启动过程中的问题,就要进行多次测量并前后比较。但是如果变量没有控制好,就会导致误差。

所以为了保证测量的数据能够真实的反应问题,我们要减少不稳定性因素,保证在可控的相近的环境下进行测量。最后使用一致的结果来分析。

条件一致性

为了保证环境一致,我们可以做下面这几个事情:

  • 重启手机,并等待 2-3 分钟

  • 启用飞行模式或者使用模拟网络

  • 不使用或者不变更 iCloud 的账户

  • 使用 release 模式进行 build

  • 测量热启动时间

iColud 账户切换会影响性能,所以不要切换账号或者不开启 iCloud。

测量注意点

  • 尽可能的使用具有代表性的数据进行测试

    如果不使用具有代表性的数据进行测试,就会出现偏差

  • 使用不同的新旧设备进行测试

  • 最后你还可以使用 XCTest 来测试,多运行几次,取平均结果

关于使用 XCTest 测试启动时间的信息,可以看一下 WWDC2019 - 417 - Improving Battery Life and Performance[7],但是我测试了一下,目前好像还有一部分 API 还没有开放出来,暂时还不能使用。

使用 Instruments 分析和优化 App 启动过程

优化方式

苹果给了我们三个优化方式的建议,整体思想和前面提到的各个阶段的优化差不多

Minimize Work

  • 推迟与第一帧无关的工作

  • 从主线程移开阻塞工作

  • 减少内存使用量

Prioritize Work

  • 定义好任务的优先级。

  • 利用好 GCD 来优化你的启动速度。

  • 让重要的事情保持优先

更深入的了解有关 GCD 的内容,可以看一下 WWDC2017 - 706 - Modernizing Grand Central Dispatch Usage[8]

Optimize Work

  • 简化现有工作,比如只请求必要的数据。

  • 优化算法和数据结构

  • 缓存资源和计算

使用 Instruments 分析 App 启动过程

当知道如何优化之后,我们需要针对我们的启动过程进行分析。Xcode 11 的 Instruments 为此新增了一个 App launch 模板,让开发者可以更好的分析自己 App 的启动速度。

 

运行后可以看到各个阶段的具体时间,根据数据进行优化,还能看到耗时的函数调用。

 

系统优化

去年苹果做了很多优化,下面这几个高亮的是和启动速度有关的优化

 

但是不知道是不是时间原因,在 session 中对于这部分的解释特别少,很难理解 200% 到底做了什么。

但是 Craig Federighi 在 The Talk Show Live From WWDC 2019, With Craig Federighi and Greg Joswiak[9] 中针对为什么优化了 200% 说了这样一段话:

Isn’t that crazy that was quite a discovery for us. No it turns out that over times as in terms of the way the apps were encrypted and the way fair play worked and so forth. The encryption became part of the critical path actually of launching the apps. I mean the processors are capable or up and through the thing that actually it was a problem. And then there are other optimizations that based on what was visible to system at certain things. And so it actually cut out optimization opportunities and so when we really identified that opportunity we said okay. We can actually come up with better format that’s gonna eliminate that being on the critical path, It’s going to enable all these pre-binding things. And then we did a whole bunch of other work to optimize the objective-c runtime to optimize the linker the dynamic linker a bunch of other things and you put it all together. And yeah that I mean a cold launch this is we’ve never had a win like this to launch time in a single release.

从这段话中,除了 dyld3 的功劳之外,减少对代码签名加密也是优化之一。

监控线上用户 App 的启动

Xcode 11 在 Xcode Organizer 新增了一个监控面板,在这个面板里面可以查看多个维度的用户数据,其中还包括平均启动时间。

 

当你通过 Instruments 分析完你的启动过程,并做了大量优化之后,你就可以通过 Xcode Organizer 来分析你这次优化效果到底怎么样。

当然你可以通过去年新出的 MetricKit[10] 获取一些自定义的数据,具体参照 WWDC2019 - 417 -Improving Battery Life and Performance[11]

总结

今年苹果提的很多优化建议其实在早几年都已经说过一遍了,属于老生常谈了。但是值得点赞的是新出的 Instruments 的 App launch 和 Xcode Organizer 的性能监控。

启动优化是一个需要经常反复做的事情,越早发现问题,越容易解决问题,如果你想给你的用户一个良好的第一印象,就赶快行动起来吧。

参考资料

[1]

WWDC 2019 keynote: https://developer.apple.com/videos/play/wwdc2019/101/

[2]

WWDC2019 - 423 - Optimizing App Launch: https://developer.apple.com/videos/play/wwdc2019/423/

[3]

dyld启动流程: https://leylfl.github.io/2018/05/28/dyld启动流程/

[4]

WWDC2017 - 413 - App Startup Time: Past, Present, and Future: https://developer.apple.com/videos/play/wwdc2017/413/

[5]

Static linking vs dyld3: https://allegro.tech/2018/05/Static-linking-vs-dyld3.html

[6]

WWDC2018 - 220 - High Performance Auto Layout: https://developer.apple.com/videos/play/wwdc2018/220/

[7]

WWDC2019 - 417 - Improving Battery Life and Performance: https://developer.apple.com/videos/play/wwdc2019/417/

[8]

WWDC2017 - 706 - Modernizing Grand Central Dispatch Usage: https://developer.apple.com/videos/play/wwdc2017/706/

[9]

The Talk Show Live From WWDC 2019, With Craig Federighi and Greg Joswiak: https://daringfireball.net/2019/06/the_talk_show_live_from_wwdc_2019

[10]

MetricKit: https://developer.apple.com/documentation/metrickit

[11]

WWDC2019 - 417 -Improving Battery Life and Performance: https://developer.apple.com/videos/play/wwdc2019/417/


一个APP如何适配多个Andiroid终端?

IM即时通讯大兴 发表了文章 • 0 个评论 • 85 次浏览 • 2020-10-22 17:45 • 来自相关话题

Android响应式方案响应式的核心是拉通多终端的适配规则,开发一套界面,一个APP兼容多尺寸终端设备的显示,能够根据用户的行为以及设备的环境(屏幕尺寸、屏幕方向、是否分屏等)进行相应的页面布局以及容器尺寸的调整,为用户提供更加舒适的界面和更好的用户体验。1&... ...查看全部


Android响应式方案

响应式的核心是拉通多终端的适配规则,开发一套界面,一个APP兼容多尺寸终端设备的显示,能够根据用户的行为以及设备的环境(屏幕尺寸、屏幕方向、是否分屏等)进行相应的页面布局以及容器尺寸的调整,为用户提供更加舒适的界面和更好的用户体验。

image.png

1  响应式SDK



App的每个页面支持响应式,开发成本是很高的。

响应式SDK,就是为了解决App在不同尺寸设备下的适配问题,把设备的屏幕信息、容器布局规则(列数、尺寸)、业务数据二次加工等行为进行统一管理,以适应新的屏幕尺寸。


image.png2  加载流程设计image.png

通用的页面加载流程,通常都是从数据返回开始,数据解析完成后,进行页面布局渲染以及容器布局渲染。响应式在通用加载流程的基础上,加入了响应式状态变化通知、响应式数据剪裁、响应式页面布局、响应式容器布局等流程。



具体加载的流程分为两种情况:


  • 用户请求数据

  • 屏幕尺寸发生变化


 

3  架构设计



优酷各个业务开发团队,使用了统一的业务架构,我们在统一架构的基础上进行响应式适配,提供了响应式SDK,拉通各个业务方不同页面的适配规则,确保了适配效果的一致性,同时提供了基础的响应式控件,降低业务方的接入成本,那么响应式架构具体是怎么实现的呢?


image.png

从结构上看,响应式由优酷统一架构、响应式SDK、响应式页面布局、响应式容器布局四部分相互配合完成。在这些基础上支撑了首页、频道页、播放页、会员页、搜索、个人中心等众多的业务场景。




优酷统一架构和响应式SDK,提供响应式架构能力。




响应式页面布局、响应式容器布局,提供响应式参考实现。




4  数据二次加工



响应式并不是简单的将现有Phone端的业务数据,投放到Pad、折叠屏上,单纯的进行UI页面适配。想要在不同尺寸设备上都能获得良好的适配效果,需要对Phone端的业务数据二次加工,进行数据过滤、数据映射、数据合并、数据补全等操作,才能更好的适配Pad和折叠屏。

响应式SDK只是负责把数据二次加工的协议规则定下来,具体的数据二次加工逻辑需要业务方自己实现。优酷的统一架构提供了数据切面的能力,在切面上增加数据二次处理的逻辑,实现了统一的数据处理。


数据过滤



大尺寸设备上,总会遇到一些复杂的,适配不了的,也不重要的组件,这部分组件可以根据具体情况过滤处理,例如:下图中的weex组件,在Pad上直接过滤掉,不显示。


image.png

数据映射



存在一些带交互的复杂组件或者Pad上适配效果较差的组件,可以直接映射成其他已适配的组件。例如:下图中的带视频预览的预约组件映射成普通的预约组件。


image.png

数据合并



相邻的两个组件,其中有一个组件无法很好的适配大尺寸Pad,可以尝试将其数据合并到其他组件内。

例如:下图中第1个组件宽度铺满页面宽度,在大尺寸上无法适配,第2个组件通过修改列数、尺寸就可以适配。Pad竖屏下,将第一个组件插入到第二个组件的首位,进行数据合并,按照第二个组件的进行适配,显示为3列2行,达到很好的适配效果。


image.png

数据补全



在横竖屏切换过程中,部分组件会遇到组件的数量,无法铺满屏幕的宽度,导致出现留白的问题。

例如:把手机上的6条数据,直接投放到Pad横屏下,就会出现下图的留白问题:


image.png

为了解决这一类数据缺失的问题,我们选择的解法是服务端多下发一部分业务数据,客户端根据具体的屏幕尺寸,动态调整显示的个数,确保显示效果。



例如:下图中手机上显示2列3行,共6条数据,到了Pad竖屏上显示3列2行,共6条数据,到了Pad横屏上会补全2条数据,显示4列2行,共8条数据。


image.png

5  页面响应式



响应式状态

响应式状态是页面响应式最基础也是最重要的一个能力,像横竖屏切换、分屏模式、折叠屏折叠打开,都会导致页面的宽高发生变化,产生不同的响应式状态,页面内的内容会进行重新布局以及组件尺寸调整,以适应页面尺寸的变化,铺满屏幕,达到更好的显示效果。

横竖屏切换:


image.png分屏模式

image.png

折叠屏:

image.png



获取响应式状态



响应式状态的定义,需要有一个具体计算的规则,在所有尺寸的设备上都按照统一的规则进行状态区分,那么不同的响应式状态是如何区分的呢?

首先定义标准手机屏幕的物理宽度为400dp(经过大量手机设备调试采样之后获得的手机标准物理尺寸经验值),那么响应式状态的变化,由两个比例阈值决定,一个是页面物理宽度与标准物理宽度的比例阈值1.67倍(物理宽度 = 像素宽度÷屏幕密度),另一个是页面高度与页面宽度的比例阈值1.25倍。那么这两个比例阈值是如何得来的呢?

(1)1.67倍是怎么来的呢?


image.png

在播放页的适配过程中,需要适配左右分栏的显示,我们认为左侧播放器的宽度是标准物理宽度,那么整个页面的宽度就是标准物理宽度的1.67倍,这样左侧播放器有足够的空间保障视频播放的体验,右侧的也有足够的空间保障评论的显示效果。


 
(2)1.25倍是怎么来的呢?


image.png

上图列举了竖屏华为Pad上,页面高度是页面宽度的1.6倍,播放器下方的视频内容操作区,显示的视频内容是足够多的。如果页面高度小于页面宽度的1.25倍,就会挤压视频内容操作区的高度,导致显示出来的视频内容过少,影响用户体验。




当页面物理宽度大于标准物理宽度的1.67倍,同时页面高度小于等于页面宽度的1.25倍,即为大屏状态,其他情况则为小屏状态。
 
不同的响应式状态

目前支持了小屏布局和大屏布局两种状态。
 
(1)小屏布局状态

折叠屏折叠、折叠屏分屏、Pad竖屏:


image.png

image.png

image.png

(2)大屏布局状态



折叠屏打开、Pad横屏:


image.png

6  容器响应式



容器响应式,主要解决在页面尺寸发生变化时,动态调整容器布局的列数以及坑位的尺寸,优酷统一架构提供了常用的响应式容器布局:轮播布局、网格布局、横划布局、瀑布流布局。业务方可以快速实现响应式的效果。


容器适配列数、尺寸的效果

image.png

列数适配


同一个容器,在不同的尺寸页面下,会根据页面的物理宽度动态适配,显示为不同的列数。

网络布局、横划布局、瀑布流布局都采用这一套列数适配的规则:

响应式适配后的列数 = 当前屏幕宽度÷(标准屏幕宽度÷标准屏幕宽度下的组件列数 )

响应式适配后的列数,并不能解决Pad横屏上部分组件列数过多,显示过密的问题,为了解决这类问题,提供了列数二次适配的能力。

如下图所示,左侧是直接根据规则算出来的Pad横屏下的列数8列,过于密集,显示效果不好,右侧是列数二次调整后,显示为6列。

image.png

适配效果:image.png

控件尺寸适配


由于不同屏幕尺寸下,容器内部会动态调整显示不同的列数,导致控件的尺寸也会发生变化,那么如何适配控件尺寸的动态变化呢,响应式基础控件能够很好的解决这一类问题。
<span style=&qu

30万+App都在用的服务商,有什么特别之处?

科技创新融云那些事 发表了文章 • 0 个评论 • 125 次浏览 • 2020-08-03 16:57 • 来自相关话题

两个月前,一款名为 Clubhouse 的 App 开始流行于美国 VC、名人圈层,并且一直热度不减。尽管它的用户只有几千人,但其估值却已超过 1 亿美元。Clubhouse 是一款语音社交软件,被誉为是“音频 Twitter”。打开 Clubhouse,选一... ...查看全部

两个月前,一款名为 Clubhouse 的 App 开始流行于美国 VC、名人圈层,并且一直热度不减。尽管它的用户只有几千人,但其估值却已超过 1 亿美元。

Clubhouse 是一款语音社交软件,被誉为是“音频 Twitter”。

讯飞1.webp.jpg

打开 Clubhouse,选一个房间进入,人们可以查看房间里都有哪些参与者,并可以听到他们的谈话,再决定是否继续听或直接加入群聊。

如果要说它最大的规则是什么,那就是:只用语音,没有文字

近年来,移动互联网飞速发展,语音功能已开始成为 App 的标配,各种属性的音频类 App 尤其是声音社交类 App 也在悄然壮大。

红杉中国发布的《创造未来——红杉 00 后泛娱乐消费研究报告》显示,社交性、潮流性和个性化是“00 后”用户最看重的三大产品特征。因此,比图片和文字有温度,比视频更含蓄的声音社交方式,开始在他们之中风靡。

而根据艾瑞咨询的数据,2019 年中国网络音频行业市场规模为 175.8 亿元,同比增长 55.1%,预计 2020 年中国网络音频行业市场规模达 272.4 亿元。越来越多的资本和资源看到了声音背后蕴藏的更多可能性。

讯飞2.png

随着 5G 网络的逐渐普及,网络速度变得更快,以音频、视频为主要交互方式的互联网产品或将大行其道。

今年年初的疫情期间,我们已经看到各种音视频软件在在线教育、在线办公中发挥着巨大的作用。但有一个不得不面对的事实是,它们都曾因短时期内使用人数过多而崩溃。

对于音视频类互联网产品来说,保证用户使用过程中不会出现卡顿、声音效果差等问题十分重要,这就需要技术上的强力支持与保证,在这一方面能够提供“实时音视频”技术的服务商做的相当专业。

目前,在我国从事“实时音视频”技术底层搭建的服务商主要分为两大阵营。其一,是只专注“实时音视频”技术的单一产业服务商;其二,是 IM 领域已经耕耘多年,现将 IM 即时通讯及实时音视频两大业务版块交融聚合的服务商。例如,已多年稳居 IM 市场占有率第一的融云。

讯飞3.webp.jpg

北京云中融信网络科技有限公司(简称融云),是一家安全、可靠的全球互联网通信云服务商,向开发者和企业提供即时通讯和实时音视频通信云服务。虽成立时间不长,但发展迅速,成立不久就获得了 1 亿元人民币 B 轮融资。

截止目前,已有中原地产、汽车之家、融创地产、丽兹行、寺库、哈啰出行、核桃编程、易车网、编程猫以及 Castbox、Opera 等超过 30 万款海内外 App 通过融云实现了全球化的互联网通信能力。

融云现已在科大讯飞旗下人工智能全产业链综合服务平台——讯飞 AI 服务市场入驻,来一起看看,它的哪些技术能让如此之多的 App “钟情”于它?

融云IM即时通讯

许多 85 后甚至 90 后的大学记忆中都有一款即时通讯工具,飞信。在那个短信 1 毛钱一条,流量 5 块钱只有 30M 的年代,飞信免流量、免费发短信的功能,很有吸引力。作为当初中国移动即时通讯的扛鼎之作,飞信注册用户最高时达到了 5 亿,高峰时拥有高达 9000 万的活跃用户,其技术水平自不必多说。

融云的技术团队源自飞信技术团队,在即时通讯领域有着丰富的经验和强大的核心技术实力。

融云即时通讯支持多聊天模式、多消息类型、自定义界面等功能,同时支持聊天记录漫游、消息回执与召回、消息内容全文搜索、消息内容审核等功能。用户可以根据自己的需求调用相关接口,大大节约在通信能力上的研发时间和成本。

讯飞7.png

基于融云私有通信协议,可以保障消息不丢不重不乱,支持无上限用户数的群组和聊天室互动,公有云平台 150 亿条日均消息量2218 亿条日消息量峰值。融云建立了全球多数据中心,服务覆盖全球所有国家及地区(共 233 个),实时监控全球网络,基于融云分布在全球的数据中心与节点建设,向客户提供链路接入方案,持续的海外链路优化让消息发送稳定可靠,畅通无阻。

讯飞8.webp.jpg

无论是私密社交、兴趣社交还是商业沟通、系统消息等场景,融云都可以轻松应对,它也是业内唯一承诺消息可靠性 100% 的厂商。

融云RTC实时音视频

为了适应移动互联网发展和市场需求,融云在即时通讯之外还发展了实时音视频业务。融云实时音视频主要包括音视频通话、低延迟直播、音视频会议、云端录制等功能。用户可以根据自己的需求调用相关接口,大大节约在通信能力上的研发时间。

讯飞9.png

融云实时音视频具备低延迟、低成本、高流畅、高品质、部署简单、扩展灵活等技术优势。在整体网络架构上,全球分布式架构的部署让扩容时间大幅缩短,能轻松应对海量流量的激增。弱网优化策略方面,增强了抗丢包及抗网络抖动能力,音频能对抗 80% 丢包,视频能对抗 40% 丢包,延时最低可达 66ms,以保证低成本输出高性能的实时音视频能力(可以提供最高 1080P 的高清画质和最高 48KHz 的音频采样率) 。经过海量客户业务验证,融云实时音视频业务在稳定性、连通性、并发/负载等方面服务可用性达到 99.9%

融云在疫情期间还免费开放了在线医疗、在线教育及协同办公场景的通信能力,并开发了 VR 看房等新的业务场景。

最新活动推荐:

 融云年中大促活动海报.png


印度宣布封杀59个中国App,透露了哪些信息?

IM即时通讯大兴 发表了文章 • 0 个评论 • 111 次浏览 • 2020-07-02 11:27 • 来自相关话题

6月29日,印度政府信息技术部宣布,以 “有损印度主权和完整、印度国防、国家安全和公共秩序”为由,宣布禁止59款来自中国的应用程序,这些应用包括抖音海外版Tik Tok、微信、微博、百度地图、UC浏览器、快手海外版Kwai、QQ等,59款App均来自中国公司。... ...查看全部

6月29日,印度政府信息技术部宣布,以 “有损印度主权和完整、印度国防、国家安全和公共秩序”为由,宣布禁止59款来自中国的应用程序,这些应用包括抖音海外版Tik Tok、微信、微博、百度地图、UC浏览器、快手海外版Kwai、QQ等,59款App均来自中国公司。

截至《财经》记者发稿时(6月30日下午),Helo和Tik Tok已经在印度各大应用商店下架,包括Vigo(火山视频海外版)在内的其他一些应用暂时还可以下载和使用。印度官方公告中也并未说明会用哪种方式禁止这些App以及具体的禁止时间。此外,印度政府此番对中国互联网公司的限制尚未波及来自中国的资本,如有中资背景的印度支付应用Paytm以及外卖软件Zomato等均尚未出现在禁令上。

一位印度高级官员在匿名接受印度《经济时报》采访时表示,这些被禁止的中国App已存在很长时间并存在隐私和安全问题。他还称,印度政府在做出这项决定之前已考虑了所有方面。

针对印度近日宣布封杀微信抖音等59款中国App,中国外交部发言人赵立坚6月30日回应称,中方对印方有关公告表示强烈关注,正在了解核实情况,中国政府一向要求中国企业在遵守国际规则和当地法律法规的基础上开展对外合作,印度政府有责任根据市场原则维护包括中国企业在内的国际投资者的合法权益。中印两国在务实领域的合作是互利共赢的,这种合作格局受到人为损害,实际上并不符合印方自己的利益。

腾讯集团相关负责人向《财经》记者表示,针对这一事件不予评价和回应。截至《财经》记者发稿,字节跳动和百度等相关公司也没有给予《财经》记者正式回应。

资深科技行业从业者和观察家李军向《财经》记者分析,印度政府对中国App的限制措施可能包括但不仅限于:在用户购买手机时不再预装,印度本地的应用商店下架这些App,用户只能通过转换其他国家的账号来进行下载,他认为,大部分用户会卡在应用商店下载环节。

名单上的59个中国App可以分为三类,一类是在中国已经有市场基础,复制到印度市场,如微信、微博等;第二类为中国公司针对海外市场推出的孪生产品,如抖音的海外版Tik Tok;第三类是依靠印度市场成长起来的中国公司,例如跨境电商公司Club Factory,目前已经是印度第三大电商平台,仅次于亚马逊和Flipkart。

090807184205.jpg

随着技术的进步,信息安全对于国家安全的影响等级一再提升。此前欧盟多次指出互联网公司的数据安全隐患。几乎所有公司在面临类似指控时,都会强调对数据安全的重视,比如,该公司针对特定国家用户的所有数据都会保存在该国本地服务器中,不会被转移或滥用。

李军分析,海外市场的数据确实不太可能被公司转移,因为性价比太低,来自印度用户市场的数据对于中国公司的最大价值,可能是用于海外市场用户数据分析。

在全球互联网和科技领域,目前处于驱动者地位的是中国和美国,印度这样的新兴市场,市场空间庞大,但基础相对欠缺,缺资金,在技术、运营和平台搭建能力上也不够成熟,需要外部力量的支持,但同时也对全球开放了机会。在全球科技行业里,印度被认为是最大的,最具有想象力的新兴市场。

印度政府的禁令短期内对中国科技公司和资本都造成了不同程度的打击。长期来看,要想在这个可能有政策变数风险的国家继续开展业务,中国科技公司需要进一步调整姿势。

一、涉及至少6.5亿印度用户

当地时间6月29日深夜,大量和中国有业务联系的印度人,以及需要和印度合作方联系的中国人在微信朋友圈发出备选联系方式,避免由于微信被封杀导致的不利影响。

漏夜准备应对措施的不仅是用户。一位关注印度市场的投资人告诉《财经》记者,他们连夜整理研究了名单上的公司,计算出的数据是,这些App在印度的用户数量去重之后,有6.5亿。6.5亿用户背后,还有大量已经在这些App上投放广告的印度本地企业及他们的员工。

此次印度政府的封禁措施影响最大的可能是Tik Tok,印度是Tik Tok最大的市场,移动应用分析公司Sensor Tower数据显示,Tik Tok在印度总下载量超过5亿,占据Tik Tok总用户30%以上。

2019年9月,UC浏览器发布数据称,在全球拥有4.3亿多用户,印度是其主要市场之一,拥有1.3亿多用户。第三方数据分析公司StatCounter的数据显示,UC浏览器占据了印度移动浏览器市场逾23%的份额,仅次于谷歌Chrome。

字节跳动旗下的Helo是一个提供14种印度语言的社交媒体平台,2019年7月,Helo称拥有超过5000万月活跃用户。

今年6月,快手旗下Kwai已停止在印度运营,用户导入旗下的另一款短视频应用UVideo,目前该产品在Google Play商店的下载量已超过5000万。

前述不愿具名的投资人判断,这么庞大的用户体量,长时间全面封禁不太现实,而且,其中很多App目前在印度市场还没有成熟的替代品。对于科技公司来说,风险不会太大。

不过,从投资的角度来看,他表达了强烈的担心。他对《财经》记者说,这一举动对中国资本投资印度的信心将带来“毁灭性打击”,“谁都不敢去一个动不动就说要封了你的市场。”

近两年,中国资本十分热衷于投资印度市场。印度全球关系智库Gateway House今年3月发布报告显示,中国科技投资者对印度初创公司的投资额预计超过40亿美元。截至2020年3月,印度30家独角兽公司中,有18家背后有来自中国的投资者。

090807484041.jpg

印度独角兽公司背后的中国资方。数据来源:印度全球关系智库Gateway House

字节跳动一直试图将Tik Tok与中国公司分拆。今年5月19日,字节跳动宣布,任命前迪士尼高管凯文·梅耶尔(Kevin Mayer)为字节跳动首席运营官兼TikTok全球首席执行官,

“请Kevin来的主要目的之一就是为了打通Tik Tok的海外政府关系,独立运营。”

禁止名单中还包括3款游戏App,分别是北京智明星通旗下的《列王的纷争》、沐瞳科技旗下的《无尽对决》和一款小游戏社交App《Hago Play With New Friends》。不过,目前在印度最热门的游戏《PUBG MOBILE》(也称“吃鸡”,由腾讯发行)不在名单之中。第三方数据机构App Annie数据显示,《PUBG MOBILE》在印度市场的下载量超过1亿,是印度月活(MAU)最高的手游。

有资深行业人士向《财经》记者分析,没有将《PUBG MOBILE》列入禁止名单,可能是因为这款游戏的研发团队是韩国公司,腾讯只是发行方。另一种可能性是,这份禁令只是第一份,后续可能会有第二份、第三份……。有腾讯游戏匿名业务人士则认为,这可能是因为腾讯专门为这款游戏做了针对印度市场的合规。

中国社科院南亚研究中心副主任刘小雪长期关注印度经济和中国企业在印度投资,她对《财经》记者表示,印度在边境问题上理亏但又要安抚国内民众情绪,因此出台这样一个象征意义大于实际意义的禁令,说明印度不想对中国采取强硬措施。

不仅是手机App,印度政府最近还在拖延中国进口产品清关。从6月22日晚开始,印度钦奈港要对所有来自中国货物实施100%检查(此前是抽查),要求提供额外的清关材料。钦奈港是电讯部件和设备的重要港口,很多中国公司货物都是从该港口进入印度市场。

印度此举不仅损害中国企业利益,同时还引起美国企业的不满。苹果、思科和戴尔等美国企业的产品也受牵连,6月23日,代表美国企业的游说团体美印战略伙伴关系论坛(USISPF)致信印度商务部表示,印度是在向寻求可预测性和透明度的外国投资者发出令人担忧的信号。

今年4月,印度为了防外资抄底出台行政法规,要求对来自“接壤”国家的投资必须申请事先审核,此举被广泛认为针对中国。

在印度从事中国企业投资法务问题的印度大恒竺成(Linklegal)律师事务所顾问李钦认为,对很多中国投资者的项目产生了延误等不良影响,印度政府甚至指示其驻中国的使领馆对投资者进行面试,让不少中国投资者唏嘘。他对《财经》记者评价,印度作为一个1991年实施对外开放政策的新兴市场国家,其政府和民众对于外资的理解仍任重而道远。

二、中国公司如何规避长期风险?

2019年,一位中国投资人去印度考察,他回忆,在印度的一家咖啡馆里,至少坐着10个中国投资人,回想起那个场景,他说,“很像当年的中关村。”

软银集团CEO孙正义曾经提出“时光机”理论——发达市场验证过的机会和模式,会在新兴市场重新出现。一些中国创业者和投资人带着这样的信心,投入到印度市场中,认为自己获得了“上帝视角”。

但“上帝视角”也并非一帆风顺,2018年,一些创业公司试图在印度复制一个“今日头条”,很快就销声匿迹,印度不少手机用户的信息获取需求已经被Facebook满足,且少有广告商愿意在新闻聚合平台上做付费营销。

回到今天,Tik Tok、Facebook、Instagram、Youtube等已经验证过的模式,确实受到了印度用户的欢迎,但他们都面临一个难题——流量变现。

多位投资人向《财经》记者确认,印度市场虽然用户量很大,但整体付费意愿非常低,仅略高于非洲,很多公司在印度有不错的用户量,却赚不到钱。

但开辟印度市场依然有价值。印度作为一个拥有13.24亿居民的国家,印度统计局数据显示2019年以前,年均GDP增速在6%以上,2019年降至5%,还是一个正在高速增长的大市场,这很难得。此外,如果能拿下印度市场,也能更好地开辟其他新兴市场。

反观印度,印度政府也希望外部资本能够进入印度。国际货币基金组织(IMF)在6月24日发布最新《世界经济展望》报告中大幅下调对印度经济增长预期,目前的预期为收缩4.5%,此前的预期为增长1.9%。

就业率低迷是印度目前的一大问题,印度经济监测中心(CMIE)发布数据称,今年3月印度就业率为38.2%,为历史最低水平,失业率已经达到8.7%,为2016年9月以来最高水平。

目前,中印两国政府已经为边境摩擦降温展开磋商,事态正在向积极方向进展。但边境摩擦已经在印度国内转化成对中国产品的抵制。

一名在印度经营手机配件生产的人士对《财经》记者透露,出售中国手机的门店已经用Made in India 遮盖品牌logo。另一位在印度本地的中国手机公司人士也提到,很多门店虽然是本地经销商开的,也担心有人打砸。

有接受《财经》记者采访的相关人士预判,印度政府暂时不会对手机硬件行业做出禁令。包括小米、OPPO、vivo、Realme等在内中国手机厂商,已经占据了印度手机市场的70%以上的市场份额,目前也没有印度本土品牌可以替代。

而且,这些手机厂商都已经在印度建厂,组装环节基本都在本地工厂完成,带动了印度的就业率。

一位在印度的vivo人士告诉《财经》记者,中国手机厂商是最早进入印度的商业力量,因此,这几家手机厂商的本地化都做得较好,日常的业务人员都是印度人,出了问题都可以尽快找人解决。

这一点,相比刚刚进入印度市场不久,尚未来得及本土化的中国互联网公司来说,确实有一些优势。“App被禁止,可能都不知道找谁来解决问题。”

包括李军在内的受访者认为,中国公司的海外业务想要独立发展,至少要做到人员切割、数据切割、业务运营切割、IT系统切割,只留下财务联系,未来财务联系也要尽量弱化,引入当地战略投资者,共享利润,在政府关系上也能提供帮助。

李钦的观点是,鉴于中国基本的影响力,印度政府不太希望中国投资者在印度市场单干,而是采取和印度某些合作对象进行深度绑定的方式。以App等互联网产品为例,印度希望中国投资本国App。

对于实业,印度则希望中国找本土企业合资,深度绑定,减少中国投资者自主权,只有这样印度才放心。中国企业却很难接受,他们认为找到合适、靠谱的合作合资对象非常难。


作者为《财经》记者

本文来自微信公众号:财经十一人(ID:caijingEleven),作者:刘以秦、 王晓枫、 柳书琪、 陈潇潇 ,编辑:谢丽容  


融云 IM SDK 集成 —- 刷新会话界面和会话列表界面

IM即时通讯大兴 发表了文章 • 0 个评论 • 414 次浏览 • 2020-12-02 16:28 • 来自相关话题

最近集成融云 IMkit 发现, 融云 IMkit 提供的 ConversationListFragment 和 ConversationFragment 都没有提供刷新方法.我们有个需求是, 使用 Message 的 extra 修改值后, 但是界面没有进行... ...查看全部

最近集成融云 IMkit 发现, 融云 IMkit 提供的 ConversationListFragment 和 ConversationFragment 都没有提供刷新方法.

微信截图_20201202162201.png

我们有个需求是, 使用 Message 的 extra 修改值后, 但是界面没有进行刷新, 需要自己调用代码进行刷新, 但是, 融云没有提供对应的刷新界面的方法, 所以只能看代码.

刷新 ListView 是需要获取 Adapter , 然后使用 Adapter 进行刷新, 但是我们无法从 ConversationListFragment 中直接获取, 那怎么办呢?

我们知道, ListView 中有 getAdapter 的方法. 那我们能不能先获取 ListView 控件呢? 答案是可以的. 我们通过 findById 方法即可获取 ListView. 通过查找, 我们可以知道, 融云会话列表的 listView 的 id 为 R.id.rc_list.

所以代码如下.

ListView mList = (ListView)findViewById(R.id.rc_list);
HeaderViewListAdapter adapter = (HeaderViewListAdapter)mList.getAdapter();
ConversationListAdapter wrappedAdapter = (ConversationListAdapter)adapter.getWrappedAdapter();
if (adapter != null) {
    wrappedAdapter.notifyDataSetChanged();
}


融云即时通讯SDK集成 — 通知检查

IM即时通讯大兴 发表了文章 • 2 个评论 • 444 次浏览 • 2020-12-02 16:28 • 来自相关话题

背景:最近公司新上的app要加上即时通讯的功能, 自己快速实现一个当然是不可能的了(项目deadline也顶不住哇).就从各家成熟的SDK厂商选来选去的, 各有各的好也各有各的不足.最后点兵点将,选了融云家的SDK(老板说了算hhhh).他家的官网和文档地址:... ...查看全部

微信截图_20201202162632.png背景:

最近公司新上的app要加上即时通讯的功能, 自己快速实现一个当然是不可能的了(项目deadline也顶不住哇).就从各家成熟的SDK厂商选来选去的, 各有各的好也各有各的不足.最后点兵点将,选了融云家的SDK(老板说了算hhhh).
他家的官网和文档地址:
官网:https://www.rongcloud.cn/
文档:https://docs.rongcloud.cn/v4
这个任务当然还是落在我的头上. 集成完毕后, 也踩了不少坑. 所以这篇文章给大家总结下排查融云消息的本地通知和远端推送的办法. 希望可以帮助到正在看这篇文章的你.

什么是本地通知:

当我的App接入了融云的即时通讯sdk后, 便拥有了即时通讯的能力. sdk与融云服务器建立长连接, 当消息发出后, 先走到融云的服务器, 再转发给相应的用户. 这里移动端到服务端, 服务端到移动端, 走的通道都是长连接. 无论你的app是在前台还是在后台, 只要没有被杀死, 那么长连接是一直在的. 所以消息可以即时的发送到达给接收者. 融云把这种走长连接到达的消息, 在通知栏展示的通知叫做本地通知. 也就是消息是顺利发送到接收者端了, 逻辑可以走到消息接收监听那里. 融云sdk内部实现了消息到达后的本地通知, 也赋予了开发者自行实现消息到达后进行本地通知的权利.

本地通知的检查:

这里我总结了一下接入融云sdk后, 关于本地通知接收不顺利的排查. 大致可以分为这么几条:

1.是否设置了 setOnReceiveMessageListener 监听, 并且 onReceived 方法返回的为 true。

 RongIM.setOnReceiveMessageListener(new RongIMClient.OnReceiveMessageListener() {
              @Override
              public boolean onReceived(Message message, 
                  return true;
              }
          });

如果 onReceived 方法返回值为 true 则是监听做了拦截, 则不会走通知逻辑。 2.发送的消息是否是自定义消息。 如果是自定义消息, 则请查看自定义消息的 MessageTag 的注解是否设置了 flag 的值为 MessageTag.ISCOUNTED 或 MessageTag.ISPERSISTED) 。 以下面为例。

@MessageTag(value = "RC:TxtMsg", flag = MessageTag.ISCOUNTED)
  @DestructionTag
  public class TextMessage extends MessageContent {
      ...
  }

如果没有设置其中之一,则不会走通知逻辑。

3.是否设置消息拦截器

设置代码如下:

/**
   * 设置接收消息时的拦截器
   *
   * @param messageInterceptor 拦截器
   */
  public void setMessageInterceptor(new RongIM.getInstance().MessageInterceptor() {
          @Override
          public boolean intercept(final Message message) {
                 return true;
          }
 });

如果设置了消息拦截器, 并且在 intercept() 方法中返回 true, 表示拦截此消息。 则不会走通知逻辑。

4.检查接收到的消息的 senderUserId 是否和当前用户的 id 相同。
如果 id 相同, 则不会走通知逻辑。 自己不可发给自己。

5.查看当前接收的消息是否是属于聊天室会话类型的
聊天室的消息目前不支持本地通知。

6.如果是自定义消息,请检查是否设置了自定义消息的 MessageProvider 。
如果没有,则不会走通知逻辑。

7.接收方本地是否有接收方的用户信息。 如果没有设置, 则不会走通知逻辑。

8.检查应用权限设置,看是否打开应用信任权限, 通知栏权限或者声音提示权限等。如果没有打开,请打开再试。

9.接收方没有发送方的用户信息,接收端不弹通知。用户信息通过用户信息提供者方式获取。
参考链接:https://docs.rongcloud.cn/v3/views/im/ui/guide/private/user/set/android.html

什么是远端推送:

集成了即时通讯的SDK, 我们的app不就能拥有像微信一样随时随地收到消息的即时通讯能力了? 说实话我一开始也是这么认为的. 可惜做开发也要按基本法来, Android平台回收app的这一关咱都过不了, app都给你杀死得透透的了你拿啥收消息呢? 咱又不是微信hhhh. 所以一番急赤白脸地阅读他家文档之后, 才发现app如果活着, 他融云能用自己的通道给你把消息推送到. 如果app被杀死了, 这个消息就在他家的服务端直接交给三方厂商了(也就是五大厂商蓝绿大厂华为小米FCM), 让这条消息走人家厂商的推送通道给送到你手机上.

远端推送的检查:

这里我总结了一下接入融云sdk后, 关于本地通知接收不顺利的排查. 大致可以分为这么几条:

  1. 退出应用的时候,只能调用融云的 disconnect() 方法,而不是 logout()。这样退出后融云才会启动push进程。

  2. push 进程的名字不能更改,必须是默认的名字,既 io.rong.push.
    (2.6.0dev之后的版本,此进程名字可以修改)

  3. 通过 ddms 或者终端里敲入 adb shell ps|grep rong 查看终端里是否存在 io.rong.push 这个进程。

有些手机厂家做了特殊限制,不允许第三方后台进程启动,所以融云的后台进程起不来,导致收不到 push 消息。这种情况可以换个手机测试(如三星,大部分三星手机没有做权限限制)。另外大部分国产手机,有权限设置的菜单,比如小米,华为等,可以手动去安全中心,设置应用的自启动权限,后台运行权限等,就可以收到Push消息了。

不过 vivo 和 oppo 有些型号的手机,一旦应用退到后台,系统会很快把它杀死,这种没有办法解决。目前市面上所有推送都存在这个问题,除非系统把该应用加入白名单。

  1. push 进程存在,仍然收不到 push 消息。

如果你的应用有消息免打扰功能,那么请确认当前登录账号之前是否设置过消息免打扰。如果不太确定,那最好去你的应用设定里重新设置下消息免打扰时间。 这里要注意的是: 如果这个账号之前在别的手机上设置过消息免打扰,换一台手机登录或者卸载重装的时候,融云服务端记录的仍然是之前设置的消息免打扰状态,所以这种情况下是收不到push消息的。


融云 IM SDK 集成 —- 刷新会话界面和会话列表界面

IM即时通讯大兴 发表了文章 • 0 个评论 • 414 次浏览 • 2020-12-02 16:28 • 来自相关话题

最近集成融云 IMkit 发现, 融云 IMkit 提供的 ConversationListFragment 和 ConversationFragment 都没有提供刷新方法.我们有个需求是, 使用 Message 的 extra 修改值后, 但是界面没有进行... ...查看全部

最近集成融云 IMkit 发现, 融云 IMkit 提供的 ConversationListFragment 和 ConversationFragment 都没有提供刷新方法.

微信截图_20201202162201.png

我们有个需求是, 使用 Message 的 extra 修改值后, 但是界面没有进行刷新, 需要自己调用代码进行刷新, 但是, 融云没有提供对应的刷新界面的方法, 所以只能看代码.

刷新 ListView 是需要获取 Adapter , 然后使用 Adapter 进行刷新, 但是我们无法从 ConversationListFragment 中直接获取, 那怎么办呢?

我们知道, ListView 中有 getAdapter 的方法. 那我们能不能先获取 ListView 控件呢? 答案是可以的. 我们通过 findById 方法即可获取 ListView. 通过查找, 我们可以知道, 融云会话列表的 listView 的 id 为 R.id.rc_list.

所以代码如下.

ListView mList = (ListView)findViewById(R.id.rc_list);
HeaderViewListAdapter adapter = (HeaderViewListAdapter)mList.getAdapter();
ConversationListAdapter wrappedAdapter = (ConversationListAdapter)adapter.getWrappedAdapter();
if (adapter != null) {
    wrappedAdapter.notifyDataSetChanged();
}


Android 端如何添加自定义表情

IM即时通讯大兴 发表了文章 • 0 个评论 • 372 次浏览 • 2020-12-02 16:28 • 来自相关话题

实现步骤1.新建 RongEmoticonTab 类继承 IEmoticonTab 。public class RongEmoticonTab implements IEmoticonTab {  &... ...查看全部

微信截图_20201202162412.png

实现步骤

1.新建 RongEmoticonTab 类继承 IEmoticonTab 。

public class RongEmoticonTab implements IEmoticonTab {
    public RongEmoticonTab() {
    }
    @Override
    public Drawable obtainTabDrawable(final Context context) {
        return context.getResources().getDrawable(R.drawable.u1f603);
    }
    @Override
    public View obtainTabPager(Context context) {
        return view;
    }
    @Override
    public void onTableSelected(int i) {
    }
}

2.在 obtainTabPager 中添加您想要展示在表情面板上的 view 。

 @Override
    public View obtainTabPager(Context context) {
        View view = LayoutInflater.from(context).inflate(R.layout.view_emoji, null);
        RecyclerView rv = view.findViewById(R.id.recycler_view);
        //LinearLayoutManager是用来做列表布局,也就是单列的列表
        GridLayoutManager mLayoutManager = new GridLayoutManager(context, 5, OrientationHelper.VERTICAL, false);
        rv.setLayoutManager(mLayoutManager);
        //谷歌提供了一个默认的item删除添加的动画
        rv.setItemAnimator(new DefaultItemAnimator());
        rv.setHasFixedSize(true);
        //模拟列表数据
        ArrayList newsList = new ArrayList<>();
        TypedArray array = context.getResources().obtainTypedArray(context.getResources().getIdentifier("rc_emoji_res", "array", context.getPackageName()));
        int i = -1;
        while (++i < array.length()) {
            newsList.add(array.getResourceId(i, -1));
        }
        rv.setAdapter(new NewsAdapter(newsList));
        return view;
    }

3.Adapter 和布局文件可以参考GitHub
4.自定义一个 ExtensionModule 继承自 DefaultExtensionModule,复写其中的 getEmoticonTabs() 方法,返回需要展示的 EmoticonTab 列表。

public class MyExtensionModule extends DefaultExtensionModule {
    private RongEmoticonTab rongEmoticon;
    @Override
    public List<IEmoticonTab> getEmoticonTabs() {
        List<IEmoticonTab> emoticonTabs =  super.getEmoticonTabs();
        RongEmoticonTab emojiTab=new RongEmoticonTab();
        emoticonTabs.add(myEmoticon);
        return emoticonTabs;
    }
}

5.在初始化之后,取消 SDK 默认的 ExtensionModule,注册自定义的 ExtensionModule, 如下:

public void setMyExtensionModule() {
    List<IExtensionModule> moduleList = RongExtensionManager.getInstance().getExtensionModules();
    IExtensionModule defaultModule = null;
    if (moduleList != null) {
        for (IExtensionModule module : moduleList) {
            if (module instanceof DefaultExtensionModule) {
                defaultModule = module;
                break;
            }
        }
        if (defaultModule != null) {
            RongExtensionManager.getInstance().unregisterExtensionModule(defaultModule);
            RongExtensionManager.getInstance().registerExtensionModule(new MyExtensionModule());
        }
    }
}

6.如果需要网络下载表情需要下载并持久化表情数据,需要在添加 EmoticonTab 前下载好表情数据。

融云即时通讯SDK集成 — 通知检查

IM即时通讯大兴 发表了文章 • 2 个评论 • 444 次浏览 • 2020-12-02 16:28 • 来自相关话题

背景:最近公司新上的app要加上即时通讯的功能, 自己快速实现一个当然是不可能的了(项目deadline也顶不住哇).就从各家成熟的SDK厂商选来选去的, 各有各的好也各有各的不足.最后点兵点将,选了融云家的SDK(老板说了算hhhh).他家的官网和文档地址:... ...查看全部

微信截图_20201202162632.png背景:

最近公司新上的app要加上即时通讯的功能, 自己快速实现一个当然是不可能的了(项目deadline也顶不住哇).就从各家成熟的SDK厂商选来选去的, 各有各的好也各有各的不足.最后点兵点将,选了融云家的SDK(老板说了算hhhh).
他家的官网和文档地址:
官网:https://www.rongcloud.cn/
文档:https://docs.rongcloud.cn/v4
这个任务当然还是落在我的头上. 集成完毕后, 也踩了不少坑. 所以这篇文章给大家总结下排查融云消息的本地通知和远端推送的办法. 希望可以帮助到正在看这篇文章的你.

什么是本地通知:

当我的App接入了融云的即时通讯sdk后, 便拥有了即时通讯的能力. sdk与融云服务器建立长连接, 当消息发出后, 先走到融云的服务器, 再转发给相应的用户. 这里移动端到服务端, 服务端到移动端, 走的通道都是长连接. 无论你的app是在前台还是在后台, 只要没有被杀死, 那么长连接是一直在的. 所以消息可以即时的发送到达给接收者. 融云把这种走长连接到达的消息, 在通知栏展示的通知叫做本地通知. 也就是消息是顺利发送到接收者端了, 逻辑可以走到消息接收监听那里. 融云sdk内部实现了消息到达后的本地通知, 也赋予了开发者自行实现消息到达后进行本地通知的权利.

本地通知的检查:

这里我总结了一下接入融云sdk后, 关于本地通知接收不顺利的排查. 大致可以分为这么几条:

1.是否设置了 setOnReceiveMessageListener 监听, 并且 onReceived 方法返回的为 true。

 RongIM.setOnReceiveMessageListener(new RongIMClient.OnReceiveMessageListener() {
              @Override
              public boolean onReceived(Message message, 
                  return true;
              }
          });

如果 onReceived 方法返回值为 true 则是监听做了拦截, 则不会走通知逻辑。 2.发送的消息是否是自定义消息。 如果是自定义消息, 则请查看自定义消息的 MessageTag 的注解是否设置了 flag 的值为 MessageTag.ISCOUNTED 或 MessageTag.ISPERSISTED) 。 以下面为例。

@MessageTag(value = "RC:TxtMsg", flag = MessageTag.ISCOUNTED)
  @DestructionTag
  public class TextMessage extends MessageContent {
      ...
  }

如果没有设置其中之一,则不会走通知逻辑。

3.是否设置消息拦截器

设置代码如下:

/**
   * 设置接收消息时的拦截器
   *
   * @param messageInterceptor 拦截器
   */
  public void setMessageInterceptor(new RongIM.getInstance().MessageInterceptor() {
          @Override
          public boolean intercept(final Message message) {
                 return true;
          }
 });

如果设置了消息拦截器, 并且在 intercept() 方法中返回 true, 表示拦截此消息。 则不会走通知逻辑。

4.检查接收到的消息的 senderUserId 是否和当前用户的 id 相同。
如果 id 相同, 则不会走通知逻辑。 自己不可发给自己。

5.查看当前接收的消息是否是属于聊天室会话类型的
聊天室的消息目前不支持本地通知。

6.如果是自定义消息,请检查是否设置了自定义消息的 MessageProvider 。
如果没有,则不会走通知逻辑。

7.接收方本地是否有接收方的用户信息。 如果没有设置, 则不会走通知逻辑。

8.检查应用权限设置,看是否打开应用信任权限, 通知栏权限或者声音提示权限等。如果没有打开,请打开再试。

9.接收方没有发送方的用户信息,接收端不弹通知。用户信息通过用户信息提供者方式获取。
参考链接:https://docs.rongcloud.cn/v3/views/im/ui/guide/private/user/set/android.html

什么是远端推送:

集成了即时通讯的SDK, 我们的app不就能拥有像微信一样随时随地收到消息的即时通讯能力了? 说实话我一开始也是这么认为的. 可惜做开发也要按基本法来, Android平台回收app的这一关咱都过不了, app都给你杀死得透透的了你拿啥收消息呢? 咱又不是微信hhhh. 所以一番急赤白脸地阅读他家文档之后, 才发现app如果活着, 他融云能用自己的通道给你把消息推送到. 如果app被杀死了, 这个消息就在他家的服务端直接交给三方厂商了(也就是五大厂商蓝绿大厂华为小米FCM), 让这条消息走人家厂商的推送通道给送到你手机上.

远端推送的检查:

这里我总结了一下接入融云sdk后, 关于本地通知接收不顺利的排查. 大致可以分为这么几条:

  1. 退出应用的时候,只能调用融云的 disconnect() 方法,而不是 logout()。这样退出后融云才会启动push进程。

  2. push 进程的名字不能更改,必须是默认的名字,既 io.rong.push.
    (2.6.0dev之后的版本,此进程名字可以修改)

  3. 通过 ddms 或者终端里敲入 adb shell ps|grep rong 查看终端里是否存在 io.rong.push 这个进程。

有些手机厂家做了特殊限制,不允许第三方后台进程启动,所以融云的后台进程起不来,导致收不到 push 消息。这种情况可以换个手机测试(如三星,大部分三星手机没有做权限限制)。另外大部分国产手机,有权限设置的菜单,比如小米,华为等,可以手动去安全中心,设置应用的自启动权限,后台运行权限等,就可以收到Push消息了。

不过 vivo 和 oppo 有些型号的手机,一旦应用退到后台,系统会很快把它杀死,这种没有办法解决。目前市面上所有推送都存在这个问题,除非系统把该应用加入白名单。

  1. push 进程存在,仍然收不到 push 消息。

如果你的应用有消息免打扰功能,那么请确认当前登录账号之前是否设置过消息免打扰。如果不太确定,那最好去你的应用设定里重新设置下消息免打扰时间。 这里要注意的是: 如果这个账号之前在别的手机上设置过消息免打扰,换一台手机登录或者卸载重装的时候,融云服务端记录的仍然是之前设置的消息免打扰状态,所以这种情况下是收不到push消息的。


解决融云 SDK 4.0 版本配置 https 导航报 SSLHandshakeException

IM即时通讯大兴 发表了文章 • 0 个评论 • 375 次浏览 • 2020-12-02 16:28 • 来自相关话题

我们公司最近使用融云 IM 进行集成开发. 我们是私有云部署, 导航是通过接口进行设置的. 是 Https 的.之前我们是使用的2.x 版本, 使用下面接口配置一下即可.    RongIMClient.getInsta... ...查看全部

微信截图_20201202162800.png

我们公司最近使用融云 IM 进行集成开发. 我们是私有云部署, 导航是通过接口进行设置的. 是 Https 的.

之前我们是使用的2.x 版本, 使用下面接口配置一下即可.

    RongIMClient.getInstance().enableHttpsSelfCertificate(false);

但是最近我们升级融云到 4.0 版本. 突然发现连接不上了. 日志中报

javax.net.ssl.SSLHandshakeException: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.

这个错误.

于是咨询融云技术支持, 得知 4.0 需要自己进行证书验证.

需在 application 中添加即可.

 try {
    //使用X509TrustManager代替CertificateTrustManager跳过证书验证。
    TrustManager tm[] =  new X509TrustManager() {
                @Override
                public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
                }
                @Override
                public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
                }
                @Override
                public X509Certificate[] getAcceptedIssuers() {
                    return new X509Certificate[0];
                }
            }
        };
        SSLContext context = SSLContext.getInstance("TLS");   
        context.init(null, tm, null);
        SSLUtils.setSSLContext(context);
        SSLUtils.setHostnameVerifier(new HostnameVerifier() {
            @Override
            public boolean verify(String hostname, SSLSession session) {
                return true;
             }
            });
} catch (Throwable e) {
    throw new IllegalStateException(e);
}

注意的是, 由于导航请求是在融云的 ipc 进程中的, 所以在 application 中设置上面代码的时候, 千万不要设置在主进程中. 否则无效.

在融云 IMkit 会话界面基础上添加消息已读未读.

WebRTC大兴 发表了文章 • 0 个评论 • 442 次浏览 • 2020-11-06 15:37 • 来自相关话题

使用过融云的同学们可能知道. 融云 IMkit 的会话界面,  发送玩消息后, 如果对方已读, 发送端则会显示小对号的图片. 但是更具需求要把小对号改为已读未读. 接下来我们就一块实现这个功能.打开回执功能首先, 要确定打开融云的消息回执功能. 这个... ...查看全部

使用过融云的同学们可能知道. 融云 IMkit 的会话界面,  发送玩消息后, 如果对方已读, 发送端则会显示小对号的图片. 但是更具需求要把小对号改为已读未读. 接下来我们就一块实现这个功能.

打开回执功能

首先, 要确定打开融云的消息回执功能. 这个很简单, 就是在 rc_config.xml 中把下面属性配置为 true 即可.

<!-- 设置是否开启消息回执, true 为打开, false 为关闭--><bool name="rc_read_receipt">true</bool>

这样发送消息后, 对方已读, 发送端就会出现融云默认的小对号了.

自定义 Adapter

出现小对号后, 下一步就是要进行对融云适配器的改造了.

第一步
创建 CustomMessageListAdapter 继承 MessageListAdapter. 根据自己的需求复写 newView() 或者 bindView() 方法.

class CustomMessageListAdapter extends MessageListAdapter {    @Override    protected void bindView(View v, final int position, final UIMessage data) {        // 此方法中操作控件    }}

第二步

创建 CustomConversationFragment 继承于 ConversationFragment, 并复写父类中的 onResolveAdapter(), 返回 CustomMessageListAdapter 对象.

 class CustomMessageListAdapter extends ConversationFragment {        @Override        public MessageListAdapter onResolveAdapter() {            return new CustomMessageListAdapter();        }    }

第三步

使用 CustomConversationListFragment 代替 ConversationListFragment 进行配置使用即可.

u=2322698630,4022941591&fm=26&gp=0.jpg

添加已读未读

自定了了 Adapter, 我们就可以在原先逻辑的基础上进行扩展了. 现在主要的任务就是找到小对号的控件.

我们先看一下默认的 item 布局 rc_item_message.xml. 在布局中我们可找到下面的控件

    <TextView        android:id="@id/rc_read_receipt"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:layout_gravity="bottom"        android:layout_marginRight="4dp"        android:drawableLeft="@drawable/rc_read_receipt"        android:drawableStart="@drawable/rc_read_receipt"        android:textColor="@color/rc_read_receipt_status"        android:textSize="12sp"        android:visibility="gone" />

这个就是显示对号的控件了.  是一个 TextView 的控件, 由于我们是添加 “已读” 、“未读” 字符串, 正好可以直接使用这个 TextView 控件.

我们通过控件的 id 在 Adapter 中找到控件的名称.

class CustomMessageListAdapter extends MessageListAdapter {    @Override    protected void bindView(View v, final int position, final UIMessage data) {         super(v, position, data);       // 此方法中操作控件       final ViewHolder holder = (ViewHolder) v.getTag();       if (data.getMessageDirection() == Message.MessageDirection.SEND) {            if (readRec && data.getSentStatus() == Message.SentStatus.READ) {                if (data.getConversationType().equals(Conversation.ConversationType.PRIVATE) && tag.showReadState()) {                    holder.readReceipt.setVisibility(View.VISIBLE);                    holder.readReceipt.setText(已读);                    holder.readReceipt.setBackground(null);                } else {                      holder.readReceipt.setText(未读);                      holder.readReceipt.setBackground(null);                      holder.readReceipt.setVisibility(View.VISIBLE);            }        }    }}

这样就会显示就会显示已读未读的字样了.


融云集成之避坑指南-Android推送篇

WebRTC大兴 发表了文章 • 0 个评论 • 451 次浏览 • 2020-11-06 15:37 • 来自相关话题

在集成推送时候,需要先了解融云针对推送的定义: Push 通知是当接收方的主进程被杀死或者回收,或者您主动调用 disconnect() 的操作, 导致 IM 长连接通道与服务器断开后。 在这种情况下,会收到Push 服务;说白了,就是应用进程被杀掉,才会收到... ...查看全部

在集成推送时候,需要先了解融云针对推送的定义: Push 通知是当接收方的主进程被杀死或者回收,或者您主动调用 disconnect() 的操作, 导致 IM 长连接通道与服务器断开后。 在这种情况下,会收到Push 服务;

说白了,就是应用进程被杀掉,才会收到push 的,否则的话,是会走融云内部的长链接通道的;若是连应用进程都不了解的话,我要拿起我四十米的大刀了。
avatar

好了,前提介绍结束,接下来,我们正式开始避坑。

融云的推送分为俩大类:RongPush 以及其他厂商推送;至于为什么会有厂商推送 ,主要书由于众说周知的原因,各个手机厂商针对系统的room 太深,导致融云的push 进程服务无法自启动以及无法常存与服务后台,所以为了满足离线时候,push 的到达率,所以接入了主流的厂商推送服务,至于你说,为什么不接入统一推送联盟,估计等推送联盟孵化出来,可能得等到2045年了;哈哈,估计我再不开始介绍接入,大家就要发飙了。
avatar

各位客官,稍安勿躁,马上开始。本篇我们先介绍厂商推送之华为推送过程中遇到的坑;

首先,按照融云官网的描述,融云SDK 目前是有俩个版本,2.x 版本的SDK 以及4.x 版本的SDK ,分别对应的是华为的2.x 的jar 以及4.x 的jar ,可以分别参考 2.x版本 以及4.x版本,按照文档进行集成即可,具体集成过程按照文档集成即可,此处无需多说;

接下来,先介绍我在集成2.x 版本时候遇到的问题:

1.出现的问题一,没有填写对应的应用标识;(此处是我的锅,没有细致的看文档);

按照融云文档的描述,
avatar 此处要填写对应的华为的appkey 等数据的,而愚蠢的我竟然没有填写对应的 AppIDAppSecret,希望各位 大大在集成过程中不要跟我一样犯傻;

2.出现的问题二,按照文档配置完成了,结果,还是收不到推送,不会进行排查;

似乎这个问题,很多小伙伴都遇到了,所以,我在融云知识库中找到了一篇官方提供的排查手段,仅供大家参考 https://support.rongcloud.cn/ks/ODg0 ,若是大家还是搞不定,可以直接找融云进行技术支持的;

以上是在集成2.x 版本时候遇到的问题,不算特别困难,也是比较轻松的解决了;

但是,事情万万没有想到的那么简单,时间过去一个月,项目要升级4.x 的融云SDK ,于是,华为推送也要进行对应的升级,于是我开开心心准备认真参谋一下4.x文档,还好,文档比较简单;

于是,对应的问题,也出现了:

问题1:在升级过程中,并没有找见 agconnect-servics.json  文件的下载地址。

经过与融云技术人员的沟通,在华为提供5.0版本的推送服务之后,agconnect-servics.json  下载位置改变了,所以我将我知道的下载路径提供给大家,在华为开发者平台-我的项目中-对应的项目下载即可。

问题2: 由于我是从2.x 升级到4.x 的,所以,眼瞎的我没有看清楚文档中AndroidMainfest 中的配置是要删除的,所以导致找不到文件了,所以请大家一定要看清楚标题,是删除配置,不是添加配置;

问题3: 在EMUI 10以上 是可以收到推送的,但是在EMUI 10以下,收不到推送。

这个问题经过与融云技术人员以及华为技术人员的沟通可以确定,由于华为不再继续维护2.0推送服务,改推4.0 以上的服务,但是4.0的推送服务,又对HMSCore 是由要求的,所以只能升级用户的 HMSCore服务,不过融云对外提供了升级监听,具体可以参考融云文档


iOS 性能优化:优化 App 启动速度

IM即时通讯大神庵 发表了文章 • 0 个评论 • 152 次浏览 • 2020-10-26 12:00 • 来自相关话题

作者:Damonwong,iOS 开发者来源公众号丨老司机技术周报(ID:LSJCoding)Sessions: https://developer.apple.com/videos/play/wwdc2019/423/苹果是一家特别注重用户体验的公司,过去几... ...查看全部

作者:Damonwong,iOS 开发者

来源公众号丨老司机技术周报(ID:LSJCoding)

Sessions: https://developer.apple.com/videos/play/wwdc2019/423/

苹果是一家特别注重用户体验的公司,过去几年一直在优化 App 的启动时间,特别是去年的 WWDC 2019 keynote[1] 上提到,在过去一年苹果开发团队对启动时间提升了 200%

虽然说是提升了 200%,但是有些问题还是没有说清楚,比如:

  • 为什么优化了这么多时间?

  • 作为开发者的我们,我们还可以做哪些针对启动速度的优化?

所以我们今天结合 WWDC2019 - 423 - Optimizing App Launch[2] 聊一下和启动相关的东西

名词解释

先介绍一些和启动相关的名词。

Mach-O

Mach-O 是 iOS 系统不同运行时期可执行的文件的文件类型统称。主要分以下三类:

  • Executable - 可执行文件,是 App 中的主要二进制文件

  • Dylib - 动态库,在其他平台也叫 DSO 或者 DLL

  • Bundle - 苹果平台特有的类型,是无法被连接的 Dylib。只能在运行时通过 dlopen() 加载

Mach-O 的基本结构如下图所示,分为三个部分:

  • Header 包含了 Mach-O 文件的基本信息,如 CPU 架构,文件类型,加载指令数量等

  • Load Commands 是跟在 Header 后面的加载命令区,包含文件的组织架构和在虚拟内存中的布局方式,在调用的时候知道如何设置和加载二进制数据

  • Data 包含 Load Commands 中需要的各个 Segment 的数据。

绝大多数 Mach-O 文件包括以下三种 Segment

  • __TEXT - 代码段,包括头文件、代码和常量。只读不可修改。

  • __DATA - 数据段,包括全局变量, 静态变量等。可读可写。

  • __LINKEDIT - 如何加载程序, 包含了方法和变量的元数据(位置,偏移量),以及代码签名等信息。只读不可修改。

Image

指的是 Executable,Dylib 或者 Bundle 的一种。

Framework

有很多东西都叫做 Framework,但在本文中,Framework 指的是一个 dylib,它周围有一个特殊的目录结构来保存该 dylib 所需的文件。

虚拟内存(Virtual Memory)

虚拟内存是建立在物理内存和进程之间的中间层。是一个连续的逻辑地址空间,而且逻辑地址可以没有对应的实际物理内存地址,也可以让多个逻辑地址对应到一个物理内存地址上。

Page Fault

当进程访问一个没有对应物理地址的逻辑地址时,会发生 Page Fault

Lazy Reading

某个想要读取的页没有在内存中就会触发 Page Fault,系统通过调用 mmap() 函数读取指定页,这个过程叫做 Lazy Reading

COW(Copy-On-Write)

当进程需要对某一页内容进行修改时,内核会把需要修改的部分先复制一份,然后再修改,并把逻辑地址重新映射到新的物理内存去。这个过程叫做 Copy-On-Write

Dirty Page & Clean Page

Image 加载后,被修改过内容的 Page 叫做 Dirty Page,会包含着进程特定的信息。与之相对的叫 Clean Page,可以从磁盘重新生成。

共享内存(Share RAM)

当多个 Mach-O 都依赖同一个 Dylib(eg. UIKit)时,系统会让这几个 Mach-O 的调用 Dylib 的逻辑地址都指向同一块物理内存区域,从而实现内存共享。Dirty Page 为进程独有,不能被共享。

地址空间布局随机化(ASLR)

当 Image 加载到逻辑地址空间的时候,系统会利用 ASLR 技术,使得 Image 的起始地址总是随机的,以避免黑客通过起始地址+偏移量找到函数的地址

当系统利用 ASLR 分配了随机地址后,从 0 到该地址的整个区间会被标记为不可访问,意味着不可读,不可写,不可被执行。这个区域就是 __PAGEZERO 段,它的大小在 32 位系统是 4KB+,而在 64 位系统是 4GB+

代码签名(Code Sign)

代码签名可以让 iOS 系统确保要被加载的 Image 的安全性,用 Code Sign 设置签名时,每页内容都会生成一个单独的加密散列值,并存储到 __LINKEDIT 中去,系统在加载时会校验每页内容确保没有被篡改。

dyld(dynamic loader)

dyld 是 iOS 上的二进制加载器,用于加载 Image。有不少人认为 dyld 只负责加载应用依赖的所有动态链接库,这个理解是错误的。dyld 工作的具体流程如下:参考:dyld启动流程[3]

Load dylibs

dyld 在加载 Mach-O 之前会先解析 Header 和 Load Commands, 然后就知道了这个 Mach-O 所依赖的 dylibs,以此类推,通过递归的方式把全部需要的 dylib 都加载进来。

一般来说,一个 App 所依赖的 dylib 在 100 - 400 左右,其中大多数都是系统的 dylib,因为有缓存和共享的缘故,读取速度比较高。

Fix-ups

因为 ASLR 和 Code Sign 的原因,刚被加载进来的 dylib 都处于相对独立的状态,为了把它们绑定起来,需要经过一个 Fix-ups 过程。Fix-ups 主要有两种类型:Rebase 和 Bind。

PIC(Position Independent Code)

因为代码签名的原因,dyld 无法直接修改指令,但是为了实现在运行时可以 Fix-ups,在 code gen 时,通过动态 PIC(Position Independent Code)技术,使本来因为代码签名限制不能再修改的代码,可以被加载到间接地址上。当要调用一个方法时,会先在 __DATA 段中建立一个指针指向这个方法,再通过这个指针实现间接调用。

Rebase

Rebase 就是针对“因为 ASLR 导致 Mach-O 在加载到内存中是一个随机的首地址”这一个问题做一个数据修正的过程。会将内部指针地址都加上一个偏移量,偏移量的计算方法如下:

  Slide = actual_address - preferred_address

所有需要 Rebase 的指针信息已经被编码到 __LINKEDIT 里。然后就是不断重复地对 __DATA 中需要 Rebase 的指针加上这个偏移量。这个过程中可能会不断发生 Page Fault 和 COW,从而导致 I/0 的性能损耗问题,不过因为 Rebase 处理的是连续地址,所以内核会预先读取数据,减少 I/O 的消耗。

Binding

Binding 就是对调用的外部符号进行绑定的过程。比如我们要使用到 UITableView,即符号 _OBJC_CLASS_$_UITableView, 但这个符号又不在 Mach-O 中,需要从 UIKit.framework 中获取,因此需要通过 Binding 把这个对应关系绑定到一起。

在运行时,dyld 需要找到符号名对应的实现。而这需要很多计算,包括去符号表里找。找到后就会将对应的值记录到 __DATA 的那个指针里。Binding 的计算量虽然比 Rebasing 更多,但实际需要的 I/O 操作很少,因为之前 Rebasing 已经做过了。

dyld2 & dyld3

 

在 iOS 13 之前,所有的第三方 App 都是通过 dyld 2 来启动 App 的,主要过程如下:

  • 解析 Mach-O 的 Header 和 Load Commands,找到其依赖的库,并递归找到所有依赖的库

  • 加载 Mach-O 文件

  • 进行符号查找

  • 绑定和变基

  • 运行初始化程序

上面的所有过程都发生在 App 启动时,包含了大量的计算和I/O,所以苹果开发团队为了加快启动速度,在 WWDC2017 - 413 - App Startup Time: Past, Present, and Future[4] 上正式提出了 dyld3。

dyld3 被分为了三个组件:

  • 一个进程外的 MachO 解析器


    • 预先处理了所有可能影响启动速度的 search path、@rpaths 和环境变量

    • 然后分析 Mach-O 的 Header 和依赖,并完成了所有符号查找的工作

    • 最后将这些结果创建成了一个启动闭包

    • 这是一个普通的 daemon 进程,可以使用通常的测试架构

  • 一个进程内的引擎,用来运行启动闭包


    • 这部分在进程中处理

    • 验证启动闭包的安全性,然后映射到 dylib 之中,再跳转到 main 函数

    • 不需要解析 Mach-O 的 Header 和依赖,也不需要符号查找。

  • 一个启动闭包缓存服务


    • 系统 App 的启动闭包被构建在一个 Shared Cache 中, 我们甚至不需要打开一个单独的文件

    • 对于第三方的 App,我们会在 App 安装或者升级的时候构建这个启动闭包。

    • 在 iOS、tvOS、watchOS中,这这一切都是 App 启动之前完成的。在 macOS 上,由于有 Side Load App,进程内引擎会在首次启动的时候启动一个 daemon 进程,之后就可以使用启动闭包启动了。

dyld 3 把很多耗时的查找、计算和 I/O 的事前都预先处理好了,这使得启动速度有了很大的提升。

App 启动

介绍完上面这一大堆名词之后,我们正式进入主题。

App 启动为什么这么重要?

  • App 启动是和用户的第一个交互过程,所以要尽量缩短这个过程的时间,给用户一个良好的第一印象

  • 启动代表了你的代码的整体性能,如果启动的性能不好,其他部分的性能可能也不会太好

  • 启动会占用 CPU 和内存,从而影响系统性能和电池

所以我们要好好优化启动时间。

启动类型

App 的启动类型分为三类

  • Cold Launch 也就是冷启动,冷启动需要满足以下几个条件:


    • 重启之后

    • App 不在内存中

    • 没有相关的进程存在

  • Warm Launch 也就是热启动,热启动需要满足以下几个条件:


    • App 刚被终止

    • App 还没完全从内存中移除

    • 没有相关的进程存在

  • Resume Launch 指的是被挂起的 App 继续的过程,需要满足以下几个条件:


    • App 被挂起

    • App 还全部都在内存中

    • 还存在相关的进程

App 启动阶段

App 启动分为三个阶段

  • 初始化 App 的准备工作

  • 绘制第一帧 App 的准备工作及绘制(这里的第一帧并不是获取到数据之后的第一帧,可以是一张占位视图),这时候用户与App已经可以交互了,比如 tabbar 切换

  • 获取到页面的所有数据之后的完整的绘制第一帧页面

在这个地方,苹果再次强调了一下,建议「用户从点击 App 图标到可以再次交互,也就是第二阶段结束」的时间最好在 400ms 以内。目前来看,大部分 App 都没有达到这个目标。

下面,我们把上面三个阶段分成下面这 6 个部分,讲一下这几个阶段做了什么以及有什么可以优化的地方。

 

System Interface

初始化 App 的准备工作,系统主要做了两个事情:Load dylibs 和 libSystem init

在 2017 年苹果介绍过 dyld3 给系统 App 带来了多少优化,今年 dyld3 正式开发给开发者使用,这意味着 iOS 系统会将你热启动的运行时依赖给缓存起来。以达到减少启动时间的目的。这也就是提升 200% 的原因之一。

视频中只说优化了热启动时间,理论上对于 iOS 系统来说 dyld3 应该还可以优化冷启动时间,所以不知道是因为给 iPad 增加了多任务功能的原因,还是没有把所有功能开放的原因,作者只提了热启动这个原因暂时还不太清楚。

除此之外,在 Load dylibs 阶段,开发者还可以做以下优化:

  • 避免链接无用的 frameworks,在 Xcode 中检查一下项目中的「Linked Frameworks and Librares」部分是否有无用的链接。

  • 避免在启动时加载动态库,将项目的 Pods 以静态编译的方式打包,尤其是 Swift 项目,这地方的时间损耗是很大的。

  • 硬链接你的依赖项,这里做了缓存优化。

也许有人会困惑是不是使用了 dyld3 了,我们就不需要做 Static Link 了,其实还是需要的,感兴趣的可以看一下 Static linking vs dyld3[5] 这篇文章,里面有一个详细的数据对比。

libSystem init 部分,主要是加载一些优先级比较低的系统组件,这部分时间是一个固定的成本,所以我们开发人员不需要关心。

Static Runtime Initializaiton

这个阶段主要是 Objective-C 和 Swift Runtime 的初始化,会调用所有的 +load 方法,将类的信息注册到 runtime 中。

在这个阶段,原则上不建议开发者做任何事情,所以为了避免一些启动时间的损耗,你可以做以下几个事情:

  • 在 framework 开发时,公开专有的初始化 API

  • 减少在 +load 中做的事情

  • 使用 initialize 进行懒加载初始化工作

UIKit Initializaiton

这个阶段主要做了两个事情:

  • 实例化 UIApplication 和 UIApplicationDelegate

  • 开始事件处理和系统集成

所以这个阶段的优化也比较简单,你需要做两个事情:

  • 最大限度的减少 UIApplication 子类初始化时候的工作,更甚至与不子类化 UIApplication

  • 减少 UIApplicationDelegate 的初始化工作

Application Initializaiton

这个阶段主要是生命周期方法的回调,也正是开发者最熟悉的部分。

调用 UIApplicationDelegate 的 App 生命周期方法:

  application:willFinishLaunchingWithOptions: 
  application:didFinishLaunchingWithOptions:

和 UIApplicationDelegate 的 UI 生命周期方法:

  applicationDidBecomeActive:

同时,iOS 13 针对 UISceneDelegate 增加了新的回调:

  scene:willConnectToSession:options:
  sceneWillEnterForeground:
  sceneDidBecomeActive:

也会在这个阶段调用。感兴趣的可以关注一下 Getting the Most out of Multitasking 这个 Session,暂时没有视频资源,怀疑是现场演示翻车了,所以没有把视频资源放出来。

在这个阶段,开发者可以做的优化:

  • 推迟和启动时无关的工作

  • Senens 之间共享资源

Fisrt Frame Render

这个阶段主要做了创建、布局和绘制视图的工作,并把准备好的第一帧提交给渲染层渲染。会频繁调用以下几个函数:

 loadView
 viewDidLoad 
 layoutSubviews

在这个阶段,开发者可以做的优化:

  • 减少视图层级,懒加载一些不需要的视图

  • 优化布局,减少约束

更多细节可以从 WWDC2018 - 220 - High Performance Auto Layout[6] 中了解

Extend

大部分 App 都会通过异步的方式获取数据,并最终呈现给用户。我们把这一部分称为 Extend。

因为这一部分每个 App 的表现都不一样,所以苹果建议开发者使用 os_signpost 进行测量然后慢慢分析慢慢优化。

测量 App 启动时间

要找到启动过程中的问题,就要进行多次测量并前后比较。但是如果变量没有控制好,就会导致误差。

所以为了保证测量的数据能够真实的反应问题,我们要减少不稳定性因素,保证在可控的相近的环境下进行测量。最后使用一致的结果来分析。

条件一致性

为了保证环境一致,我们可以做下面这几个事情:

  • 重启手机,并等待 2-3 分钟

  • 启用飞行模式或者使用模拟网络

  • 不使用或者不变更 iCloud 的账户

  • 使用 release 模式进行 build

  • 测量热启动时间

iColud 账户切换会影响性能,所以不要切换账号或者不开启 iCloud。

测量注意点

  • 尽可能的使用具有代表性的数据进行测试

    如果不使用具有代表性的数据进行测试,就会出现偏差

  • 使用不同的新旧设备进行测试

  • 最后你还可以使用 XCTest 来测试,多运行几次,取平均结果

关于使用 XCTest 测试启动时间的信息,可以看一下 WWDC2019 - 417 - Improving Battery Life and Performance[7],但是我测试了一下,目前好像还有一部分 API 还没有开放出来,暂时还不能使用。

使用 Instruments 分析和优化 App 启动过程

优化方式

苹果给了我们三个优化方式的建议,整体思想和前面提到的各个阶段的优化差不多

Minimize Work

  • 推迟与第一帧无关的工作

  • 从主线程移开阻塞工作

  • 减少内存使用量

Prioritize Work

  • 定义好任务的优先级。

  • 利用好 GCD 来优化你的启动速度。

  • 让重要的事情保持优先

更深入的了解有关 GCD 的内容,可以看一下 WWDC2017 - 706 - Modernizing Grand Central Dispatch Usage[8]

Optimize Work

  • 简化现有工作,比如只请求必要的数据。

  • 优化算法和数据结构

  • 缓存资源和计算

使用 Instruments 分析 App 启动过程

当知道如何优化之后,我们需要针对我们的启动过程进行分析。Xcode 11 的 Instruments 为此新增了一个 App launch 模板,让开发者可以更好的分析自己 App 的启动速度。

 

运行后可以看到各个阶段的具体时间,根据数据进行优化,还能看到耗时的函数调用。

 

系统优化

去年苹果做了很多优化,下面这几个高亮的是和启动速度有关的优化

 

但是不知道是不是时间原因,在 session 中对于这部分的解释特别少,很难理解 200% 到底做了什么。

但是 Craig Federighi 在 The Talk Show Live From WWDC 2019, With Craig Federighi and Greg Joswiak[9] 中针对为什么优化了 200% 说了这样一段话:

Isn’t that crazy that was quite a discovery for us. No it turns out that over times as in terms of the way the apps were encrypted and the way fair play worked and so forth. The encryption became part of the critical path actually of launching the apps. I mean the processors are capable or up and through the thing that actually it was a problem. And then there are other optimizations that based on what was visible to system at certain things. And so it actually cut out optimization opportunities and so when we really identified that opportunity we said okay. We can actually come up with better format that’s gonna eliminate that being on the critical path, It’s going to enable all these pre-binding things. And then we did a whole bunch of other work to optimize the objective-c runtime to optimize the linker the dynamic linker a bunch of other things and you put it all together. And yeah that I mean a cold launch this is we’ve never had a win like this to launch time in a single release.

从这段话中,除了 dyld3 的功劳之外,减少对代码签名加密也是优化之一。

监控线上用户 App 的启动

Xcode 11 在 Xcode Organizer 新增了一个监控面板,在这个面板里面可以查看多个维度的用户数据,其中还包括平均启动时间。

 

当你通过 Instruments 分析完你的启动过程,并做了大量优化之后,你就可以通过 Xcode Organizer 来分析你这次优化效果到底怎么样。

当然你可以通过去年新出的 MetricKit[10] 获取一些自定义的数据,具体参照 WWDC2019 - 417 -Improving Battery Life and Performance[11]

总结

今年苹果提的很多优化建议其实在早几年都已经说过一遍了,属于老生常谈了。但是值得点赞的是新出的 Instruments 的 App launch 和 Xcode Organizer 的性能监控。

启动优化是一个需要经常反复做的事情,越早发现问题,越容易解决问题,如果你想给你的用户一个良好的第一印象,就赶快行动起来吧。

参考资料

[1]

WWDC 2019 keynote: https://developer.apple.com/videos/play/wwdc2019/101/

[2]

WWDC2019 - 423 - Optimizing App Launch: https://developer.apple.com/videos/play/wwdc2019/423/

[3]

dyld启动流程: https://leylfl.github.io/2018/05/28/dyld启动流程/

[4]

WWDC2017 - 413 - App Startup Time: Past, Present, and Future: https://developer.apple.com/videos/play/wwdc2017/413/

[5]

Static linking vs dyld3: https://allegro.tech/2018/05/Static-linking-vs-dyld3.html

[6]

WWDC2018 - 220 - High Performance Auto Layout: https://developer.apple.com/videos/play/wwdc2018/220/

[7]

WWDC2019 - 417 - Improving Battery Life and Performance: https://developer.apple.com/videos/play/wwdc2019/417/

[8]

WWDC2017 - 706 - Modernizing Grand Central Dispatch Usage: https://developer.apple.com/videos/play/wwdc2017/706/

[9]

The Talk Show Live From WWDC 2019, With Craig Federighi and Greg Joswiak: https://daringfireball.net/2019/06/the_talk_show_live_from_wwdc_2019

[10]

MetricKit: https://developer.apple.com/documentation/metrickit

[11]

WWDC2019 - 417 -Improving Battery Life and Performance: https://developer.apple.com/videos/play/wwdc2019/417/


一个APP如何适配多个Andiroid终端?

IM即时通讯大兴 发表了文章 • 0 个评论 • 85 次浏览 • 2020-10-22 17:45 • 来自相关话题

Android响应式方案响应式的核心是拉通多终端的适配规则,开发一套界面,一个APP兼容多尺寸终端设备的显示,能够根据用户的行为以及设备的环境(屏幕尺寸、屏幕方向、是否分屏等)进行相应的页面布局以及容器尺寸的调整,为用户提供更加舒适的界面和更好的用户体验。1&... ...查看全部


Android响应式方案

响应式的核心是拉通多终端的适配规则,开发一套界面,一个APP兼容多尺寸终端设备的显示,能够根据用户的行为以及设备的环境(屏幕尺寸、屏幕方向、是否分屏等)进行相应的页面布局以及容器尺寸的调整,为用户提供更加舒适的界面和更好的用户体验。

image.png

1  响应式SDK



App的每个页面支持响应式,开发成本是很高的。

响应式SDK,就是为了解决App在不同尺寸设备下的适配问题,把设备的屏幕信息、容器布局规则(列数、尺寸)、业务数据二次加工等行为进行统一管理,以适应新的屏幕尺寸。


image.png2  加载流程设计image.png

通用的页面加载流程,通常都是从数据返回开始,数据解析完成后,进行页面布局渲染以及容器布局渲染。响应式在通用加载流程的基础上,加入了响应式状态变化通知、响应式数据剪裁、响应式页面布局、响应式容器布局等流程。



具体加载的流程分为两种情况:


  • 用户请求数据

  • 屏幕尺寸发生变化


 

3  架构设计



优酷各个业务开发团队,使用了统一的业务架构,我们在统一架构的基础上进行响应式适配,提供了响应式SDK,拉通各个业务方不同页面的适配规则,确保了适配效果的一致性,同时提供了基础的响应式控件,降低业务方的接入成本,那么响应式架构具体是怎么实现的呢?


image.png

从结构上看,响应式由优酷统一架构、响应式SDK、响应式页面布局、响应式容器布局四部分相互配合完成。在这些基础上支撑了首页、频道页、播放页、会员页、搜索、个人中心等众多的业务场景。




优酷统一架构和响应式SDK,提供响应式架构能力。




响应式页面布局、响应式容器布局,提供响应式参考实现。




4  数据二次加工



响应式并不是简单的将现有Phone端的业务数据,投放到Pad、折叠屏上,单纯的进行UI页面适配。想要在不同尺寸设备上都能获得良好的适配效果,需要对Phone端的业务数据二次加工,进行数据过滤、数据映射、数据合并、数据补全等操作,才能更好的适配Pad和折叠屏。

响应式SDK只是负责把数据二次加工的协议规则定下来,具体的数据二次加工逻辑需要业务方自己实现。优酷的统一架构提供了数据切面的能力,在切面上增加数据二次处理的逻辑,实现了统一的数据处理。


数据过滤



大尺寸设备上,总会遇到一些复杂的,适配不了的,也不重要的组件,这部分组件可以根据具体情况过滤处理,例如:下图中的weex组件,在Pad上直接过滤掉,不显示。


image.png

数据映射



存在一些带交互的复杂组件或者Pad上适配效果较差的组件,可以直接映射成其他已适配的组件。例如:下图中的带视频预览的预约组件映射成普通的预约组件。


image.png

数据合并



相邻的两个组件,其中有一个组件无法很好的适配大尺寸Pad,可以尝试将其数据合并到其他组件内。

例如:下图中第1个组件宽度铺满页面宽度,在大尺寸上无法适配,第2个组件通过修改列数、尺寸就可以适配。Pad竖屏下,将第一个组件插入到第二个组件的首位,进行数据合并,按照第二个组件的进行适配,显示为3列2行,达到很好的适配效果。


image.png

数据补全



在横竖屏切换过程中,部分组件会遇到组件的数量,无法铺满屏幕的宽度,导致出现留白的问题。

例如:把手机上的6条数据,直接投放到Pad横屏下,就会出现下图的留白问题:


image.png

为了解决这一类数据缺失的问题,我们选择的解法是服务端多下发一部分业务数据,客户端根据具体的屏幕尺寸,动态调整显示的个数,确保显示效果。



例如:下图中手机上显示2列3行,共6条数据,到了Pad竖屏上显示3列2行,共6条数据,到了Pad横屏上会补全2条数据,显示4列2行,共8条数据。


image.png

5  页面响应式



响应式状态

响应式状态是页面响应式最基础也是最重要的一个能力,像横竖屏切换、分屏模式、折叠屏折叠打开,都会导致页面的宽高发生变化,产生不同的响应式状态,页面内的内容会进行重新布局以及组件尺寸调整,以适应页面尺寸的变化,铺满屏幕,达到更好的显示效果。

横竖屏切换:


image.png分屏模式

image.png

折叠屏:

image.png



获取响应式状态



响应式状态的定义,需要有一个具体计算的规则,在所有尺寸的设备上都按照统一的规则进行状态区分,那么不同的响应式状态是如何区分的呢?

首先定义标准手机屏幕的物理宽度为400dp(经过大量手机设备调试采样之后获得的手机标准物理尺寸经验值),那么响应式状态的变化,由两个比例阈值决定,一个是页面物理宽度与标准物理宽度的比例阈值1.67倍(物理宽度 = 像素宽度÷屏幕密度),另一个是页面高度与页面宽度的比例阈值1.25倍。那么这两个比例阈值是如何得来的呢?

(1)1.67倍是怎么来的呢?


image.png

在播放页的适配过程中,需要适配左右分栏的显示,我们认为左侧播放器的宽度是标准物理宽度,那么整个页面的宽度就是标准物理宽度的1.67倍,这样左侧播放器有足够的空间保障视频播放的体验,右侧的也有足够的空间保障评论的显示效果。


 
(2)1.25倍是怎么来的呢?


image.png

上图列举了竖屏华为Pad上,页面高度是页面宽度的1.6倍,播放器下方的视频内容操作区,显示的视频内容是足够多的。如果页面高度小于页面宽度的1.25倍,就会挤压视频内容操作区的高度,导致显示出来的视频内容过少,影响用户体验。




当页面物理宽度大于标准物理宽度的1.67倍,同时页面高度小于等于页面宽度的1.25倍,即为大屏状态,其他情况则为小屏状态。
 
不同的响应式状态

目前支持了小屏布局和大屏布局两种状态。
 
(1)小屏布局状态

折叠屏折叠、折叠屏分屏、Pad竖屏:


image.png

image.png

image.png

(2)大屏布局状态



折叠屏打开、Pad横屏:


image.png

6  容器响应式



容器响应式,主要解决在页面尺寸发生变化时,动态调整容器布局的列数以及坑位的尺寸,优酷统一架构提供了常用的响应式容器布局:轮播布局、网格布局、横划布局、瀑布流布局。业务方可以快速实现响应式的效果。


容器适配列数、尺寸的效果

image.png

列数适配


同一个容器,在不同的尺寸页面下,会根据页面的物理宽度动态适配,显示为不同的列数。

网络布局、横划布局、瀑布流布局都采用这一套列数适配的规则:

响应式适配后的列数 = 当前屏幕宽度÷(标准屏幕宽度÷标准屏幕宽度下的组件列数 )

响应式适配后的列数,并不能解决Pad横屏上部分组件列数过多,显示过密的问题,为了解决这类问题,提供了列数二次适配的能力。

如下图所示,左侧是直接根据规则算出来的Pad横屏下的列数8列,过于密集,显示效果不好,右侧是列数二次调整后,显示为6列。

image.png

适配效果:image.png

控件尺寸适配


由于不同屏幕尺寸下,容器内部会动态调整显示不同的列数,导致控件的尺寸也会发生变化,那么如何适配控件尺寸的动态变化呢,响应式基础控件能够很好的解决这一类问题。
<span style=&qu

30万+App都在用的服务商,有什么特别之处?

科技创新融云那些事 发表了文章 • 0 个评论 • 125 次浏览 • 2020-08-03 16:57 • 来自相关话题

两个月前,一款名为 Clubhouse 的 App 开始流行于美国 VC、名人圈层,并且一直热度不减。尽管它的用户只有几千人,但其估值却已超过 1 亿美元。Clubhouse 是一款语音社交软件,被誉为是“音频 Twitter”。打开 Clubhouse,选一... ...查看全部

两个月前,一款名为 Clubhouse 的 App 开始流行于美国 VC、名人圈层,并且一直热度不减。尽管它的用户只有几千人,但其估值却已超过 1 亿美元。

Clubhouse 是一款语音社交软件,被誉为是“音频 Twitter”。

讯飞1.webp.jpg

打开 Clubhouse,选一个房间进入,人们可以查看房间里都有哪些参与者,并可以听到他们的谈话,再决定是否继续听或直接加入群聊。

如果要说它最大的规则是什么,那就是:只用语音,没有文字

近年来,移动互联网飞速发展,语音功能已开始成为 App 的标配,各种属性的音频类 App 尤其是声音社交类 App 也在悄然壮大。

红杉中国发布的《创造未来——红杉 00 后泛娱乐消费研究报告》显示,社交性、潮流性和个性化是“00 后”用户最看重的三大产品特征。因此,比图片和文字有温度,比视频更含蓄的声音社交方式,开始在他们之中风靡。

而根据艾瑞咨询的数据,2019 年中国网络音频行业市场规模为 175.8 亿元,同比增长 55.1%,预计 2020 年中国网络音频行业市场规模达 272.4 亿元。越来越多的资本和资源看到了声音背后蕴藏的更多可能性。

讯飞2.png

随着 5G 网络的逐渐普及,网络速度变得更快,以音频、视频为主要交互方式的互联网产品或将大行其道。

今年年初的疫情期间,我们已经看到各种音视频软件在在线教育、在线办公中发挥着巨大的作用。但有一个不得不面对的事实是,它们都曾因短时期内使用人数过多而崩溃。

对于音视频类互联网产品来说,保证用户使用过程中不会出现卡顿、声音效果差等问题十分重要,这就需要技术上的强力支持与保证,在这一方面能够提供“实时音视频”技术的服务商做的相当专业。

目前,在我国从事“实时音视频”技术底层搭建的服务商主要分为两大阵营。其一,是只专注“实时音视频”技术的单一产业服务商;其二,是 IM 领域已经耕耘多年,现将 IM 即时通讯及实时音视频两大业务版块交融聚合的服务商。例如,已多年稳居 IM 市场占有率第一的融云。

讯飞3.webp.jpg

北京云中融信网络科技有限公司(简称融云),是一家安全、可靠的全球互联网通信云服务商,向开发者和企业提供即时通讯和实时音视频通信云服务。虽成立时间不长,但发展迅速,成立不久就获得了 1 亿元人民币 B 轮融资。

截止目前,已有中原地产、汽车之家、融创地产、丽兹行、寺库、哈啰出行、核桃编程、易车网、编程猫以及 Castbox、Opera 等超过 30 万款海内外 App 通过融云实现了全球化的互联网通信能力。

融云现已在科大讯飞旗下人工智能全产业链综合服务平台——讯飞 AI 服务市场入驻,来一起看看,它的哪些技术能让如此之多的 App “钟情”于它?

融云IM即时通讯

许多 85 后甚至 90 后的大学记忆中都有一款即时通讯工具,飞信。在那个短信 1 毛钱一条,流量 5 块钱只有 30M 的年代,飞信免流量、免费发短信的功能,很有吸引力。作为当初中国移动即时通讯的扛鼎之作,飞信注册用户最高时达到了 5 亿,高峰时拥有高达 9000 万的活跃用户,其技术水平自不必多说。

融云的技术团队源自飞信技术团队,在即时通讯领域有着丰富的经验和强大的核心技术实力。

融云即时通讯支持多聊天模式、多消息类型、自定义界面等功能,同时支持聊天记录漫游、消息回执与召回、消息内容全文搜索、消息内容审核等功能。用户可以根据自己的需求调用相关接口,大大节约在通信能力上的研发时间和成本。

讯飞7.png

基于融云私有通信协议,可以保障消息不丢不重不乱,支持无上限用户数的群组和聊天室互动,公有云平台 150 亿条日均消息量2218 亿条日消息量峰值。融云建立了全球多数据中心,服务覆盖全球所有国家及地区(共 233 个),实时监控全球网络,基于融云分布在全球的数据中心与节点建设,向客户提供链路接入方案,持续的海外链路优化让消息发送稳定可靠,畅通无阻。

讯飞8.webp.jpg

无论是私密社交、兴趣社交还是商业沟通、系统消息等场景,融云都可以轻松应对,它也是业内唯一承诺消息可靠性 100% 的厂商。

融云RTC实时音视频

为了适应移动互联网发展和市场需求,融云在即时通讯之外还发展了实时音视频业务。融云实时音视频主要包括音视频通话、低延迟直播、音视频会议、云端录制等功能。用户可以根据自己的需求调用相关接口,大大节约在通信能力上的研发时间。

讯飞9.png

融云实时音视频具备低延迟、低成本、高流畅、高品质、部署简单、扩展灵活等技术优势。在整体网络架构上,全球分布式架构的部署让扩容时间大幅缩短,能轻松应对海量流量的激增。弱网优化策略方面,增强了抗丢包及抗网络抖动能力,音频能对抗 80% 丢包,视频能对抗 40% 丢包,延时最低可达 66ms,以保证低成本输出高性能的实时音视频能力(可以提供最高 1080P 的高清画质和最高 48KHz 的音频采样率) 。经过海量客户业务验证,融云实时音视频业务在稳定性、连通性、并发/负载等方面服务可用性达到 99.9%

融云在疫情期间还免费开放了在线医疗、在线教育及协同办公场景的通信能力,并开发了 VR 看房等新的业务场景。

最新活动推荐:

 融云年中大促活动海报.png


印度宣布封杀59个中国App,透露了哪些信息?

IM即时通讯大兴 发表了文章 • 0 个评论 • 111 次浏览 • 2020-07-02 11:27 • 来自相关话题

6月29日,印度政府信息技术部宣布,以 “有损印度主权和完整、印度国防、国家安全和公共秩序”为由,宣布禁止59款来自中国的应用程序,这些应用包括抖音海外版Tik Tok、微信、微博、百度地图、UC浏览器、快手海外版Kwai、QQ等,59款App均来自中国公司。... ...查看全部

6月29日,印度政府信息技术部宣布,以 “有损印度主权和完整、印度国防、国家安全和公共秩序”为由,宣布禁止59款来自中国的应用程序,这些应用包括抖音海外版Tik Tok、微信、微博、百度地图、UC浏览器、快手海外版Kwai、QQ等,59款App均来自中国公司。

截至《财经》记者发稿时(6月30日下午),Helo和Tik Tok已经在印度各大应用商店下架,包括Vigo(火山视频海外版)在内的其他一些应用暂时还可以下载和使用。印度官方公告中也并未说明会用哪种方式禁止这些App以及具体的禁止时间。此外,印度政府此番对中国互联网公司的限制尚未波及来自中国的资本,如有中资背景的印度支付应用Paytm以及外卖软件Zomato等均尚未出现在禁令上。

一位印度高级官员在匿名接受印度《经济时报》采访时表示,这些被禁止的中国App已存在很长时间并存在隐私和安全问题。他还称,印度政府在做出这项决定之前已考虑了所有方面。

针对印度近日宣布封杀微信抖音等59款中国App,中国外交部发言人赵立坚6月30日回应称,中方对印方有关公告表示强烈关注,正在了解核实情况,中国政府一向要求中国企业在遵守国际规则和当地法律法规的基础上开展对外合作,印度政府有责任根据市场原则维护包括中国企业在内的国际投资者的合法权益。中印两国在务实领域的合作是互利共赢的,这种合作格局受到人为损害,实际上并不符合印方自己的利益。

腾讯集团相关负责人向《财经》记者表示,针对这一事件不予评价和回应。截至《财经》记者发稿,字节跳动和百度等相关公司也没有给予《财经》记者正式回应。

资深科技行业从业者和观察家李军向《财经》记者分析,印度政府对中国App的限制措施可能包括但不仅限于:在用户购买手机时不再预装,印度本地的应用商店下架这些App,用户只能通过转换其他国家的账号来进行下载,他认为,大部分用户会卡在应用商店下载环节。

名单上的59个中国App可以分为三类,一类是在中国已经有市场基础,复制到印度市场,如微信、微博等;第二类为中国公司针对海外市场推出的孪生产品,如抖音的海外版Tik Tok;第三类是依靠印度市场成长起来的中国公司,例如跨境电商公司Club Factory,目前已经是印度第三大电商平台,仅次于亚马逊和Flipkart。

090807184205.jpg

随着技术的进步,信息安全对于国家安全的影响等级一再提升。此前欧盟多次指出互联网公司的数据安全隐患。几乎所有公司在面临类似指控时,都会强调对数据安全的重视,比如,该公司针对特定国家用户的所有数据都会保存在该国本地服务器中,不会被转移或滥用。

李军分析,海外市场的数据确实不太可能被公司转移,因为性价比太低,来自印度用户市场的数据对于中国公司的最大价值,可能是用于海外市场用户数据分析。

在全球互联网和科技领域,目前处于驱动者地位的是中国和美国,印度这样的新兴市场,市场空间庞大,但基础相对欠缺,缺资金,在技术、运营和平台搭建能力上也不够成熟,需要外部力量的支持,但同时也对全球开放了机会。在全球科技行业里,印度被认为是最大的,最具有想象力的新兴市场。

印度政府的禁令短期内对中国科技公司和资本都造成了不同程度的打击。长期来看,要想在这个可能有政策变数风险的国家继续开展业务,中国科技公司需要进一步调整姿势。

一、涉及至少6.5亿印度用户

当地时间6月29日深夜,大量和中国有业务联系的印度人,以及需要和印度合作方联系的中国人在微信朋友圈发出备选联系方式,避免由于微信被封杀导致的不利影响。

漏夜准备应对措施的不仅是用户。一位关注印度市场的投资人告诉《财经》记者,他们连夜整理研究了名单上的公司,计算出的数据是,这些App在印度的用户数量去重之后,有6.5亿。6.5亿用户背后,还有大量已经在这些App上投放广告的印度本地企业及他们的员工。

此次印度政府的封禁措施影响最大的可能是Tik Tok,印度是Tik Tok最大的市场,移动应用分析公司Sensor Tower数据显示,Tik Tok在印度总下载量超过5亿,占据Tik Tok总用户30%以上。

2019年9月,UC浏览器发布数据称,在全球拥有4.3亿多用户,印度是其主要市场之一,拥有1.3亿多用户。第三方数据分析公司StatCounter的数据显示,UC浏览器占据了印度移动浏览器市场逾23%的份额,仅次于谷歌Chrome。

字节跳动旗下的Helo是一个提供14种印度语言的社交媒体平台,2019年7月,Helo称拥有超过5000万月活跃用户。

今年6月,快手旗下Kwai已停止在印度运营,用户导入旗下的另一款短视频应用UVideo,目前该产品在Google Play商店的下载量已超过5000万。

前述不愿具名的投资人判断,这么庞大的用户体量,长时间全面封禁不太现实,而且,其中很多App目前在印度市场还没有成熟的替代品。对于科技公司来说,风险不会太大。

不过,从投资的角度来看,他表达了强烈的担心。他对《财经》记者说,这一举动对中国资本投资印度的信心将带来“毁灭性打击”,“谁都不敢去一个动不动就说要封了你的市场。”

近两年,中国资本十分热衷于投资印度市场。印度全球关系智库Gateway House今年3月发布报告显示,中国科技投资者对印度初创公司的投资额预计超过40亿美元。截至2020年3月,印度30家独角兽公司中,有18家背后有来自中国的投资者。

090807484041.jpg

印度独角兽公司背后的中国资方。数据来源:印度全球关系智库Gateway House

字节跳动一直试图将Tik Tok与中国公司分拆。今年5月19日,字节跳动宣布,任命前迪士尼高管凯文·梅耶尔(Kevin Mayer)为字节跳动首席运营官兼TikTok全球首席执行官,

“请Kevin来的主要目的之一就是为了打通Tik Tok的海外政府关系,独立运营。”

禁止名单中还包括3款游戏App,分别是北京智明星通旗下的《列王的纷争》、沐瞳科技旗下的《无尽对决》和一款小游戏社交App《Hago Play With New Friends》。不过,目前在印度最热门的游戏《PUBG MOBILE》(也称“吃鸡”,由腾讯发行)不在名单之中。第三方数据机构App Annie数据显示,《PUBG MOBILE》在印度市场的下载量超过1亿,是印度月活(MAU)最高的手游。

有资深行业人士向《财经》记者分析,没有将《PUBG MOBILE》列入禁止名单,可能是因为这款游戏的研发团队是韩国公司,腾讯只是发行方。另一种可能性是,这份禁令只是第一份,后续可能会有第二份、第三份……。有腾讯游戏匿名业务人士则认为,这可能是因为腾讯专门为这款游戏做了针对印度市场的合规。

中国社科院南亚研究中心副主任刘小雪长期关注印度经济和中国企业在印度投资,她对《财经》记者表示,印度在边境问题上理亏但又要安抚国内民众情绪,因此出台这样一个象征意义大于实际意义的禁令,说明印度不想对中国采取强硬措施。

不仅是手机App,印度政府最近还在拖延中国进口产品清关。从6月22日晚开始,印度钦奈港要对所有来自中国货物实施100%检查(此前是抽查),要求提供额外的清关材料。钦奈港是电讯部件和设备的重要港口,很多中国公司货物都是从该港口进入印度市场。

印度此举不仅损害中国企业利益,同时还引起美国企业的不满。苹果、思科和戴尔等美国企业的产品也受牵连,6月23日,代表美国企业的游说团体美印战略伙伴关系论坛(USISPF)致信印度商务部表示,印度是在向寻求可预测性和透明度的外国投资者发出令人担忧的信号。

今年4月,印度为了防外资抄底出台行政法规,要求对来自“接壤”国家的投资必须申请事先审核,此举被广泛认为针对中国。

在印度从事中国企业投资法务问题的印度大恒竺成(Linklegal)律师事务所顾问李钦认为,对很多中国投资者的项目产生了延误等不良影响,印度政府甚至指示其驻中国的使领馆对投资者进行面试,让不少中国投资者唏嘘。他对《财经》记者评价,印度作为一个1991年实施对外开放政策的新兴市场国家,其政府和民众对于外资的理解仍任重而道远。

二、中国公司如何规避长期风险?

2019年,一位中国投资人去印度考察,他回忆,在印度的一家咖啡馆里,至少坐着10个中国投资人,回想起那个场景,他说,“很像当年的中关村。”

软银集团CEO孙正义曾经提出“时光机”理论——发达市场验证过的机会和模式,会在新兴市场重新出现。一些中国创业者和投资人带着这样的信心,投入到印度市场中,认为自己获得了“上帝视角”。

但“上帝视角”也并非一帆风顺,2018年,一些创业公司试图在印度复制一个“今日头条”,很快就销声匿迹,印度不少手机用户的信息获取需求已经被Facebook满足,且少有广告商愿意在新闻聚合平台上做付费营销。

回到今天,Tik Tok、Facebook、Instagram、Youtube等已经验证过的模式,确实受到了印度用户的欢迎,但他们都面临一个难题——流量变现。

多位投资人向《财经》记者确认,印度市场虽然用户量很大,但整体付费意愿非常低,仅略高于非洲,很多公司在印度有不错的用户量,却赚不到钱。

但开辟印度市场依然有价值。印度作为一个拥有13.24亿居民的国家,印度统计局数据显示2019年以前,年均GDP增速在6%以上,2019年降至5%,还是一个正在高速增长的大市场,这很难得。此外,如果能拿下印度市场,也能更好地开辟其他新兴市场。

反观印度,印度政府也希望外部资本能够进入印度。国际货币基金组织(IMF)在6月24日发布最新《世界经济展望》报告中大幅下调对印度经济增长预期,目前的预期为收缩4.5%,此前的预期为增长1.9%。

就业率低迷是印度目前的一大问题,印度经济监测中心(CMIE)发布数据称,今年3月印度就业率为38.2%,为历史最低水平,失业率已经达到8.7%,为2016年9月以来最高水平。

目前,中印两国政府已经为边境摩擦降温展开磋商,事态正在向积极方向进展。但边境摩擦已经在印度国内转化成对中国产品的抵制。

一名在印度经营手机配件生产的人士对《财经》记者透露,出售中国手机的门店已经用Made in India 遮盖品牌logo。另一位在印度本地的中国手机公司人士也提到,很多门店虽然是本地经销商开的,也担心有人打砸。

有接受《财经》记者采访的相关人士预判,印度政府暂时不会对手机硬件行业做出禁令。包括小米、OPPO、vivo、Realme等在内中国手机厂商,已经占据了印度手机市场的70%以上的市场份额,目前也没有印度本土品牌可以替代。

而且,这些手机厂商都已经在印度建厂,组装环节基本都在本地工厂完成,带动了印度的就业率。

一位在印度的vivo人士告诉《财经》记者,中国手机厂商是最早进入印度的商业力量,因此,这几家手机厂商的本地化都做得较好,日常的业务人员都是印度人,出了问题都可以尽快找人解决。

这一点,相比刚刚进入印度市场不久,尚未来得及本土化的中国互联网公司来说,确实有一些优势。“App被禁止,可能都不知道找谁来解决问题。”

包括李军在内的受访者认为,中国公司的海外业务想要独立发展,至少要做到人员切割、数据切割、业务运营切割、IT系统切割,只留下财务联系,未来财务联系也要尽量弱化,引入当地战略投资者,共享利润,在政府关系上也能提供帮助。

李钦的观点是,鉴于中国基本的影响力,印度政府不太希望中国投资者在印度市场单干,而是采取和印度某些合作对象进行深度绑定的方式。以App等互联网产品为例,印度希望中国投资本国App。

对于实业,印度则希望中国找本土企业合资,深度绑定,减少中国投资者自主权,只有这样印度才放心。中国企业却很难接受,他们认为找到合适、靠谱的合作合资对象非常难。


作者为《财经》记者

本文来自微信公众号:财经十一人(ID:caijingEleven),作者:刘以秦、 王晓枫、 柳书琪、 陈潇潇 ,编辑:谢丽容  


APP