編輯:關於Android編程
作為一個成熟的應用, 必須要有廣告. 那麼, 如何優雅地開發廣告呢? 需要注意一些細節.
本文提供一個簡單的示例, 代碼僅供參考.
需求:
vc/C1NiyorGjtOa547jmus231s/tzbzGrCwgzOG437zT1NjL2bbILjwvcD4NCjxwPr+qt6K5/bPM1tAsIMq508PBy9K70KnQoby8x8ksIM7Su+HP6s+4vbK94tei0uK1xNKqteMsILD8wKg6PGJyIC8+DQooMSkgyrnTw1J4QW5kcm9pZL/iLCDU2tDCz9+zzMnP1/bS7LK9z8LU2LnjuObQxc+iLjxiciAvPg0KKDIpIMq508NQaWNhc3Nvv+IsINLssr3PwtTYzbzGrChCaXRtYXApsqK05rSi1sGxvrXYLjxiciAvPg0KKDMpIMq508PUrcn6SGFuZGxlcsDgLCDKtc/WvMbKscb3uabE3CwgsLTD68z416rK/dfWLjxiciAvPg0KKDQpIMq508NXZWJWaWV3ytPNvCwgvNPU2LnjuObBtL3TLCCyoszhuam31s/tuabE3C48L3A+DQo8aDMgaWQ9"1-下載廣告">1. 下載廣告
在歡迎頁面中, 啟動一個異步線程, 加載廣告信息, 提高啟動速度, 防止網速過慢導致切換卡頓.
// 異步廣告信息
private void AsyncCheckInfo() {
// 異步線程處理監聽, 在新線程上監聽, 發送到主線程
Observable observable = Observable.create(new Observable.OnSubscribe() {
@Override
public void call(Subscriber subscriber) {
subscriber.onNext(checkInfo());
subscriber.onCompleted();
}
}).subscribeOn(Schedulers.newThread()).observeOn(AndroidSchedulers.mainThread());
// 成功回調
observable.subscribe(new Subscriber() {
@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 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();
}
已經存儲廣告信息之後, 即可獲得圖片下載鏈接, 為了提高顯示速度, 下載圖片存儲在本地. 因為下載屬於網絡請求, 需要異步處理, 本文使用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
中取消. 否則會出現下載異常. 參考.
首先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
時間中, 是否下載完成啟動信息, 並存儲至本地.
在廣告圖片顯示時, 提供倒計時器
, 按秒跳時, 提供跳過按鈕
直接跳過廣告.
// 顯示廣告
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
回調. 否則會遺忘線程洩露內存.
點擊廣告圖片, 會跳轉至廣告鏈接, 根據參數設置全屏或者提供分享功能, 把鏈接分享至微信. 微信分享需要標題, 內容, 圖標(Icon), 其中圖片是從服務器下載後預存在本地.
/**
* 廣告Activity
*
* 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.
從今天開始,把看書時候的知識點整理成博客, 這個比較簡單,估計有經驗的都用過,weight屬性 在做Android布局的時候,經常遇到需要幾個控件按比例分配空間的情況
前言本次主要是實現一個Android應用,實現靜態廣播、動態廣播兩種改變 widget內容的方法,即在上篇博文中實驗的基礎上進行修改,所以此次實驗的重點是AppWidge
今天又開始我的App開發,因為之前一直做的是SDK,所以涉及到界面UI很少,剛開始做Android應用的時候,沒有對dp,px,sp等概念有一個深入的了解,只知道他們之間
筆者近2天在 Android Studio上玩了一下百度地圖,碰到了常見的230錯誤 APP Scode校驗失敗,下面我來介紹一下具體的解決辦法. 1.在andriod