開發(fā)首屏廣告(Android)簡(jiǎn)述

歡迎Follow我的GitHub, 關(guān)注我的簡(jiǎn)書.

需求:

廣告需求圖

本文的合集已經(jīng)編著成書,高級(jí)Android開發(fā)強(qiáng)化實(shí)戰(zhàn),歡迎各位讀友的建議和指導(dǎo)。在京東即可購買:https://item.jd.com/12385680.html

Android

作為一個(gè)成熟的應(yīng)用, 必須要有廣告. 那么, 如何優(yōu)雅地開發(fā)廣告呢? 需要注意一些細(xì)節(jié).
本文提供一個(gè)簡(jiǎn)單的示例, 代碼僅供參考.

具體來說, 就是

  1. 顯示本地存儲(chǔ)廣告圖片, 點(diǎn)擊圖片, 跳轉(zhuǎn)廣告鏈接, 并提供微信分享功能.
  2. 異步下載廣告信息, 提高啟動(dòng)速度; 異步下載并保存廣告和分享圖片, 提高加載速度.

開發(fā)過程中, 使用了一些小技巧, 我會(huì)詳細(xì)講解注意的要點(diǎn), 包括:
(1) 使用RxAndroid庫, 在新線程上做異步下載廣告信息.
(2) 使用Picasso庫, 異步下載圖片(Bitmap)并存儲(chǔ)至本地.
(3) 使用原生Handler類, 實(shí)現(xiàn)計(jì)時(shí)器功能, 按秒跳轉(zhuǎn)數(shù)字.
(4) 使用WebView視圖, 加載廣告鏈接, 并提供分享功能.

1. 下載廣告

在歡迎頁面中, 啟動(dòng)一個(gè)異步線程, 加載廣告信息, 提高啟動(dòng)速度, 防止網(wǎng)速過慢導(dǎo)致切換卡頓.

    // 異步廣告信息
    private void AsyncCheckInfo() {
        // 異步線程處理監(jiān)聽, 在新線程上監(jiān)聽, 發(fā)送到主線程
        Observable<String> observable = Observable.create(new Observable.OnSubscribe<String>() {
            @Override
            public void call(Subscriber<? super String> subscriber) {
                subscriber.onNext(checkInfo());
                subscriber.onCompleted();
            }
        }).subscribeOn(Schedulers.newThread()).observeOn(AndroidSchedulers.mainThread());

        // 成功回調(diào)
        observable.subscribe(new Subscriber<String>() {
            @Override
            public void onCompleted() {
                Log.i(TAG, "onCompleted");
            }

            @Override
            public void onError(Throwable e) {
            }

            @Override
            public void onNext(String s) {
                Log.i(TAG, "onNext");
            }
        });
    }

在新線程(newThread)中加載, 完成后發(fā)送到主線程(mainThread). 參考.

判斷網(wǎng)絡(luò), 在有網(wǎng)的時(shí)候, 加載廣告信息; 在無網(wǎng)的時(shí)候, 直接略過.

    // 加載廣告信息
    public String checkInfo() {
        if (NetUtils.isNetworkConnected(ChunyuApp.getAppContext())) {
            UpdateUtils.checkDailyInfo(WelcomeActivity.this, mDailyRequestCallback);
            return "Begin to load info.";
        } else {
            return "Stop to load info";
        }
    }

UpdateUtils.checkDailyInfo中, 解析廣告請(qǐng)求的返回值. 如果包含廣告信息, 則存儲(chǔ)在首選項(xiàng)(SharedPreference)中, 下次啟動(dòng)廣告直接讀取; 如果不包含廣告信息, 則設(shè)置無數(shù)據(jù)標(biāo)記, 在使用時(shí)判定無廣告.
最后調(diào)用回調(diào)接口mDailyRequestCallback繼續(xù)處理.

                        ArrayList<Advert> adverts = version.advert;
                        if (adverts.size() > 0) {
                            for (int i = 0; i < adverts.size(); ++i) {
                                Advert advert = adverts.get(i);
                                if (advert.Number == 1) { // Number等于0是廣告
                                    PedometerAdManager.getInstance().init(advert);
                                }
                            }
                        } else {
                            Log.e(TAG, "廣告是空");
                            SharedPreferences sp =
                                    PreferenceManager.getDefaultSharedPreferences(ChunyuApp.getAppContext());
                            sp.edit().putBoolean(WelcomeActivity.FIRST_AD_IS_HAVE_PREFS, false).apply();
                        }

2. 存儲(chǔ)圖片

已經(jīng)存儲(chǔ)廣告信息之后, 即可獲得圖片下載鏈接, 為了提高顯示速度, 下載圖片存儲(chǔ)在本地. 因?yàn)橄螺d屬于網(wǎng)絡(luò)請(qǐng)求, 需要異步處理, 本文使用Picasso庫, 沒有發(fā)明輪子.

    // 日常信息回調(diào)
    private final UpdateUtils.DailyRequestCallback mDailyRequestCallback
            = new UpdateUtils.DailyRequestCallback() {
        @Override
        public void operationExecutedSuccess() {
            if (mAdManager.getImageUrl() != null && !mAdManager.getImageUrl().isEmpty())
                Picasso.with(WelcomeActivity.this).
                        load(mAdManager.getImageUrl()).into(mAdImageTarget);

            if (mAdManager.getShareIcon() != null && !mAdManager.getShareIcon().isEmpty())
                Picasso.with(WelcomeActivity.this).
                        load(mAdManager.getShareIcon()).into(mAdShareImageTarget);
        }

        @Override
        public void operationExecutedFailed() {
            Log.e(TAG, "operationExecutedFailed");
        }
    };

    // 廣告圖片
    private Target mAdImageTarget = new Target() {
        @Override
        public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) {
            String path = FileUtility.savePic(bitmap);
            mPrefs.edit().putString(FIRST_AD_PATH_PREFS, path).apply();
        }

        @Override public void onBitmapFailed(Drawable errorDrawable) {

        }

        @Override public void onPrepareLoad(Drawable placeHolderDrawable) {

        }
    };

    // 分享Icon
    private Target mAdShareImageTarget = new Target() {
        @Override
        public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) {
            String path = FileUtility.savePic(bitmap);
            mPrefs.edit().putString(FIRST_AD_SHARE_IMAGE_URL_PREFS, path).apply();
        }

        @Override public void onBitmapFailed(Drawable errorDrawable) {
        }

        @Override public void onPrepareLoad(Drawable placeHolderDrawable) {

        }
    };

在頁面暫停時(shí), 移除Picasso的請(qǐng)求線程.

    @Override
    protected void onPause() {
        MobclickAgent.onPause(this);
        handler.removeCallbacks(runnable); // 停止
        Picasso.with(this).cancelRequest(mAdImageTarget); // 停止
        Picasso.with(this).cancelRequest(mAdShareImageTarget); // 停止
        super.onPause();
    }

注意: 在Picasso中, Target是和ImageView控件弱綁定, 在銷毀ImageView時(shí), 會(huì)隨之銷毀. 如果未提供ImageView控件, 需要手動(dòng)銷毀請(qǐng)求, 如在onPause中取消. 否則會(huì)出現(xiàn)下載異常. 參考.

3. 顯示廣告

首先Logo頁顯示LOGO_TIME秒, 再判斷顯示引導(dǎo)(首次啟動(dòng))顯示廣告.
顯示廣告是使用存儲(chǔ)在首選項(xiàng)(SharedPreference)中的數(shù)據(jù), 圖片使用本地資源解析, 提高顯示速度.

    // 顯示啟動(dòng)信息
    private void showLaunchInfo() {
        // 顯示一段時(shí)間的主屏Logo
        new Handler().postDelayed(this::showAdInfo, LOGO_TIME);
    }

    // 顯示廣告信息
    private void showAdInfo() {
        // 判斷是否有廣告
        if (mPrefs.getBoolean(FIRST_AD_IS_HAVE_PREFS, false)) {
            Log.e(TAG, "包含廣告");
            String path = mPrefs.getString(FIRST_AD_PATH_PREFS, "");
            if (!path.isEmpty()) {
                int time = mPrefs.getInt(FIRST_AD_TIME_PREFS, 0);
                Bitmap bitmap = BitmapFactory.decodeFile(path);
                Log.e(TAG, "time: " + time);
                showAdImage(bitmap, time);
                if (!NetUtils.isNetworkConnected(ChunyuApp.getAppContext())) {
                    mIvWebImage.setClickable(false);
                }
            } else {
                gotoOtherActivity();
            }
        } else {
            gotoOtherActivity();
        }
    }

顯示的廣告使用上次網(wǎng)絡(luò)請(qǐng)求的存儲(chǔ)數(shù)據(jù), 也可能是本次網(wǎng)絡(luò)請(qǐng)求的, 主要取決于在LOGO_TIME時(shí)間中, 是否下載完成啟動(dòng)信息, 并存儲(chǔ)至本地.

4. 廣告計(jì)時(shí)器

在廣告圖片顯示時(shí), 提供倒計(jì)時(shí)器, 按秒跳時(shí), 提供跳過按鈕直接跳過廣告.

    // 顯示廣告
    private void showAdImage(Bitmap bitmap, int time) {
        mIvWebImage.setVisibility(View.VISIBLE);
        mTvSkip.setVisibility(View.VISIBLE);
        mTvSkip.setOnClickListener(v -> gotoOtherActivity());
        mIvBackground.setVisibility(View.INVISIBLE);
        mIvFirstLogo.setVisibility(View.INVISIBLE);

        mIvWebImage.setImageBitmap(bitmap);
        mAdTime = time + 2;
        handler.post(runnable); // 設(shè)置讀秒
    }

    // 設(shè)置讀秒器
    private int s = 0; // 時(shí)間Delay
    private final Handler handler = new Handler();
    private final Runnable runnable = new Runnable() {
        @Override
        public void run() {
            // handler自帶方法實(shí)現(xiàn)定時(shí)器
            try {
                handler.postDelayed(this, 1000);

                if (s < 1) {
                    s++;
                    return;
                }

                if (s <= (mAdTime - 1)) {
                    mTvSkip.setText(String.valueOf("跳過\n"
                            + Integer.toString((mAdTime - 1) - (s++)) + "秒"));
                }

                // 計(jì)時(shí)器為0時(shí), 開始跳轉(zhuǎn)
                if (s == mAdTime) {
                    gotoOtherActivity();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    };

廣告時(shí)間額外顯示兩秒, 提供頁面跳轉(zhuǎn)間隔, 前一秒后一秒, 保證廣告時(shí)間充足.

廣告頁跳轉(zhuǎn)頁面結(jié)束時(shí), 刪除計(jì)時(shí)回調(diào).

    // 跳轉(zhuǎn)到現(xiàn)實(shí)廣告的視圖
    public void gotoShowAdView(View view) {
        NV.o(this, AdvertisementActivity.class);
        handler.removeCallbacks(runnable);
        finish();
    }
    @Override
    protected void onPause() {
        MobclickAgent.onPause(this);
        handler.removeCallbacks(runnable); // 停止
        Picasso.with(this).cancelRequest(mAdImageTarget); // 停止
        Picasso.with(this).cancelRequest(mAdShareImageTarget); // 停止
        super.onPause();
    }

本文使用handler類, 循環(huán)調(diào)用計(jì)時(shí), 必須在離開頁面時(shí), 清除runnable回調(diào). 否則會(huì)遺忘線程泄露內(nèi)存.

5. 鏈接頁面

點(diǎn)擊廣告圖片, 會(huì)跳轉(zhuǎn)至廣告鏈接, 根據(jù)參數(shù)設(shè)置全屏或者提供分享功能, 把鏈接分享至微信. 微信分享需要標(biāo)題, 內(nèi)容, 圖標(biāo)(Icon), 其中圖片是從服務(wù)器下載后預(yù)存在本地.

/**
 * 廣告Activity
 * <p>
 * Created by wangchenlong on 15/12/2.
 */
public class AdvertisementActivity extends PActivity {

    @SuppressWarnings("unused")
    private static final String TAG = "DEBUG-WCL: "
            + AdvertisementActivity.class.getSimpleName();

    @Bind(R.id.advertise_pwv_container) PedoWebView mPwvContainer;
    @Bind(R.id.advertise_ll_back_home) LinearLayout mLlBackHome;
    @Bind(R.id.advertise_ll_send_session) LinearLayout mLlSendSession;
    @Bind(R.id.advertise_ll_send_timeline) LinearLayout mLlSendTimeline;
    @Bind(R.id.advertise_ll_action_bar) LinearLayout mLlActionBar;

    private SharedPreferences mPrefs;
    private int mFlag; // 判斷分享地點(diǎn)

    private static final int WECHAT_SESSION = 0;    // 微信對(duì)話
    private static final int WECHAT_TIMELINE = 1;   // 朋友圈

    @Override protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_advertisement);
        ButterKnife.bind(this);

        mPrefs = PreferenceManager.getDefaultSharedPreferences(ChunyuApp.getAppContext());

        ActionBar actionBar = getSupportActionBar();
        if (actionBar != null)
            actionBar.setDisplayHomeAsUpEnabled(true);

        // 是否全屏
        if (mPrefs.getBoolean(WelcomeActivity.FIRST_AD_IS_FULL_PREFS, false)) {
            mLlActionBar.setVisibility(View.GONE);
        } else {
            mLlBackHome.setOnClickListener(v -> {
                NV.o(this, PedometerActivity.class);
                finish();
            });

            // 是否分享
            if (mPrefs.getBoolean(WelcomeActivity.FIRST_AD_IS_SHARE_PREFS, false)) {
                mLlSendSession.setOnClickListener(v -> {
                    mFlag = WECHAT_SESSION;
                    shareWechat();
                });
                mLlSendTimeline.setOnClickListener(v -> {
                    mFlag = WECHAT_TIMELINE;
                    shareWechat();
                });
            } else {
                mLlSendSession.setVisibility(View.GONE);
                mLlSendTimeline.setVisibility(View.GONE);
            }
        }

        mPwvContainer.loadUrl(mPrefs.getString(WelcomeActivity.FIRST_AD_URL_PREFS, ""));
    }

    // 分享到微信
    public void shareWechat() {
        IWXAPI wxapi =
                WXAPIFactory.createWXAPI(ChunyuApp.getAppContext(), SNSConst.WX_APP_ID_ONLINE, true);
        wxapi.registerApp(SNSConst.WX_APP_ID_ONLINE);

        WXWebpageObject webpage = new WXWebpageObject();
        webpage.webpageUrl = mPrefs.getString(WelcomeActivity.FIRST_AD_URL_PREFS, "");
        WXMediaMessage msg = new WXMediaMessage(webpage);
        msg.title = mPrefs.getString(WelcomeActivity.FIRST_AD_SHARE_TITLE_PREFS, "");
        msg.description = mPrefs.getString(WelcomeActivity.FIRST_AD_SHARE_CONTENT_PREFS, "");

        String path = mPrefs.getString(WelcomeActivity.FIRST_AD_SHARE_IMAGE_URL_PREFS, "");
        if (!path.isEmpty()) {
            Bitmap bitmap = BitmapFactory.decodeFile(path);
            if (bitmap != null) {
                msg.setThumbImage(bitmap);
            } else {
                msg.setThumbImage(BitmapFactory.decodeResource(getResources(), R.drawable.icon));
            }
            SendMessageToWX.Req req = new SendMessageToWX.Req();
            req.transaction = String.valueOf(System.currentTimeMillis());
            req.message = msg;
            req.scene = ((mFlag == 0) ?
                    SendMessageToWX.Req.WXSceneSession : SendMessageToWX.Req.WXSceneTimeline);
            wxapi.sendReq(req);
        }
    }

    @Override public void onBackPressed() {
        if (mPwvContainer.canGoBack()) {
            mPwvContainer.goBack();
        } else {
            NV.o(this, PedometerActivity.class);
            finish();
        }
    }
}

調(diào)用后退按鈕(onBackPressed): 在網(wǎng)頁跳轉(zhuǎn)多頁時(shí), 返回上一頁; 在首頁時(shí), 退出廣告頁面, 跳轉(zhuǎn)主頁. 微信分享的圖標(biāo)(Icon), 最好使用方形全圖, 否則透明部分會(huì)被黑色替代, 服務(wù)器提供圖片時(shí)需要注意.

最終效果:

動(dòng)畫效果

OK, 廣告頁面開發(fā)完成了, 可以開心的賺錢了! Enjoy It.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 178,812評(píng)論 25 709
  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫、插件、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 15,086評(píng)論 4 61
  • 本文的作者是看房狗原創(chuàng)志愿者小玄子,房子是租來的,但生活不是。她在租房過程中惹了一朵爛桃花,哭笑不得,不過租房中遇...
    看房狗閱讀 404評(píng)論 0 0
  • 1、感謝真我近期安排那么多功課來到我,考驗(yàn)我修得如何 2、感謝自己每天堅(jiān)持認(rèn)真地活在功課中,把自己鉚釘在線上,和平...
    張艾雯閱讀 515評(píng)論 0 0
  • 來源:中國(guó)法院網(wǎng)重慶五中院|作者:馬建偉劉建偉 【案情】 根據(jù)相關(guān)政策規(guī)定,重慶市于1999年7月開始實(shí)施住房分配...
    小好閱讀 466評(píng)論 0 2

友情鏈接更多精彩內(nèi)容