Android 视频播放自定义布局

-

在Android开发中,会遇到这样一个问题,需要在App中添加一个视频,但是功能又不需要很复杂,如果我们导入一个很大的视频库的话,会增加我们App的size,所以我们就需要自制一个简易的视频,并且可以自定义它的样式。
接下来就介绍一下怎样快速的自己写一个可自定义样式的视频。

VideoView(Android封装的播放器)

我们先看看Android自带的视频播放器,VideoView就是AndroidAPI中的自带视频播放器,其设计有以下几点:

  • VideoView继承SurfaceView
  • VideoView用的视频播放器是Android自带的MediaPlayer
  • 控制VideoView播放器视图的是MediaController,控制一些基本的操作的视图,比如快进,后退,播放占等播放状态

所以如果我们不需要自定义视图,且功能非常的简单,我们就直接可以使用VideoView播放器

在VideoView中,需要明白的是SurfaceView,MediaController和MediaPlayer各个标注位,SurfaceView是承接MediaPlayer的视图,MediaController是控制MediaPlayer的视图
所以我们自定义视频播放器的话,也可以按照这个思路来,播放器继承SurfaceView,在写一个控制视图。其实我在想为什么不设计一个小的库,然后我们直接写一个布局传进去,然后就能得到我们想要的播放的样式,这个难点
就是我们传进去的视图组件需要绑定监听啊,所以这点还是我现在无法想到解决办法的,所以我们可以实现一个播放视图,然后自己实现一个控制视图去控制它,所以我们只要把这个思路搞清楚其实就是码代码的事情和对一些逻辑
标志位的处理就行了,还有就是对Android各个自定义系统的适配问题了,因为视频这个问题很多坑。

逻辑结构

这里先整理一下这个视频的整个逻辑
先要知道的是几个逻辑点:

  • 连接wifi自动播放
  • 连接流量出现播放按钮,不自动播放
  • 无网状态:
    • 在开始加载状态突然无网,一直出现加载loading
    • 播放状态无网,播放到加载处,暂停,记录暂停位置,然后出现无网logo
    • 播放状态无网,拖动进度条。直接出现无网logo,暂停,记录暂停位置
  • 点击无网状态logo,判断是否连接网络,若有网络,直接加载,否则还显示无网logo
  • 加载loading的出现时机与消失时机,在开始时出现。在拖动进度条,若是没有被加载,则出现loading,否则不显示loading,API版本不同的适配
  • 前后台切换,对视频状态的保存和恢复,以及不同Android平台的适配,这个坑太多

以上大概就是视频播放的一些必要点,虽然看着这个逻辑不是很多,但是要把这些点串起来,并且去适配各种不同的平台和API还是有很多东西需要考虑的

上面这张图就是一个网络视频播放器的简单流程图。

代码结构

  • WMVideoGesture:手势操作类
  • IController:WMVideoController绑定Activity生命周期接口
  • ControllerChangeView:视图控件显示隐藏接口
  • WMMediaInterface:视频播放生命周期接口
  • WMVideoController:控制视频的视图,继承WMVideoGesture
  • WMVideoView:视频实现类,内部是MediaPlayer,继承SurfaceView,实现WMMediaInterface接口

逻辑细节梳理

Reduce APK Size

-

在开发Android工程中,要尽量减少APK的大小,提高性能

理解Apk的结构

  • META-INF/:包含CERT.SFCERT.RSA签名文件,还包括MANIFEST.MF的文件清单

  • assets/:直接打包进APK的资源,不会直接在R.java生成资源ID,可使用AssetManager来对它进行引用使用

  • res/:包含不编译成resources.arsc的资源

  • lib/:包含不同处理器的编译代码,包含各个平台类型,比如armeabi, armeabi-v7a, arm64-v8a, x86, x86_64, and mips.

    APK还包含以下几个部分,除了AndroidManifest.xml是必须的,其他都是不强制的

  • resources.arsc:包含所有配置的XML内容来自res/values/文件夹,通过工具获取XML内容,然后编译二进制文件,然后归档内容,内容包括字符串和样式,但是不包括内容的路径,例如布局文件和图片

  • classes.dex:包含通过Dalvik/ART虚拟机编译java源码生成的DEX文件

  • AndroidManifest.xml:包含核心的Android清单文件,此文件包含应用的名称,权限,版本,引用的库。此文件是二进制文件

减少资源文件的数量和大

APK的大小会影响APP的性能,如加载速度,内存的使用,以及电力的消耗。一个很常见的减少APK大小的方法就是减少资源文件的数量和大小,比如,你可以移除不在使用的资源,使用可以扩展的Drawable对象来代替图像文件,所以接下来就是讨论怎样减少资源文件的数量和大小

删除不使用的资源文件
  • lint工具是AS中自带的静态代码分析器,检测res/文件下没有被引用的资源,当检测发现了没有被引用的资源后会输出像以下的警告。

    1
    2
    res/layout/preferences.xml: Warning: The resource R.layout.preferences appears
    to be unused [UnusedResources]

    但是Lint工具无法扫描检测assets/文件夹,因为此文件夹下的是通过反射引用的资源或者是链接的库文件,所以它是不可以的移除的资源,它只会存在一个提醒
    Libraries代码库中也可能存在不被使用的情况,Gradle可以自动的移除这些无用的资源当你在build.gradle文件中配置shrinkResources

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    android {
    // Other settings

    buildTypes {
    release {
    minifyEnabled true
    shrinkResources true
    proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
    }
    }
    }

    使用shrinkResources,就必须使用代码压缩,在构建的过程中,ProGuard会首先移除未使用的代码但不会移除未使用的资源,之后Gradle会移除未使用的资源,(关于ProGuard和其他方法的来压缩代码和资源可学习此文章Shrink Your Code and Resources

减少资源库
  • 在开发中我们肯定会使用外部库来提高我们应用的可用性,所以中会包含很多对象和方法是不需要,所以如果许可证认可,可以去动态的编辑库文件,增加删除库文件,来定制自己的APP内容,ProGuard可以移除不使用的库中代码,但是它不能够移除库中的内部依赖项
支持特定的屏幕密度
  • 若只有一小部分的用户使用,则需要特殊处理,如果你APP中不包含这些特定的屏幕密度,android会自动缩放现有的资源。如果你需要自动缩放的资源,你可以通过绘制图像的变种放置drawable-nodpi/来节省空间,我们建议至少存在一个xxhdpi的图片变种
使用Drawable对象
  • 一些图片不需要静态的图片资源而是在运行的时候动态的绘制图片,Drawable对象(shape in XML)在APK中占用很少的资源,并且Drawable符合md的设计原则
复用资源
  • 比如可以对图像的变化包含一个单独的资源,在图片的旋转,切片等,建议使用同一组资源,然后在使用的时候定制它们。Android提供工具来改变资源的颜色,在5.0以上,可以是使用android:tinttintMode,而低版本可以使用ColorFilter类来实现。还可以通过旋转图片来达到资源的复用,比如下面的代码就是通过旋转180度,来达到『拇指向上』到『拇指向下』的转变

    1
    2
    3
    4
    5
    6
    <?xml version="1.0" encoding="utf-8"?>
    <rotate xmlns:android="http://schemas.android.com/apk/res/android"
    android:drawable="@drawable/ic_thumb_up"
    android:pivotX="50%"
    android:pivotY="50%"
    android:fromDegrees="180" />
代码渲染
  • 通过代码渲染来试图来节省图片的size,通过代码渲染就可以节省存储图片的空间。
压缩PNG文件
  • aapt在构建过程中可以优化和无损压缩放置在res/drawable/的图片资源,例如,aapt可以将不需要超过256色的真彩色PNG转换为具有调色板的8位PNG,这样可以相同质量的图片占更小的内存。但是aapt有以下几点局限:

    • 不能够压缩在asset/文件下的PNG文件
    • aapt优化图片文件需要256或者更少色
    • aapt可能会填充已经压缩的PNG文件,为了防止个问题,我们应该在Gradle配置中使用cruncherEnabled标志来防止此问题

      1
      2
      3
      aaptOptions {
      cruncherEnabled = false
      }
压缩PNG和JPEG文
  • 可以使用pngcrush, pngquant, or zopflipng在无损的情况下减少PNG质量,pngcrush是非常有效的,它遍历PNG过滤器和 zlib (Deflate)参数,使用每个组合的过滤器和参数来压缩图片,然后选择最好的压缩输出,而JPEG可以使用packJPGguetzli
使用WebP文件格式
  • 可以使用WebP格式来代替PNG和JPEG格式,当targeting超过3.2(API 13),WebP可以提供有损压缩(JPEG)以及透明度(PNG)并且压缩效果要比PNG和JPEG效果好,所以可以使用WebP来代替BMP, JPG, PNG 和静态GIF文件在AS中,更多信息关于WebP可以看 Create WebP Images Using Android Studio

    Google Play 只接受启动图标为PNG格式的APK

使用矢量图标
  • 可以使用矢量图形创建独立于分辨率的图标和其他可伸缩的媒体。使用这些图标可以大大的减少APK的体积,矢量图标在Android中代表VectorDrawable对象,一个VectorDrawable对象只需使用100个字节大小就可以生成清晰的图像,但是系统在渲染VectorDrawable对象时需要大量的时间,所以如果是一张大图,则在显示过程中会花费大量的时间,所以只推荐小图的时候使用矢量图标。更多关于使用VectorDrawable对象的信息可以看 Working with Drawables
使用矢量动画图像

减少本地和Java代码

减少不必要的生成代
  • 要了解自动生成的代码空间大小,例如,许多协议缓冲工具生成了大量的方法和类,可以将应用程序的大小增加一倍甚至更多。
避免枚举
  • 一个枚举在应用程序class.dex文件中将会增加1-1.4kb,这些添加会很快的积累到复杂的系统和共享库中,如果可以考虑使用 @IntDef注释和ProGuar处理枚举代码转换为interger值,这样可以保证枚举类型的安全效益。
减少二进制文件的大小
  • 如果你的APP中有存在native代码和AndroidNDK代码,也可以通过优化你的代码来减少APP的size,所以可以通过下面2种方式来进行优化。
    • 移除debug符号:当你在开发的时候使用debug符号是有意义的,Android NDK 提供arm-eabi-strip工具,用来去除native库中不必要的debug符号,在这之后在打release包。
    • 避免复制native库:.so文件在APK存储中是不能够被压缩的,在 元素中设置android:extractNativeLibs标记为false可以避免PackManager在应用安装时复制.so文件到文件系统并且还可以使应用程序增量更新更小

维护多个精益的APK

  • 你的APK可能有用户下载但是却没有使用的内容,像国家和语言信息,使用户能够有一个最小的下载,所以可以将APP分割成几个APK,比如不同的屏幕和GPU支持,当用户下载时,可以根据用户的型号,进行定制下载,下载用户需要的资源,而不需要的不下载,比如hdpi的设备,只需要hdpi资源,而不需要xxxxhdpi资源,所以就是不需要的资源,更多的信息可以查看Configure APK SplitsMaintaining Multiple APKs
参考文档

Android 4大组件特点

-

Activity

  • Acitvity简单的理解为View
  • Activity之间使用Intent进行通信
  • 在AndroidManifest文件进行配置申明

BoradcastReceiver

  • 简单的理解为一个监听,对各个广播的监听
  • 两种注册方式:
    • 静态:在AndroidManifest文件中注册,当设备启动并不关闭,这个BoradcastReceiver会一直存在,直到设备关闭。
    • 动态:在代码中注册,当注册的进程被干掉,这个BoradcastReceiver也被干掉
  • 在onReceive()中不能进行耗时的操作,进行耗时操作时会导致ANR,如果需要耗时操作时,必须启动一个Service服务,不能够使用线程,因为BoradcastReceiver生命周期很短,当线程还没执行完操作,就会被干掉。

Service

  • 可以在后台进行一些耗时操作的服务
  • 两种启动方式:
    • startService(),一旦启动,必须在不使用的时候关闭,因为这个时候与启动它的组件无关,它可以在后台无限的运行,必须调用stopSelf()或者stopService()方法关闭它。
    • bindService(),调用者与Service进行了绑定,所以会随着调用者的死亡而死亡,不求同年同月生,只求同年同月死的意思
  • 在AndroidManifest文件进行配置申明

ContentProvider

  • 内容提供者,内容的共享,通过ContentProvider可以指定Android中可共享的数据,这些数据可以存储在任何合理的方式,其他应用可以通过此组件获取共享数据。
  • 需要内容共享才需要使用此组件。
  • 在AndroidManifest文件进行配置申明

多线程简单介绍

-

Thread 与 Runnable的比较

  • Thread需要继承实现,而Java中是单继承,所以我一般不常用Thread,而是使用Runnable接口实现多线程

关于多线程的几个方法

  • sleep():让当前线程进入休眠状态(线程不会释放获得的锁),参数设置休眠时间。
  • yield():让步,让不低于自己优先级的线程有机会运行。(不会释放获取的锁

  • interrupt():终止哪些调用中断方法进入阻塞状态的线程。(sleep,wait,join都是中断方法

  • join(): 当前线程对一个线程调用此方法,当前线程就会停止运行,直到另一个线程执行完成后,才会继续执行当前线程。(会释放获得的锁,join方法内部调用了wait方法

  • wait():此方法是object的实例方法,让当前线程进入阻塞状态。(释放获取对象的内部锁)可通过notify和notifyall来唤醒

  • notify()/notifyall():是Object的实例方法,notify()是唤醒等待该对象的线程,而notifall()是唤醒所有等待的对象。

synchronized关键字

  • Java中的每个对象都有一个内部锁,每个类都有一个锁,控制多线程对其静态成员的并发访问。如果一个实例方法使用synchronized关键字修饰,内部锁就会监听这个方法,只有获取到该对象内部锁的线程才能执行此方法。
  • 对象的内部锁在同一时刻只能由一个线程持有,其他线程尝试获取该对象内部锁都会被阻塞,这种情况下的阻塞不会被中断。

volatile域

  • 此关键字可以保证实例域是可见的,通常当我们访问内存中的变量时,就会把它缓存在寄存器中,当需要读这个值时,直接从寄存器拿取,进行加减运算时,直接将寄存器中的值进行加减运行,结束后将值写回内存,问题就出来了,对一个线程对flag变量进行操作,先将flag放进寄存器中,准备将它加上2,这个时候,另一个线程抢占了资源,对flag值进行操作,进行了加3的操作,并将flag写回了内存,这时,第一个线程对flag继续进行操作,对原来的flag值进行操作,加2,然后写回内存,当我们读取时就会发现,flag值为2,所以volatile就是为了解决该问题,就是每次访问变量时,直接从内存拿值,而不会从寄存器中获取。
参考资料