Android設備廣告投放解決方案——大量網絡圖片、多個網絡視頻的輪播、緩存與更新

    轉載請註明原文地址:http://www.cnblogs.com/ygj0930/p/7742996.htmlhtml

    

    一:業務場景android

    基於Android系統的設備上投放廣告,諸如:地鐵廣告屏、自助服務機器上的廣告位等。git

 

    二:業務難點github

    廣告投放的主要矛盾集中於:廣告的本地緩存與及時更新。算法

    廣告本地緩存的必要性:圖片、視頻都是比較吃流量的內容,在不停輪播過程當中,若是每展現一張圖片、播放一個視頻,都實時從服務器拉取,那麼廣告播多久,流量就消耗多久,這樣明顯是不合算的。緩存

    廣告更新的時效性:廣告不是一成不變的,往大了說,能夠按日期跨度來規劃;小了說,能夠按天天的時段來規劃。這就要求咱們播放的廣告與服務器進行同步。服務器

 

    三:難點解決思路網絡

    1:本地緩存的實現app

    圖片緩存:圖片的緩存很容易實現,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();//視頻三播放完後播放視頻一,從而實現輪播
            }
        });
    }
}
相關文章
相關標籤/搜索