AndroidVideoCache-视频边播放边缓存的代理策略

android视频缓存框架 [AndroidVideoCache](https://github.com/danikula/AndroidVideoCache) 源码解析与评估 文章目录android视频缓存框架 [AndroidVideoCache](https://github.com/danikula/AndroidVideoCache) 源码解析与评估引言使用方式关键类解析HttpProxyCacheServer 代理缓存服务类**java.net.ProxySelector ** 代理选择Pinger 判断本地serverSocket是否存活GetRequest 封装用于获取请求中信息;HttpProxyCacheServerClients 以url为key绑定的 阅读详情
视频现状

现在视频播放的需求越来越常见,就和16年上半年的直播一样,似乎不加个视频已经不是个正常的APP了,连微信朋友圈都支持上传小视频,更别谈以视频为本命的一系列APP。
视频方面主要是两块,一个是视频录制,这个已经翻过一篇比较全的文章,再加上google开源的 grafika ,可以在踩坑时减少很多障碍,不过录制这块适配是大问题,需要不断调整。
另一个方面就是视频播放,这方面的轮子比上面录制就多太多了,无论是google(开源良心)的 ExoPlayer,以及b站的 ijkplayer,还是一些其他的,基本上满足了正常的需求。
当然,今天这篇文章这两个并不是主角,在视频播放这种极其容易造成卡顿,跳帧等影响用户体验的需求上,如何优化体验是一件十分重要的事情。一般情况比较正常的就直接播放,一句设置数据源的代码了事。但是要为了用户体验考虑。纵观这些有视频功能的APP,主要分为两类,一种是直接下载然后再播放,比如微信,微信的小视频录制压缩比比较好,一个视频大概几百k,所以比较适合先全量下载,然后再播放的模式,另一种自然就是边播放边缓存,这是比较多的策略,大部分的视频都是比较大的,等全部下完,黄花菜都凉了。

基本原理

既然是采取边播放边缓存的策略,比较逗的方式就是一边正常的给videoview设置数据源,一边开一个线程去下载文件,下完后就可以使用本地缓存了,这个方式是比较逗的,相当于两份网络请求,大大的拖慢了用户体验。所以我们会想如何只有一份请求但是能够操作这些数据边读边写呢,这个就是 AndroidVideoCache 所做的事情。
AndroidVideoCache 通过代理的策略实现一个中间层将我们的网络请求转移到本地实现的代理服务器上,这样我们真正请求的数据就会被代理拿到,这样代理一边向本地写入数据,一边根据我们需要的数据看是读网络数据还是读本地缓存数据再提供给我们,真正做到了数据的复用。
这就和我们使用的抓包软件性质一样,上个原理图更清晰

代理服务器策略
从使用开始

这里在如何使用上直接搬运作者自己的readme。
首先AS用户一行代码在gradle中导包

dependencies {
    compile 'com.danikula:videocache:2.6.4'
}

然后在全局初始化一个本地代理服务器,这里选择在Application的实现类中,至于这个类是干什么的,后面会详细分析

public class App extends Application {

    private HttpProxyCacheServer proxy;

    public static HttpProxyCacheServer getProxy(Context context) {
        App app = (App) context.getApplicationContext();
        return app.proxy == null ? (app.proxy = app.newProxy()) : app.proxy;
    }

    private HttpProxyCacheServer newProxy() {
        return new HttpProxyCacheServer(this);
    }
}

有了代理服务器,我们就可以使用了,把自己的网络视频url用提供的方法替换成另一个URL

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

    HttpProxyCacheServer proxy = getProxy();
    String proxyUrl = proxy.getProxyUrl(VIDEO_URL);
    videoView.setVideoPath(proxyUrl);
}

这样就已经可以正常使用了,当然这个库提供了更多的可以自定义的地方,比如缓存的文件最大大小,以及文件个数,缓存采取的是LruCache的方法,对于老文件在达到上限后会自动清理。

private HttpProxyCacheServer newProxy() {
    return new HttpProxyCacheServer.Builder(this)
            .maxCacheSize(1024 * 1024 * 1024)       // 1 Gb for cache
            .build();
}

private HttpProxyCacheServer newProxy() {
    return new HttpProxyCacheServer.Builder(this)
            .maxCacheFilesCount(20)
            .build();
}

除了这个,还有一个就是生成的文件名,默认是使用的MD5方式生成key,考虑到一些业务逻辑,我们也可以继承一个 FileNameGenerator 来实现自己的策略

public class MyFileNameGenerator implements FileNameGenerator {

    // Urls contain mutable parts (parameter 'sessionToken') and stable video's id (parameter 'videoId').
    // e. g. http://example.com?videoId=abcqaz&sessionToken=xyz987
    public String generate(String url) {
        Uri uri = Uri.parse(url);
        String videoId = uri.getQueryParameter("videoId");
        return videoId + ".mp4";
    }
}

...
HttpProxyCacheServer proxy = HttpProxyCacheServer.Builder(context)
    .fileNameGenerator(new MyFileNameGenerator())
    .build()

到这里基本上整个AndroidVideoCache的使用就没什么问题了。

具体分析

知道了怎么使用后,我们继续往下走,看看是怎么实现的,这里就不分析后面的那些LruCache这些缓存策略,生成key之类的逻辑了,和一般的网络请求里的都大同小异,我们直接看这个代码最有含金量的地方。
前面在使用中,全局实例化过一个代理服务器,就先从这里开始

HttpProxyCacheServer.java
    private static final String PROXY_HOST = "127.0.0.1";
    private final Object clientsLock = new Object();
    private final ExecutorService socketProcessor = Executors.newFixedThreadPool(8);
    private final Map<String, HttpProxyCacheServerClients> clientsMap = new ConcurrentHashMap<>();
    private final ServerSocket serverSocket;
    private final int port;
    private final Thread waitConnectionThread;
    private final Config config;
    private final Pinger pinger;

    private HttpProxyCacheServer(Config config) {
        this.config = checkNotNull(config);
        try {
            InetAddress inetAddress = InetAddress.getByName(PROXY_HOST);
            this.serverSocket = new ServerSocket(0, 8, inetAddress);
            this.port = serverSocket.getLocalPort();
            CountDownLatch startSignal = new CountDownLatch(1);
            this.waitConnectionThread = new Thread(new WaitRequestsRunnable(startSignal));
            this.waitConnectionThread.start();
            startSignal.await(); // freeze thread, wait for server starts
            this.pinger = new Pinger(PROXY_HOST, port);
            LOG.info("Proxy cache server started. Is it alive? " + isAlive());
        } catch (IOException | InterruptedException e) {
            socketProcessor.shutdown();
            throw new IllegalStateException("Error starting local proxy server", e);
        }
    }

这个构造函数一眼看过去就很清楚了,参数就是前面那些自定义配置,这里使用的是 127.0.0.1,这个就是localhost的ip也就是本地ip,创建了一个 ServerSocket ,随机分配了一个端口,这里通过 getLocalPort 拿到了这个服务器端口,后面用来通信。
这里出现了一个线程 WaitRequestsRunnable 并且调用了 start 方法,继续跟进去看这个线程

        @Override
        public void run() {
            startSignal.countDown();
            waitForRequest();
        }

信号量主要是为了保证这个run方法先执行,继续看这个 waitForRequest 方法

    private void waitForRequest() {
        try {
            while (!Thread.currentThread().isInterrupted()) {
                Socket socket = serverSocket.accept();
                LOG.debug("Accept new socket " + socket);
                socketProcessor.submit(new SocketProcessorRunnable(socket));
            }
        } catch (IOException e) {
            onError(new ProxyCacheException("Error during waiting connection", e));
        }
    }

好了,到这里服务器socket一套比较清晰了,整理一下就是先构建一个全局的一个本地代理服务器 ServerSocket,指定一个随机端口,然后新开一个线程,在线程的 run 方法里,通过accept() 方法监听这个服务器socket的入站连接,accept() 方法会一直阻塞,直到有一个客户端尝试建立连接。
现在有了服务器,然后就是客户端的socket,先从使用时代理替换url地方开始看

    HttpProxyCacheServer proxy = getProxy();
    String proxyUrl = proxy.getProxyUrl(VIDEO_URL);

这里使用的是HttpProxyCacheServer 中的 getProxyUrl 方法

    public String getProxyUrl(String url, boolean allowCachedFileUri) {
        if (allowCachedFileUri && isCached(url)) {
            File cacheFile = getCacheFile(url);
            touchFileSafely(cacheFile);
            return Uri.fromFile(cacheFile).toString();
        }
        return isAlive() ? appendToProxyUrl(url) : url;
    }

整个策略就是如果本地已经缓存了,就直接那本地地址的Uri,并且touch一下文件,把时间更新后最新,因为后面LruCache是根据文件被访问的时间进行排序的,如果文件没有被缓存那么就会先走一下 isAlive() 方法, 这里会ping一下目标url,确保url是一个有效的,如果用户是通过代理访问的话,就会ping不通,这样就还是原生url,正常情况都会进入这个 appendToProxyUrl 方法里面。

    private String appendToProxyUrl(String url) {
        return String.format(Locale.US, "http://%s:%d/%s", PROXY_HOST, port, ProxyCacheUtils.encode(url));
    }

比较直接,这里拼接出来一个带有127.0.0.1目标地址及端口并携带原url的新地址,这个请求的话就会被我们的服务器socket监听到,也就是前面的accept() 会继续往下走,这里接收到的socket就是我们所请求的客户端socket

 socketProcessor.submit(new SocketProcessorRunnable(socket));

整个socket会被包裹成一个runnable,发配给线程池。这个 runnable 的 run 方法中所做的事情就是调用了一个方法

    private void processSocket(Socket socket) {
        try {
            GetRequest request = GetRequest.read(socket.getInputStream());
            LOG.debug("Request to cache proxy:" + request);
            String url = ProxyCacheUtils.decode(request.uri);
            if (pinger.isPingRequest(url)) {
                pinger.responseToPing(socket);
            } else {
                HttpProxyCacheServerClients clients = getClients(url);
                clients.processRequest(request, socket);
            }
        } catch (SocketException e) {
            // There is no way to determine that client closed connection http://stackoverflow.com/a/10241044/999458
            // So just to prevent log flooding don't log stacktrace
            LOG.debug("Closing socket… Socket is closed by client.");
        } catch (ProxyCacheException | IOException e) {
            onError(new ProxyCacheException("Error processing request", e));
        } finally {
            releaseSocket(socket);
            LOG.debug("Opened connections: " + getClientsCount());
        }
    }

前面ping的过程其实也被会这个socket监听并且走进来这一段,不过这个比较简单,就不分析了,我们直接看里面的 else 框内的代码,这里一个 getClients 就是一个ConcurrentHashMap,重复url返回的是同一个HttpProxyCacheServerClients ,

 HttpProxyCacheServerClients clients = clientsMap.get(url);
            if (clients == null) {
                clients = new HttpProxyCacheServerClients(url, config);
                clientsMap.put(url, clients);
            }
            return clients;

如果是第一次就会根据url构建出一个HttpProxyCacheServerClients并被put到ConcurrentHashMap中,真正的操作都在这个客户端的 processRequest 操作中,并且传递过去一个是request,这是一个GetRequest 对象,是一个url和rangeoffset以及partial的包装类,另一个就是客户端socket。

    public void processRequest(GetRequest request, Socket socket) throws ProxyCacheException, IOException {
        startProcessRequest();
        try {
            clientsCount.incrementAndGet();
            proxyCache.processRequest(request, socket);
        } finally {
            finishProcessRequest();
        }
    }

这里 startProcessRequest 方法会得到一个HttpProxyCache 类

    private synchronized void startProcessRequest() throws ProxyCacheException {
        proxyCache = proxyCache == null ? newHttpProxyCache() : proxyCache;
    }
    private HttpProxyCache newHttpProxyCache() throws ProxyCacheException {
        HttpUrlSource source = new HttpUrlSource(url, config.sourceInfoStorage);
        FileCache cache = new FileCache(config.generateCacheFile(url), config.diskUsage);
        HttpProxyCache httpProxyCache = new HttpProxyCache(source, cache);
        httpProxyCache.registerCacheListener(uiCacheListener);
        return httpProxyCache;
    }

在这里,我们构建一个基于原生url的HttpUrlSource ,这个类负责持有url,并开启HttpURLConnection来获取一个InputStream,这样才能通过这个输入流读数据,同时也创建了一个本地的临时文件,一个以.download结尾的临时文件,这个文件在成功下载完后的 FileCache 类中的 complete 方法中被更名。
我们构建了一个HttpProxyCache 类,也注册了一个CacheListener,这个listener可以用来回调进度。
做完这一切之后,然后这个HttpProxyCache 对象就开始 processRequest ,

    public void processRequest(GetRequest request, Socket socket) throws IOException, ProxyCacheException {
        OutputStream out = new BufferedOutputStream(socket.getOutputStream());
        String responseHeaders = newResponseHeaders(request);
        out.write(responseHeaders.getBytes("UTF-8"));

        long offset = request.rangeOffset;
        if (isUseCache(request)) {
            responseWithCache(out, offset);
        } else {
            responseWithoutCache(out, offset);
        }
    }

这里我们用传过来的那个客户端socket,拿到一个OutputStream输出流,这样我们就能往里面写数据了,如果不用缓存就走常规逻辑,这里我们只看走缓存的行为。

    private void responseWithCache(OutputStream out, long offset) throws ProxyCacheException, IOException {
        byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
        int readBytes;
        while ((readBytes = read(buffer, offset, buffer.length)) != -1) {
            out.write(buffer, 0, readBytes);
            offset += readBytes;
        }
        out.flush();
    }

构造一个8 * 1024字节的buffer,这里的read方法,实际上是调用的父类ProxyCache的实现

    public int read(byte[] buffer, long offset, int length) throws ProxyCacheException {
        ProxyCacheUtils.assertBuffer(buffer, offset, length);

        while (!cache.isCompleted() && cache.available() < (offset + length) && !stopped) {
            readSourceAsync();
            waitForSourceData();
            checkReadSourceErrorsCount();
        }
        int read = cache.read(buffer, offset, length);
        if (cache.isCompleted() && percentsAvailable != 100) {
            percentsAvailable = 100;
            onCachePercentsAvailableChanged(100);
        }
        return read;
    }

在while循环里面,开启了一个新的线程sourceReaderThread,其中封装了一个SourceReaderRunnable的Runnable,这个异步线程用来给cache,也就是本地文件写数据,同时还更新一下当前的缓存进度

        int sourceAvailable = -1;
        int offset = 0;
        try {
            offset = cache.available();
            source.open(offset);
            sourceAvailable = source.length();
            byte[] buffer = new byte[ProxyCacheUtils.DEFAULT_BUFFER_SIZE];
            int readBytes;
            while ((readBytes = source.read(buffer)) != -1) {
                synchronized (stopLock) {
                    if (isStopped()) {
                        return;
                    }
                    cache.append(buffer, readBytes);
                }
                offset += readBytes;
                notifyNewCacheDataAvailable(offset, sourceAvailable);
            }
            tryComplete();
            onSourceRead();

同时我们的另一个线程也会从cache中去读数据,在缓存结束后同样也会发送一个通知通知自己已经缓存完了,回调由外界控制。
以上差不多就是总体代码,这里我们在请求远程URL时将文件写到本地fileCache中,然后读数据从本地读取,写入到客户端socket里面,服务器Socket主要还是一个代理的作用,从中间拦截掉网络请求,然后实现对socket的读取和写入。

后记

整个分析为了节约篇幅,尽量的是描述一些其中比较重要的片段,源码文件还是比较多的,这里不能详述,对这种代理方式感兴趣的可以在自己详细阅读一下源码,毕竟源码面前,了无秘密。
这个项目用起来有一点问题,是因为如果我们的APP设置了代理,那么这个socket方式拿url就会出问题,因为我们拿到的也是一个代理url,所以在开发时需要考虑代理用户提供兼容性处理。
另外这种本地代理服务器的策略也能为我们提供一些不一样的思路,既然视频可行那么音频文件呢,进而推导到普通的网络请求,json文件。基于这样一套思路,在其基础上甚至能够实现一套离线缓存加载的策略,当然这取决于我们自身的服务器架构,服务端URL策略。



作者:sheepm
链接:https://www.jianshu.com/p/4745de02dcdc
來源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。
AndroidVideoCache解析 前言        之前做短视频项目,需求是需要视频缓存功能,我也觉得比较合理,毕竟一个视频看完之后重复观看的时候还需要从网上加载是很不友好的事情,一方面耗费用户的流量,另一方面直接从本地播放要更流畅,特别是在seek的时候。在github上看到了AndroidVideoCache,使用起来非常方便,大概知道它是用代理实现的,但是代理具体怎么做的一直没去深究,今天刚好得空,好好研究下,和大家分享。... 阅读详情

相关推荐

【Android 进阶】仿抖音系列之列表播放视频(三)

在上一篇【Android 进阶】仿抖音系列之列表播放视频(二)中,我们实现列表播放视频,这一篇我们来对其做些优化。 当我们滑动列表播放视频时,如果你设置了状态栏显示网速的话,可以看到网速占用比较大,我们需要实现边播边缓存,下次播放时,就可以从缓存中取,减少网络使用。 Github上已经有现成的缓存库,我们只要集成进来就好,这是链接 AndroidVideoCache 1、创建MyApp 继承 Ap...

欢子的博客 5041

视频缓存AndroidVideoCache

一、关于AndroidVideoCache AndroidVideoCache是一个音视频缓存库,用于支持VideoView/MediaPlayer, ExoPlayer ,IJK等播放器的边下载边播放,按照github列出支持的特性如下: 1、音视频播放的时候会将多媒体数据存储于磁盘上面 2、如果播放的数据已经缓存,支持离线播放 3、支持部分加载(该特性未知) 4、可以设置缓存配置,如...

qq_40144839的博客 2499

Android 边播放边缓存视频框架AndroidVideoCache简析

现在的移动应用,视频是一个非常重要的组成部分,好像里面不搞一点视频就不是一个正常的移动App。在视频开发方面,可以分为视频录制和视频播放,视频录制的场景可能还比较少,这方面可以使用Google开源的 grafika。相比于视频录制,视频播放可以选择的方案就要多许多,比如Google的 ExoPlayer,B站的 ijkplayer,以及官方的MediaPlayer。不过,我们今天要讲的是视频的缓存。最近,由于我们在开发视频方面没有考虑视频的缓存问题,造成了流量的浪费,然后遭到用户的投诉。......

xiangzhihong8的专栏 1725

音视频开发之旅(49)-边缓存边播放之AndroidVideoCache

目录 背景 AndroidVideoCache简单使用 实现原理 源码分析 AndroidVideoCache的不足 资料 收获 一、背景 播放音视频时,播放器数据的请求是由播放器内部发起的,我们只是提供了一个url,而不能控制数据的请求过程, 都是要先进行下载,下载到一定量之后播放器再开始播放,当下载进度减去播放进度小于一定阀值,进入缓冲状态。 比如MediaPlayer的最小缓存大小是4M,最大20M //framework/av/media/libdatasource/in..

yabin的专栏 1089

AndroidVideoCache:高效视频音频缓存解决方案

在现代移动应用中,视频内容的流畅播放已成为用户体验的关键要素之一。为解决网络条件不稳定带来的播放卡顿问题,AndroidVideoCache库应运而生。它不仅是一个视频播放缓存库,还通过优化网络请求、缓存处理和异步加载机制来提升视频播放的性能和用户的观看体验。AndroidVideoCache通过在本地存储视频片段,实现了在网络状况不佳时仍能保持视频的流畅播放。其核心功能包括:- 网络请求拦截与缓存管理- 边下载边播放的流媒体处理- 优化的异步加载策略来降低主UI线程的负载。

weixin_42502089的博客 1635

Zabbix监控联想服务器XCC的SNMPv3配置实战

本文详细介绍了如何通过SNMPv3协议配置Zabbix监控联想服务器XCC的实战方法。文章从安全角度阐述了选择SNMPv3的必要性,并分步讲解了在XCC中启用SNMPv3、创建认证用户,以及在Zabbix中导入专用模板、创建主机并关联模板的完整流程,帮助运维人员实现对服务器硬件健康状态的自动化、安全监控。

weixin_29210727的博客 464

推荐开源项目:AndroidVideoCache——让你的视频缓存再无烦恼!

推荐开源项目:AndroidVideoCache——让你的视频缓存再无烦恼! 项目地址:https://gitcode.com/gh_mirrors/an/AndroidVideoCache 在移动互联网时代,视频已成为信息传播的重要载体。然而,在网络状况不佳或流量限制的情况下,如何保证视频流畅播放成为开发者的一大难题。今天,我为大家介绍一款强大且实用的开源工具——AndroidVideoCach...

gitblog_00060的博客 1368

FAGLB03余额显示异常排查与修复指南

本文针对SAP新总账中FAGLB03报表余额显示异常问题,提供了专业的排查与修复指南。文章深入剖析了数据不一致的核心原因,如FAGLFLEXT汇总表与凭证明细不同步、年度余额结转(FAGLGVTR)失败等,并详细介绍了使用FAGLF03进行精准诊断、通过SE38执行标准调整程序TFC_ADJUST_VZ进行修复的标准操作流程,帮助用户快速恢复数据一致性,确保财务报表的准确性。

weixin_29313423的博客 307

Android视频边播放边缓存的代理策略之——AndroidVideoCache

简介 AndroidVideoCache是国外大神Alexey Danilov写的一个android开源库。一个视频/音频缓存库,利用本地代理实现了边下边播,支VideoView/MediaPlayer, ExoPlayer,IJK等播放器的边下载边播放。集成简单,与业务无关。 代码的架构写的也很不错,很值得研究阅读和学习借鉴。网络用httpurlconnect,实现了文件缓存处理,文...

tklwj的专栏 2087

数电实训:万年历

万年历

Lmyccq的博客 1万+

基于Flink的风控系统

可以使用Flink-Stream进行实时数据流的检测和处理,或者使用Flink-Kafka集群进行数据转发和存储,然后使用Flink-Table进行数据表的查询和分析。在Flink中,可以使用Flink-Stream进行数据流的处理,或者使用Flink-Kafka进行Kafka数据流的消费和处理。Flink的运行时本身也支持迭代算法的执行。可以使用Flink-Stream进行实时数据流的检测和处理,或者使用Flink-Kafka集群进行数据转发和存储,然后使用Flink-Table进行数据表的查询和分析。

m0_68231587的博客 1874

Android视频缓存终极指南:AndroidVideoCache vs 原生缓存谁更胜一筹?[特殊字符]

在移动应用开发中,视频播放体验直接影响用户留存率。AndroidVideoCache作为一款专为Android设计的视频缓存库,通过单行代码即可为任何视频播放器添加缓存支持,让您的应用告别卡顿,实现流畅播放!本文将深入解析AndroidVideoCache的核心优势、使用技巧,并与原生缓存方案进行全方位对比。 ## 为什么选择AndroidVideoCache?🤔 传统视频播放需要反复下载相

gitblog_00822的博客 1045

WSOLA原理及matlab仿真

WSOLA原理 对于 OLA 算法来说,常常无法保持原信号中的周期性结构,在帧裁剪的过程中,不能保证每一个帧都能覆盖完整周期并保证其相位对齐,会产生相位跳跃失真。 为保证**基频不变**,就要使用 WSOLA 算法。 WSOLA 并不直接在 Ha 处找一帧进行直接拼接,而是在一个区间内寻找与理想帧最接近的一帧进行叠加。 ![在这里插入图片描述](https://img-blog.csdnimg.cn/8a234f4fde104fc69a9f3ddb7bcf3014.png?x-oss-process=ima

ManiacLook的博客 3214

AndroidVideoCache源码赏析

前言 AndroidVideoCache 是用来帮助实现视频音频边缓存边播放的开源库,最近有个视频播放的功能,为了避免视频数据重复加载,就使用了这个库,感觉很棒,而且这个库的实现思路非常值得学习研究,就花点时间学习下它的源码。 原理 实现原理可以很简单的描述下:创建一个本地代理服务器,获取数据的请求不直接服务服务器而是访问代理服务器,代理服务器先判断请求是否有缓存,已经有缓存了的话就直接返回缓存数...

楠之枫雪的博客 628

编织数据的实时脉络——构建基于MySQL的数据编织平台,实现实时数据治理

除了上述基本设置外,我们还可以根据实际应用场景自定义一些特定的监控项目。例如,对于某些高并发读写的表,可能需要特别关注它们的锁等待时间和回滚段使用率;而对于涉及到跨库关联查询的操作,则应当着重检查网络延迟及缓存命中率等因素。-- 获取InnoDB行级锁等待情况-- 统计各表的缓存命中率这段SQL脚本不仅展示了如何查询InnoDB引擎下的锁争用状况,还介绍了计算表级别缓存命中率的方法,有助于深入理解系统的运行状态。

墨夶的博客 1789

C++ (week6、7):Linux系统编程4:网络

Linux网络:计算机网络概述、TCP协议、Socket编程、IO多路复用机制(select、epoll)、HTTP协议、进程池、线程池、大文件网络传输问题

Edward2022的博客 1429

视频缓存AndroidVideoCache攻略

好久没有更新博客了,一个是因为太忙,业务是在太多,另外一个就是也比较懒,所以拖着就有接近两个月的时间没有写博客了,实在是罪过。今天分享一个开源库 AndroidVideoCache 。这个库主要是做视频缓存管理功能,支持边下边播,离线播放,缓存管理等。用过MediaPlayer的小伙伴都知道,可以支持在线播放和播放本地资源,但是不支持缓存,这样很消耗用户流量,这个时候AndroidVideoCac...

weixin_34234721的博客 1673

Apache Jena SPARQL 查询完全指南:入门与实战案例

本文介绍了使用Apache Jena执行SPARQL查询的完整指南。SPARQL是用于RDF数据的标准查询语言,在知识图谱领域地位相当于SQL。教程从基础开始,涵盖创建模型、加载数据以及执行各种查询类型。特别强调了WHERE子句中的图模式匹配机制,通过Mermaid图直观展示数据图与查询图的匹配过程。文章还对比了变量命名规范(?和$前缀),解释了SPARQL引擎如何通过变量绑定实现数据检索。通过Hello World示例演示了完整查询流程,为开发者提供了SPARQL的实用入门指导。

Lincoln 的专栏 1371

NTscan扫描工具

ntscan是一个扫描器,可以扫描没有密码和弱密码的共享,先到配置文件中填写猜测的用户名和密码,扫描过程中右侧出现的是扫描日志。

大中型超市收银系统(可直接运行)

光步前台收银系统系统功能:商品销售、商品退货、挂单、收银员权限、会员销售、单品取消、整单取消、商品查询、脱机销售、脱机数据上传,基本资料下载,开钱箱、小票打印、收款结算,支持多币种付款方式(现金、储值卡、礼券、微信等),支持多种促销。运行环境:.net 4.0登陆信息用户名:1001 密码:123456内置商品信息,商品编码可输入00001000 - 00010000,测试效果相关功能键F5 结算 F9挂单 F11商品查询 F8退货 Insert单品取消 Del整单取消 F12开钱箱F2 会员 Ctrl+Z退出系统 Home 锁屏 相关源码待公开

Android 边播放边缓存视频框架:AndroidVideoCache简析

现在的移动应用,视频是一个非常重要的组成部分,好像里面不搞一点视频就不是一个正常的移动App。在视频开发方面,可以分为视频录制和视频播放,视频录制的场景可能还比较少,这方面可以使用Google开源的 grafika。

m0_70749039的博客 2135

Probability and Statistics - 4th edition

Probability and Statistics - 4th edition

AndroidVideoCache终极指南:本地HTTP代理服务器实现视频缓存黑科技

AndroidVideoCache是一个强大的Android视频缓存库,通过一行代码就能为任何视频播放器添加缓存支持。这个开源项目让开发者能够轻松实现视频流的本地缓存,大幅提升用户体验并节省用户流量。 ## 🎯 为什么需要AndroidVideoCache? 在移动应用开发中,视频播放是常见的需求。但频繁下载相同视频既浪费流量又影响播放体验。AndroidVideoCache通过创建本地HT

gitblog_00681的博客 804

OV9734 DATASHEET,完整规格书

OV9734完整规格书,包括电气参数,尺寸规格,寄存器配置表等

[墙裂推荐]AndroidVideoCache:实现视屏播放边下边播

热文导读 | 点击标题阅读 成为 Android 大牛的10大独门秘籍 吊炸天!74款APP完整源码! Android仿QQ侧滑菜单小红点的实现 前言 现在在Android上实现视频播放已不是什么难事,这方面的轮子已经很多,不管是Google的 ExoPlayer、B站的 ijkplayer,还是一些其他的,基本上都能满足我们的需求。 但现在

Java和Android架构 3430

音视频开发(三十九):Android视频缓存之AndroidVideoCache

目录 背景 AndroidVideoCache简单使用 实现原理 源码分析 AndroidVideoCache的不足 一、背景 播放音视频时,播放器数据的请求是由播放器内部发起的,我们只是提供了一个url,而不能控制数据的请求过程, 都是要先进行下载,下载到一定量之后播放器再开始播放,当下载进度减去播放进度小于一定阀值,进入缓冲状态。 比如MediaPlayer的最小缓存大小是4M,最大20M //framework/av/media/libdatas

m0_60259116的博客 5220
上一篇: windows环境搭建Vue开发环境
下一篇: 项目爬坑记之targetSdkVersion为23以下6.0中checkSelfPermission的问题
MrCoderr
博客等级 码龄12年 39粉丝 48原创
评论 9
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值