Android设备广告投放解决方案——大量网络图片、多个网络视频的轮播、缓存与更新
转载请注明原文地址:http://www.cnblogs.com/ygj0930/p/7742996.html
一:业务场景
基于Android系统的设备上投放广告,诸如:地铁广告屏、自助服务机器上的广告位等。
二:业务难点
广告投放的主要矛盾集中于:广告的本地缓存与及时更新。
广告本地缓存的必要性:图片、视频都是比较吃流量的内容,在不停轮播过程中,如果每展示一张图片、播放一个视频,都实时从服务器拉取,那么广告播多久,流量就消耗多久,这样明显是不合算的。
广告更新的时效性:广告不是一成不变的,往大了说,可以按日期跨度来规划;小了说,可以按每天的时段来规划。这就要求我们播放的广告与服务器进行同步。
三:难点解决思路
1:本地缓存的实现
图片缓存:图片的缓存很容易实现,android有很多图片加载框架,这些框架本身就自带缓存机制。我是采用Glide这个框架,其自带磁盘缓存、内存缓存两级缓存机制,我们无需关心它是怎么缓存图片的。其对缓存内容的访问机制是通过“键值对”的方式——图片url是key,图片内容是value。也就是说:第一次加载时,glide会根据url访问到图片并且缓存到本地,之后再通过该url进行加载时,glide会直接从本地缓存中把图片加载出来。
视频缓存:android的视频播放控件VideoView自带单个视频缓存功能,如果需要循环播放的广告视频只有一个的话,只需用videoview的setLooping(true)即可实现,这样只会在第一次加载视频url时拉取视频内容,之后就不再发生网络请求了。
问题在于,现实中不会全天候循环播放单个视频的,最起码也会根据广告投放的区域、级别,轮播好几个视频,这样的话,videoview的循环播放就不起作用了,每当播放一个新url时都会拉取数据,即使这个视频它不久前还播放过。
有一种笨办法:就是先把要播放的视频下载到sd卡,然后只需轮播下载好的本地视频即可。 这种方案解决了轮播视频时的流量消耗痛点,但是不能满足广告时效性的要求:它需要定期查询服务器,检查本地视频是否最新,如果服务器的广告内容发生了变化,又要手动下载新视频,同时还要处理旧视频,否则手机容量会被不停下载的视频文件挤爆。
最优雅的办法是:使用视频缓存框架,我推荐使用:danikula大神开源的videocache框架。其缓存内容的访问机制也是“键值对”——如果url曾经加载过,则从本地缓存中加载视频。至于缓存内容的管理,框架已经自动帮我们完成——使用LRU算法定期清理。
2:时效性的保证
广告需要定时更新,很多人第一反应就是——使用android的Alarm机制,定时更新内容,这种方案虽然可行,但是太麻烦啦~
上面提到的图片缓存框架、视频缓存框架,都设计一个重要、核心的设计理念——以url为键,以内容为值。
基于这个理念,我们可以通过动态url来达到实时更新缓存内容的目的,至于更新的频率,就看你怎么拼接url了。
按天更新:如果是按日期来更新广告,可以在图片、视频的url后面加上“年月日”,这样的话,就保证了url每日一变,而缓存框架只会在当天第一次加载时拉取数据,后面就直接从本地缓存加载数据了。而之前缓存的内容则会被自动清理掉。
按时段更新:如果是按照一天当中的不同时段来更换播放的广告,则应该先从服务器拉取有什么时段,然后根据当前时间处于那个时段之间,在url后拼接 时段的开始或结束时间 即可。
按日期区间更新:如果是按照日期跨度来更新,比如说2017/01/01~2017/02/03号播放某几个视频。其实这只不过是大概念的时段播放而已,同理,我们先从服务器查询出当前日期处于哪些视频的播放时段之间,然后在url后拼接 起始或终止日期 即可。
按日期+时段更新:综合上面的日期区间、一天当中的时间区间来播放不同广告:拼接 终止日期+时段的终止时间 即可。
实时更新:如果要保证每次播放都是新的,可以拼接随机数。
四:实战举例
0:工具类准备
public class Utils {
//获取当天年月日,作为动态后缀,每天变化一次
public static String getTimeStamp(){
Calendar now = Calendar.getInstance();
String timeStamp = ""+now.get(Calendar.YEAR)+now.get(Calendar.MONTH)+now.get(Calendar.DAY_OF_MONTH);
return timeStamp;
}
}
1:图片的轮播与按日期更新
轮播控件:使用convenientbanner。
图片缓存:使用glide。
1)添加依赖
compile 'com.github.bumptech.glide:glide:3.7.0'
compile 'com.bigkoo:convenientbanner:2.0.5'
2)编写网络图片加载Holder
import android.content.Context;
import android.view.View;
import android.widget.ImageView;
import com.bigkoo.convenientbanner.holder.Holder;
import com.bumptech.glide.Glide; /**
* Created by yeguojian on 2017/10/24.
*/ public class NetworkImageHolderView implements Holder<String> { private ImageView imageView;
@Override
public View createView(Context context) {
//你可以通过layout文件来inflate一个轮播的页面。这里我轮播的页面只有图片,所以直接在代码中创建了
imageView = new ImageView(context);
return imageView;
} @Override
public void UpdateUI(Context context, final int position, String data) {
Glide.with(context).load(data).placeholder(备用图片:网络图片加载失败时显示).into(imageView);
}
}
3)编写轮播页面,这里我是用Fragment实现的
/**
* Created by yeguojian on 2017/9/26.
*/ public class AdvertFragment extends Fragment {
private FrameLayout videoLayout;
private ConvenientBanner convenientBanner;
private List<String> networkImages;
private String[] images;
protected ImageLoader imageLoader; @Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View advert = inflater.inflate(R.layout.advert_fragment,container,false);
images=new String[]{
图片url+"&date="+Utils.getTimeStamp(),......};//这里保存向服务器请求图片的url地址们,在后面拼接时间戳参数来达到每天从服务器拉取一次的目的。
convenientBanner = advert.findViewById(R.id.convenientBanner);
imageLoader = ImageLoader.getInstance();
imageLoader.init(ImageLoaderConfiguration.createDefault(getActivity())); //网络加载图片
networkImages = Arrays.asList(images);
convenientBanner.setPages(new CBViewHolderCreator<NetworkImageHolderView>() {
@Override
public NetworkImageHolderView createHolder() {
return new NetworkImageHolderView();
}
},networkImages)
//设置自动切换(同时设置切换时间间隔)
.startTurning(2000)
//设置是否手动影响(设置了该项无法手动切换)
.setManualPageable(false);
return advert;
}
}
4)图片轮播碎片的布局文件
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_gravity="center"
android:background="#000000"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.bigkoo.convenientbanner.ConvenientBanner
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/convenientBanner"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:canLoop="true"/>
</LinearLayout>
2:多个视频的轮播缓存与按日更新
1)添加依赖:使用androidvideocache音/视频缓存框架
compile 'com.danikula:videocache:2.7.0'
2)视频播放控件使用videoview,具体布局就因项目而异了,这个不影响缓存的实现
3)videoview轮播并缓存网络视频的实现
/**
* Created by yeguojian on 2017/9/26.
*/ public class VendingFragment extends Fragment { private VideoView videoView;
private HttpProxyCacheServer proxy; //视频缓存代理 @Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View vending = inflater.inflate(R.layout.vending_fragment,container,false); //创建缓存代理
proxy = new HttpProxyCacheServer.Builder(getActivity())
.maxCacheSize(1024 * 1024 * 1024) //1Gb 缓存
.maxCacheFilesCount(5)//最大缓存5个视频
.build(); videoView = (VideoView) vending.findViewById(R.id.vending_videoView); videoView.setOnErrorListener(new MediaPlayer.OnErrorListener() {
@Override
public boolean onError(MediaPlayer mp, int what, int extra) {
videoView.stopPlayback(); //播放异常,则停止播放,防止弹窗使界面阻塞
return true;
}
}); playVideoOne();//播放第一个视频 return vending;
} public void playVideoOne(){
String proxyUrl = proxy.getProxyUrl(videoOneUrl+"&date="+Utils.getTimeStamp()); //视频url拼接日期,实现按日更新
videoView.setVideoPath(proxyUrl); //为videoview设置播放路径,而不是设置播放url
videoView.start();
videoView.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
@Override
public void onCompletion(MediaPlayer mPlayer) {
playVideoTwo(); //监听视频一的播放完成事件,播放完毕就播放视频二
}
});
} public void playVideoTwo(){
String proxyUrl = proxy.getProxyUrl(videoTwoUrl+"&date="+Utils.getTimeStamp());
videoView.setVideoPath(proxyUrl);
videoView.start();
videoView.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
@Override
public void onCompletion(MediaPlayer mPlayer) {
playVideoThree();
}
});
}
public void playVideoThree(){
String proxyUrl = proxy.getProxyUrl(videoThreeUrl+"&date="+Utils.getTimeStamp());
videoView.setVideoPath(proxyUrl);
videoView.start();
videoView.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
@Override
public void onCompletion(MediaPlayer mPlayer) {
playVideoOne();//视频三播放完后播放视频一,从而实现轮播
}
});
}
}
Android设备广告投放解决方案——大量网络图片、多个网络视频的轮播、缓存与更新的更多相关文章
- Android高级图片滚动控件,编写3D版的图片轮播器
转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/17482089 大家好,好久不见了,最近由于工作特别繁忙,已经有一个多月的时间没写博 ...
- 转: Android 设备的远程调试入门
从 Windows.Mac 或 Linux 计算机远程调试 Android 设备上的实时内容. 本教程将向您展示如何: 设置您的 Android 设备进行远程调试,并从开发计算机上发现设备. 从您的开 ...
- 仿百度壁纸客户端(二)——主页自定义ViewPager广告定时轮播图
仿百度壁纸客户端(二)--主页自定义ViewPager广告定时轮播图 百度壁纸系列 仿百度壁纸客户端(一)--主框架搭建,自定义Tab + ViewPager + Fragment 仿百度壁纸客户端( ...
- 稳定获取Android设备唯一码(UUID)的解决方案
最近做的一个项目中需要用到Android设备唯一码(UUID)来标识一台设备, Android中设备唯一码有很多,如:MAC地址.IMEI号(DeviceId).IMSI号.ANDROID_ID.序列 ...
- Android 客户端设计之解决方案
解决方案,是正对与需求来谈的.一个抽象的需求,需要一个较为上层抽象的解决方案来处理,这是病和药的关系.但是一个解决方案,可能会包含多个功能,每个功能都是解决方案上的一个节点.一个优秀的解决方案必然需要 ...
- Android闹钟设置的解决方案
Android设置闹钟并不像IOS那样这么简单,做过Android设置闹钟的开发者都知道里面的坑有多深.下面记录一下,我解决Android闹钟设置的解决方案. 主要问题 API19开始AlarmMan ...
- Android设备唯一性判断
前段时间项目需要一个功能,就是在操作完某一个逻辑之后返回给客户一个红包,安全校验团队需要我们提供android设备的唯一标示,起初直接通过获取设备的imei号传给了server端,后台公司云迹监控发现 ...
- android设备休眠机制
如果一开始就对Android手机的硬件架构有一定的了解,设计出的应用程序通常不会成为待机电池杀手,而要设计出正确的通信机制与通信协议也并不困难.但如果不去了解而盲目设计,可就没准了. 首先Androi ...
- Android设备上i-jetty环境的搭建-手机上的web服务器
本文主要跟大家分享如何将一台Android设备打造成一个web服务器使用. 编译i-jetty 1.将源码download下来,http://code.google.com/p/i-jetty/dow ...
随机推荐
- Button 在布局文件中定义监听器,文字阴影,自定义图片,代码绘制样式,添加音效的方法
1.Button自己在xml文件中绑定监听器 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/andro ...
- 使用模拟对象(Mock Object)技术进行测试驱动开发
敏捷开发 敏捷软件开发又称敏捷开发,是一种从上世纪 90 年代开始引起开发人员注意的新型软件开发方法.和传统瀑布式开发方法对比,敏捷开发强调的是在几周或者几个月很短的时间周期,完成相对较小功能,并交付 ...
- Xilinx FFT IP v9.0 使用
该ip用于实现N=2**m(m=3~16)点FFT的变换, 实现的数学类型包含: A) 定点全精度 B) 定点缩减位宽 C) 块浮点 每一级蝶型运算后舍入或者取整.对于N ...
- 浅谈 Boost.Asio 的多线程模型
Boost.Asio 有两种支持多线程的方式,第一种方式比较简单:在多线程的场景下,每个线程都持有一个io_service,并且每个线程都调用各自的io_service的run()方法. 另一种支持多 ...
- [2014亚马逊amazon] 在线笔试题 大于非负整数N的第一个回文数 Symmetric Number
1.题目 如标题,求大于整数N(N>=0)的第一个回文数的字符串表示形式. 这个题目也是当时笔试第一次见到,花了一个小时才做出了.慢慢总结还是挺简单的. 2.分析 分析如下: (1)一位数N(9 ...
- RxJava【过滤】操作符 filter distinct throttle take skip first MD
Markdown版本笔记 我的GitHub首页 我的博客 我的微信 我的邮箱 MyAndroidBlogs baiqiantao baiqiantao bqt20094 baiqiantao@sina ...
- python os.path模块常用方法详解 ZZ
os.path模块主要用于文件的属性获取,在编程中经常用到,以下是该模块的几种常用方法.更多的方法可以去查看官方文档:http://docs.python.org/library/os.path.ht ...
- xgboost入门与实战
xgboost入门与实战(实战调参篇) https://blog.csdn.net/sb19931201/article/details/52577592 前言 前面几篇博文都在学习原理知识,是时候上 ...
- (转)unity使用line renderer画线
原文地址:http://www.xuanyusong.com/archives/561 任何一个无规则曲线它都是有若干个线段组成,及时是圆形它也是又若干个线段组成的,也就是说将若干个线段拼接起来就是我 ...
- UltraISO制作U盘启动盘安装Win7/9/10系统攻略
U盘安装好处就是不用使用笨拙的光盘,光盘还容易出现问题,无法读取的问题.U盘体积小,携带方便,随时都可以制作系统启动盘. U盘建议选择8G及以上大小的. 下面一步一步讲解如果制作U盘安装盘: 1.首先 ...