• 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();//视频三播放完后播放视频一,从而实现轮播
                }
            });
        }
    }
  • 相关阅读:
    spring的常用配置
    aop切入点表达式
    代理模式
    hibernate的常用配置
    正则表达式
    Java配置
    性能提升
    创建vue3 项目
    manjaro
    单调队列
  • 原文地址:https://www.cnblogs.com/ygj0930/p/7742996.html
Copyright © 2020-2023  润新知