1

开发首屏广告(Android)简述

 2 years ago
source link: http://www.androidchina.net/4079.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

作为一个成熟的应用, 必须要有广告. 那么, 如何优雅地开发广告呢? 需要注意一些细节.
本文提供一个简单的示例, 代码仅供参考.

需求:
广告需求图

具体来说, 就是

1. 显示本地存储广告图片, 点击图片, 跳转广告链接, 并提供微信分享功能.

2. 异步下载广告信息, 提高启动速度; 异步下载并保存广告和分享图片, 提高加载速度.

开发过程中, 使用了一些小技巧, 我会详细讲解注意的要点, 包括:

(1) 使用RxAndroid库, 在新线程上做异步下载广告信息.

(2) 使用Picasso库, 异步下载图片(Bitmap)并存储至本地.

(3) 使用原生Handler类, 实现计时器功能, 按秒跳转数字.

(4) 使用WebView视图, 加载广告链接, 并提供分享功能.

1. 下载广告

在欢迎页面中, 启动一个异步线程, 加载广告信息, 提高启动速度, 防止网速过慢导致切换卡顿.

// 异步广告信息
private void AsyncCheckInfo() {
// 异步线程处理监听, 在新线程上监听, 发送到主线程
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());
// 成功回调
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)中加载, 完成后发送到主线程(mainThread). 参考.

判断网络, 在有网的时候, 加载广告信息; 在无网的时候, 直接略过.

// 加载广告信息
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中, 解析广告请求的返回值. 如果包含广告信息, 则存储在首选项(SharedPreference)中, 下次启动广告直接读取; 如果不包含广告信息, 则设置无数据标记, 在使用时判定无广告.
最后调用回调接口mDailyRequestCallback继续处理.

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. 存储图片

已经存储广告信息之后, 即可获得图片下载链接, 为了提高显示速度, 下载图片存储在本地. 因为下载属于网络请求, 需要异步处理, 本文使用Picasso库, 没有发明轮子.

// 日常信息回调
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) {
}
};

在页面暂停时, 移除Picasso的请求线程.

@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时, 会随之销毁. 如果未提供ImageView控件, 需要手动销毁请求, 如在onPause中取消. 否则会出现下载异常参考.

3. 显示广告

首先Logo页显示LOGO_TIME秒, 再判断显示引导(首次启动)显示广告.
显示广告是使用存储在首选项(SharedPreference)中的数据, 图片使用本地资源解析, 提高显示速度.

// 显示启动信息
private void showLaunchInfo() {
// 显示一段时间的主屏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();
}
}

显示的广告使用上次网络请求的存储数据, 也可能是本次网络请求的, 主要取决于在LOGO_TIME时间中, 是否下载完成启动信息, 并存储至本地.

4. 广告计时器

在广告图片显示时, 提供倒计时器, 按秒跳时, 提供跳过按钮直接跳过广告.

// 显示广告
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); // 设置读秒
}
// 设置读秒器
private int s = 0; // 时间Delay
private final Handler handler = new Handler();
private final Runnable runnable = new Runnable() {
@Override
public void run() {
// handler自带方法实现定时器
try {
handler.postDelayed(this, 1000);
if (s < 1) {
s++;
return;
}
if (s <= (mAdTime - 1)) {
mTvSkip.setText(String.valueOf("跳过\n"
+ Integer.toString((mAdTime - 1) - (s++)) + "秒"));
}
// 计时器为0时, 开始跳转
if (s == mAdTime) {
gotoOtherActivity();
}
} catch (Exception e) {
e.printStackTrace();
}
}
};

广告时间额外显示两秒, 提供页面跳转间隔, 前一秒后一秒, 保证广告时间充足.

广告页跳转页面结束时, 删除计时回调.

// 跳转到现实广告的视图
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类, 循环调用计时, 必须在离开页面时, 清除runnable回调. 否则会遗忘线程泄露内存.

5. 链接页面

点击广告图片, 会跳转至广告链接, 根据参数设置全屏或者提供分享功能, 把链接分享至微信. 微信分享需要标题, 内容, 图标(Icon), 其中图片是从服务器下载后预存在本地.

/**
* 广告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; // 判断分享地点
private static final int WECHAT_SESSION = 0;    // 微信对话
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();
}
}
}

调用后退按钮(onBackPressed): 在网页跳转多页时, 返回上一页; 在首页时, 退出广告页面, 跳转主页. 微信分享的图标(Icon), 最好使用方形全图, 否则透明部分会被黑色替代, 服务器提供图片时需要注意.

OK, 广告页面开发完成了, 可以开心的赚钱了! Enjoy It.

转载请注明:Android开发中文站 » 开发首屏广告(Android)简述


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK