快速实现android版抖音主界面的心得

如何快速肯定竞品某个界面的实现方式?

当你收到产品一个需求是模仿某个竞品且时间很短没有过多时间给你调研技术方案的时候,如何尽快肯定这个功能的技术方案呢? 这里我给出我本身的一个小窍门,能够避免走弯路,好比先肯定的方案到最后发现各类各样的缘由方案不行,致使最后临时变动 方案,需求延期发布的悲剧。。。、react

  • Android\sdk\tools 首先去这个目录下 找到 monitor这个可执行文件,而后打开你想模仿的竞品页面,而后执行这个文件

比方说抖音的主界面这种滑动整屏的东西,相信以前不少人都没作过,不少人可能觉得recyclerview能够拿来作这种场景, 实际上recyclerview确实能够作,可是抖音这里能看出来倒是使用了viewpager来作。做为一个模仿者,为了少走弯路, 那么显然 咱们也是优先要选择viewpager的。android

  • 其次用这个软件,还能够看出来界面上的某个控件所属的id。这个id很重要,后面咱们反编译的时候能够经过全局 搜索这个id 找到大概的源码位置,虽然都是混淆过的代码,可是大体仍是能分析出一些技术细节的。 这里注意一点:不少阿里系的产品用这个方法你分析界面的时候你会发现根本没有id能够捕捉到,放心这并非什么 阿里的黑科技反 反编译技术,而是阿里使用了诸如weex react native等相似的方案,当使用这种方案的时候, 咱们是找不到id的噢,若是你大体了解weex react-native的实现方案的话你应该明白我在说些什么git

  • 反编译就用jadx就能够,足够使用。在用jadx反编译以后搜索你以前的id,就能够大概找到对应的代码位置,我就是经过 这个手段,肯定了抖音使用的垂直的viewpager究竟是出自于github上哪一个开源控件。。。为了不法律纠纷我这里就 不明说是用的哪一个开源的垂直滑动的viewpager,有兴趣的同窗能够本身玩一玩。github

  • adb shell dumpsys activity | grep mFocusedActivity 这个命令是神器,能够把你当前正在显示的activity的名字 打出来,方便你更加迅速的定位竞品的代码。。shell

不要小看这条命令哦,不少功能当你不知道竞品是弹了一个popwindow仍是一个dialoagfragment仍是启动了一个透明的activity的时候,这个方法就颇有用了,能够避免走不少弯路。
复制代码

总结一下模仿抖音主界面的时候遇到的一些问题和解决方案。

  • 抖音总体技术方案是 垂直的viewpager+下拉刷新上拉加载控件+fragment的组合方案。不论是前者仍是后者咱们都有成熟的开源方案能够选择,咱们只是把他合并起来而已。这第一步其实并不难。react-native

  • 尽可能使用FragmentStatePagerAdapter。FragmentPagerAdapter不推荐使用,可能平时咱们用FragmentPagerAdapter更多, 可是要知道FragmentPagerAdapter是不会释放内存的,你不可见的fragment也是常驻内存的,像抖音这种几乎无限滑动的 短视频方案,使用FragmentPagerAdapter确定是爆内存oom的。而用 FragmentStatePagerAdapter的话,对于不可见的 fragment,系统是自动释放内存的,内存里只会保留你能看到的fragment 和这个看到的fragment先后2个fragmnt。固然 这个值能够动态设置,可是至少都会保留3个fragment。数组

  • fragmentStatePagerAdapter.notifyDataSetChanged()失效? 有时候,notifyDataSetChanged方法调用了,界面却没有变化?实际上对于fragmentStatePagerAdapter来讲,界面刷新不刷新 重绘不重绘 主要取决于getItemPosition方法的返回值,默认是返回 POSITION_UNCHANGED 也就是不重绘。只有 返回POSITION_NONE的时候才会重绘界面,从新绘制一遍fragemnt。 不少人不太理解什么意思,我简单描述一下场景:bash

    好比说,咱们刚进抖音的主界面,假设返回了10条视频数据,咱们默认播第一条。展现的是第一个fragment,对吧。 而后这个时候咱们下拉刷新又来了10条数据,咱们应该播放的是新来的这10条数据的第一条。因而你等待接口返回 之后把新来的10条数据插在了咱们的数据源这个数组的 头部。 而后调用了notifyDataSetChanged这个方法。 若是你没有重写getItemPosition的方法的话,这个方法默认返回POSITION_UNCHANGED,这是fragmentStatePagerAdapter就认为 我这个界面不须要重绘。因此你仍是在不停的播放老的视频。 有人可能会问,咱们一直加载更多的话为何不会出现这种状况?weex

    比方说如今的加载更多其实都是预加载,好比咱们一页是返回10条数据,当咱们滑到第5条数据的时候 咱们可能就会自动请求 下一页数据了。因此咱们不停的滑动viewpager 日后面滑,由于position在不停的变化,因此是不断的有新的fragment进来的。 因此无论getItemPosition 的值如何变化,针对此种状况咱们都会刷新界面的。性能

    可是对于下拉刷新这种状况就不行,由于咱们默认加载的是第一条数据,咱们内存里面已经有了这一条数据了,对于position 位置为0的fragment来讲,他已经在内存里了,等咱们下拉刷新来了新数据之后,虽然咱们调用了notifyDataSetChanged方法, 可是咱们发现这个位置为0的fragment 内存里已经有了啊,getItemPosition又返回了POSITION_UNCHANGED,那我就不重绘了, 这就是一个容易出bug的地方。

  • 既然如此咱们getItemPosition固定返回POSITION_NONE行不行? 答案是不行的,固定返回POSITION_NONE这个值,虽然能够解决下拉刷新 界面不刷新的问题,可是会引起新的问题。 主要有2: 第一,固定返回POSITION_NONE 意味着每次notifyDataSetChanged 被调用的时候,咱们内存里存在的三个fragment 都要从新绘制 这样的成本太大,低端手机明显会卡,android大部分视频播放都是软解码方案,这样的性能不行。

    第二:仍是上面的预加载,比方说咱们第一页返回了10条数据,当咱们滑到第五条数据的时候,咱们预加载预先请求了第二页的时候 而后第二页的数据回来之后 咱们调用了notifyDataSetChanged,注意这个时候 咱们可能第五条数据对应的视频咱们还没看完呢,好比这条短视频咱们才看到第六秒,结果整个界面忽然重绘了,直接又从新 从第一秒开始播放。。这个体验明显不可接受。

    因此咱们要作的就是 在须要的时候返回POSITION_NONE 不须要的时候返回 POSITION_UNCHANGED ,具体的逻辑能够根据大家本身的业务进行相应的调整。

    比方说咱们能够判断一下,若是源数据也就是mData里面的id 和正在播放的fragmetn里面的id 相等话,咱们就断定不须要刷新界面 不然不相等,就刷一下,恰好对应加载更多和下拉刷新的2个场景。

  • 如何定位内存泄漏的问题? 对于播放器来讲,初次接触的团队若是没有经验,即便有b站开源播放器的帮助也会发生内存泄漏的问题,比方说咱们 绘制一个播放界面,总免不了要画进度条,要展现倒计时,textview默认的跑马灯效果那么差,说不定还要本身写个自定义view 来完成跑马灯的特效,这些都免不了使用线程,handler,timer等等容易发生内存泄漏的东西。因此当不停的滑动的时候, 若是被滑走的fragment没有及时被释放掉,那上线就确定会发生oom的问题。对于android studio 3.0或者以上的版原本说:

嗯,既然mat都能读了,剩下的就不啰嗦了,网上一搜一大堆。

  • 如何根据index 取对应的fragment? 这个也是比较小众的一个知识点,对于fragmentStatePagerAdapter来讲,咱们知道除了当前在使用的fragment,咱们 还有这个fragment先后2个fragment,对于滑动操做来讲,咱们至少要完成 滑动到下一个fragmetn 要中止前面一个fragment 的视频播放和动画播放等等。 因此根据index 来取fragment对象就变的十分重要。

这里给出反射的实现:

public static Fragment getIndexFragment(FragmentStatePagerAdapter fragmentStatePagerAdapter, int index) {
        try {
            Field privateArrayList = FragmentStatePagerAdapter.class.getDeclaredField("mFragments");
            privateArrayList.setAccessible(true);
            ArrayList<Fragment> mFragments = (ArrayList<Fragment>) privateArrayList.get(fragmentStatePagerAdapter);
            return mFragments.size() > 0 ? mFragments.get(index) : null;

        } catch (NoSuchFieldException e) {
        } catch (IllegalAccessException e) {
        }
        return null;
    }
复制代码
相关文章
相关标签/搜索