轉載請註明原文地址: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();//視頻三播放完後播放視頻一,從而實現輪播 } }); } }