Android自動更新:這里的更新靜悄悄~
產(chǎn)品:APP的底部按鈕能夠做到自動更新嗎?
累人猿:有些麻煩(無辜臉)~
產(chǎn)品:那京東、美團(tuán)是怎么做到的?
累人猿:……
心疼自己十分鐘~
經(jīng)??吹接惺裁椿顒拥臅r候,京東、淘寶、美團(tuán)這些應(yīng)用都不需要更新應(yīng)用就能夠?qū)崿F(xiàn)應(yīng)用UI的更新,特別是底部菜單按鈕的圖標(biāo),及時的營造出活動的氣氛。作為一個移動開發(fā)者都想弄明白他們是怎么做到的,我所了解的方式有以下三種:
1、使用前端框架,如果是這種情況的話,整個應(yīng)用都沒有多少原生的東西,比較火的框架:React Native、ionic等;
2、純原生,這種方式是通過圖片下載,本地圖片讀取,動態(tài)生成StateListDrawable,大致這樣一個流程實現(xiàn)不更新應(yīng)用就更新底部按鈕的圖標(biāo);
3、JS交互:將整個底部按鈕做成H5,然后使用webview加載,這也是今天的重點。
其中第二和第三種方式,必須得做好容錯處理,比如網(wǎng)絡(luò)問題導(dǎo)致圖片下載失敗,本地圖片丟失等問題,為了避免這些問題導(dǎo)致應(yīng)用的不正常運行,我們需要有備選方案,一旦問題出現(xiàn),就放棄整個流程,從本地讀取資源。
我并不是前端開發(fā)者,h5相關(guān)的東西學(xué)得不多,所以在實現(xiàn)這個功能的過程中還是花了一些功夫的,JS和CSS用得也不熟,所以在處理有些細(xì)節(jié)的時候,用得方式有些暴力,希望大伙兒能夠理解,有考慮使用這種方式的小伙伴可以讓h5開發(fā)的小伙伴來做。
整個功能是基于JS交互這個基礎(chǔ)上的,至于需要怎么交互,我就不做過多的講解了,不清楚的小伙伴可以去網(wǎng)上找一些資料來學(xué)學(xué)。
這里的排版沒有弄好,小伙伴們可以到這里去看這篇文章:Android自動更新:這里的更新靜悄悄~
一、CSS布局
整個底部按鈕的界面,都是一些html標(biāo)簽,我使用了比較經(jīng)典的四個按鈕,如果有特別需求的小伙伴,可以自己增加或者刪除幾個標(biāo)簽,源碼如下:
<div>
<label onclick="myFun('img_one')">
<input type="radio" id="one" name="tabBtn" checked>
<img id="img_one">
<p style="color: #f00;"></p>
</label>
<label onclick="myFun('img_two')">
<input type="radio" id="two" name="tabBtn">
<img id="img_two">
<p></p>
</label>
<label onclick="myFun('img_three')">
<input type="radio"id="three"name="tabBtn">
<img id="img_three">
<p></p>
</label>
<label onclick="myFun('img_four')">
<input type="radio"id="four"name="tabBtn">
<img id="img_four">
<p></p>
</label>
</div>
當(dāng)然,要實現(xiàn)整個界面方式不止一種,我采用的是radio的方式,接著我們講講css中比較關(guān)鍵的地方,第一label的p標(biāo)簽設(shè)置了文字顏色為紅色,是為了初始化,一般應(yīng)進(jìn)入應(yīng)用第一個按鈕是選中狀態(tài),當(dāng)然這個工作可以放在JS中來做,和圖片初始化一起做,如果設(shè)計不是紅色的小伙伴就要注意了,在使用的時候需要修改成設(shè)計要求的顏色。
label的CSS代碼:
label{
margin-top:0px;
margin-bottom: -3px;
padding-top:5px;
display:inline-block;
width:25%;
font-weight:normal;
vertical-align:middle;
cursor:pointer;
float:left;
text-align:center;
}
一定要注意25%,因為是四個按鈕,我們需要平分100%,所以每個按鈕的范圍是25%,如果按鈕的個數(shù)修改一定要注意這個百分比的修改。
因為使用的webview加載h5的方式,細(xì)心的小伙伴會發(fā)現(xiàn),我長按某個按鈕的時候可以復(fù)制文本的,所以為了禁用復(fù)制功能,我們需要在CSS中做一些限制
/*禁止長按復(fù)制功能
*/*{
-webkit-touch-callout:none;
-webkit-user-select:none;
-khtml-user-select:none;
-moz-user-select:none;
-ms-user-select:none;
user-select:none;
}
其實就做了一件事,需要這么些代碼是因為兼容不懂瀏覽器內(nèi)核。關(guān)于div、p、body、input的CSS代碼這里就不詳細(xì)講解了,這幾個標(biāo)簽的CSS代碼可以不做任何修改,直接使用,如果要改的話可以讓h5的小伙伴幫忙改得更規(guī)范一些。
二、JS交互
不管是ios還是android都提供了Webview和h5交互的方法。
我們先來看看本地提供給JS的接口方法:
/*
js調(diào)用本地的方法改變底部按鈕的選中狀態(tài)原理:js調(diào)用本地方法,方法內(nèi)部通過廣播的方式傳遞選中按鈕的編號
*/@JavascriptInterface
public voidchangeTab(String index) {
Intent intent =newIntent();
intent.setAction("ChangeTab");
intent.putExtra("index", index);
context.sendBroadcast(intent);
}
/*將Json數(shù)組傳遞給h5頁面
*/@JavascriptInterface
publicString getImages(List lists) {
try{
JSONArray array =newJSONArray();
for(Item item : lists) {
JSONObject object =newJSONObject();
object.put("icon_nor", item.getIcon_nor());
object.put("icon_sel", item.getIcon_sel());
object.put("title", item.getTitle());
array.put(object);
}
String json = array.toString();
returnjson;
}catch(Exception e) {
e.printStackTrace();
}
return"";
}
第一個方法的作用是,在h5界面點擊某個按鈕的使用,js調(diào)用本地的方法實現(xiàn)原生fragment的切換,我使用了廣播的方式,來通知原生界面的切換操作。
第二個方法,是為了初始化h5界面,通過js調(diào)用這個方法,將數(shù)據(jù)傳遞給h5頁面,達(dá)到初始化界面的效果。
其中Item是我將每個按鈕封裝成了一個對象,這樣方便數(shù)據(jù)的讀取和傳遞,源碼如下:
public classItem {
privateStringicon_nor;//圖標(biāo)正常狀態(tài)privateStringicon_sel;//圖標(biāo)選中狀態(tài)privateStringtitle;//按鈕文案publicString getIcon_nor() {
returnicon_nor;
}
public voidsetIcon_nor(String icon_nor) {
this.icon_nor= icon_nor;
}
publicString getIcon_sel() {
returnicon_sel;
}
public voidsetIcon_sel(String icon_sel) {
this.icon_sel= icon_sel;
}
publicString getTitle() {
returntitle;
}
public voidsetTitle(String title) {
this.title= title;
}
}
其實這個對象中還應(yīng)該增加一個顏色變量,這樣的話,我們不需要修改h5的顏色,直接通過傳值來改變顏色,有興趣的小伙伴可以嘗試一下,一定要記得在Js中獲取顏色和應(yīng)用顏色。
不如我們先來看看應(yīng)用截圖吧!

這是最正常的情況,也就是說,服務(wù)器正常開啟,圖片地址正確傳遞。

這種情況是服務(wù)器關(guān)閉的情況,看著并沒有什么區(qū)別,因為我做了容錯處理,其實這種情況圖片是讀取失敗的,如果沒有容錯處理,圖片會顯示一個錯誤圖片的圖標(biāo)。

這種情況是,我故意少傳了一張圖片的運行界面,細(xì)心的小伙伴會發(fā)現(xiàn)這張圖中的tab后面的編號跟前面一張的圖片不一樣,其實這也算是一種保底的做法,一旦出錯,就放棄方法,采用本地的不就方案,保證應(yīng)用的運行沒有問題。
繼續(xù)回到源碼分析,在點擊某個tab的時候,相應(yīng)ui也應(yīng)該發(fā)生變化,還記得剛剛我們在原生提供的第一個方法嗎,現(xiàn)在就是使用它的時候,
/*通過js調(diào)用本地方法,改變tab選中狀態(tài)
*/varoneRB=document.getElementById("one");
oneRB.addEventListener('click',function() {
if(oneRB.checked) {
appNative.changeTab('one');
}
},false);
vartwoRB=document.getElementById("two");
twoRB.addEventListener('click',function() {
if(twoRB.checked) {
appNative.changeTab('two');
}
},false);
varthreeRB=document.getElementById("three");
threeRB.addEventListener('click',function() {
if(threeRB.checked) {
appNative.changeTab('three');
}
},false);
varfourRB=document.getElementById("four");
fourRB.addEventListener('click',function() {
if(fourRB.checked) {
appNative.changeTab('four');
}
},false);
啟動應(yīng)用的時候,我們需要初始化整個tab界面,第二個方法排上用場了,
/*初始化數(shù)據(jù),為了排除網(wǎng)絡(luò)等不確定因素,需要在images文件夾下面放一套缺省圖標(biāo)
*/vardatas;
varisOk=true;
varicons=newArray();
varicon1=newObject();
icon1.icon_sel="images/icon_tab_one_sel.png";
icon1.icon_nor="images/icon_tab_one_nor.png";
icon1.title="tab0";
icons.push(icon1);
varicon2=newObject();
icon2.icon_sel="images/icon_tab_two_sel.png";
icon2.icon_nor="images/icon_tab_two_nor.png";
icon2.title="tab1";
icons.push(icon2);
varicon3=newObject();
icon3.icon_sel="images/icon_tab_three_sel.png";
icon3.icon_nor="images/icon_tab_three_nor.png";
icon3.title="tab2";
icons.push(icon3);
varicon4=newObject();
icon4.icon_sel="images/icon_tab_four_sel.png";
icon4.icon_nor="images/icon_tab_four_nor.png";
icon4.title="tab3";
icons.push(icon4);
varoImg=document.getElementsByTagName('img');
functiontranData(jsondata) {
datas=eval(jsondata);
if(isEmpty(jsondata)){
isOk=false;
}else{
for(i=0;i<datas.length;i++){
if(typeofdatas[i].icon_sel==="undefined"||typeofdatas[i].icon_nor==="undefined"){
isOk=false;
}
}
}
if(isOk){
oImg[0].src=datas[0].icon_sel;
oImg[0].nextSibling.nextSibling.innerText=datas[0].title;
oImg[0].onerror=function(){
oImg[0].src=icons[0].icon_sel;
isOk=false;
}
oImg[1].src=datas[1].icon_nor;
oImg[1].nextSibling.nextSibling.innerText=datas[1].title;
oImg[1].onerror=function(){
oImg[1].src=icons[1].icon_nor;
isOk=false;
}
oImg[2].src=datas[2].icon_nor;
oImg[2].nextSibling.nextSibling.innerText=datas[2].title;
oImg[2].onerror=function(){
oImg[2].src=icons[2].icon_nor;
isOk=false;
}
oImg[3].src=datas[3].icon_nor;
oImg[3].nextSibling.nextSibling.innerText=datas[3].title;
oImg[3].onerror=function(){
oImg[3].src=icons[3].icon_nor;
isOk=false;
}
}else{
oImg[0].src=icons[0].icon_sel;
oImg[0].nextSibling.nextSibling.innerText=icons[0].title;
oImg[1].src=icons[1].icon_nor;
oImg[1].nextSibling.nextSibling.innerText=icons[1].title;
oImg[2].src=icons[2].icon_nor;
oImg[2].nextSibling.nextSibling.innerText=icons[2].title;
oImg[3].src=icons[3].icon_nor;
oImg[3].nextSibling.nextSibling.innerText=icons[3].title;
}
}
這段處理有些復(fù)雜,因為需要考慮容錯方法,所以我建議小伙伴們,把整個html放在app的assets文件夾下面,不要放在服務(wù)端,同時我們還要再assets文件夾下面放一套默認(rèn)圖標(biāo),我個人認(rèn)為圖標(biāo)更新失敗,比應(yīng)用不能夠正常使用要有好的多,建議采用如下的方式,使用這種方案,主要的思路就是不管什么原因?qū)е逻h(yuǎn)程圖片讀取失敗,都直接使用本地的缺省圖片,還要注意,是整套圖片都使用本地的,不能夠某一張讀取失敗了,只替換某一張噻~

建議使用的小伙伴不要把你們的圖片改成截圖中的名字,這樣就不需要再html源碼中修改。
然后就是處理某個tab選中之后的樣式修改了。
/*改變選中按鈕的樣式和狀態(tài)
*/functionmyFun(sId) {
for(vari=0;i<oImg.length;i++) {
iconSelect(i,sId);
}
}
functioniconSelect(i, sId){
if(oImg[i].id== sId) {
oImg[i].previousSibling.previousSibling.checked=true;
oImg[i].nextSibling.nextSibling.style.color="red";
if(isOk){
oImg[i].src=datas[i].icon_sel;
}else{
oImg[i].src=icons[i].icon_sel;
}
}else{
if(isOk){
oImg[i].src=datas[i].icon_nor;
}else{
oImg[i].src=icons[i].icon_nor;
}
oImg[i].nextSibling.nextSibling.style.color="black";
}
}
另外,有時候我們會遇到在某個具體的fragment中點擊某個按鈕跳到其它tab頁面中去,比如在fragment中我點擊了按鈕,需要跳到第二個tab頁面,所以我通過js提供了一個切換tab的方法。
/*提供給原生的方法,原生調(diào)用該方法,實現(xiàn)設(shè)置某個tab選中
*/functionsetChecked(id) {
varbtn=document.getElementById(id);
btn.checked=true;
if(typeofappNative !=="undefined"&& appNative.changeTab) {
appNative.changeTab(id);
myFun("img_"+ id);
}
}
使用的時候只需要原生中調(diào)用,方法如下:
webView.loadUrl("javascript:setChecked('four')");
其中的”four”對應(yīng)某個需要跳轉(zhuǎn)到的tab編號,注意只能是“one”、“two”、“three”、“four”
前面就是整個功能的js代碼,我知道,有些功能是完全可以用CSS來實現(xiàn)的,CSS我還不是很熟,所以暴力的使用了JS來處理細(xì)節(jié),并且我并沒有使用JQuery這些簡單的語法結(jié)構(gòu),沒有別的原因,我還不會~
三、原生調(diào)用
講了CSS和JS,接著應(yīng)該講講怎么在原生中調(diào)用。
首先加載整個tab的html頁面
webView.loadUrl("file:///android_asset/radioGroup.html");
當(dāng)然初始化數(shù)據(jù)是少不了的
/*將圖片地址和按鈕文案傳遞給h5頁面
*/private voidinitData() {
lists=newArrayList<>();
Item item =newItem();
item.setIcon_nor("http://192.168.111.20/drawable-hdpi/icon_tab_one_nor.png");
item.setIcon_sel("http://192.168.111.20/drawable-hdpi/icon_tab_one_sel.png");
//item.setIcon_sel("http://192.168.111.20/drawable-hdpi/push.png");item.setTitle("tab1");
Item item1 =newItem();
item1.setIcon_nor("http://192.168.111.20/drawable-hdpi/icon_tab_two_nor.png");
item1.setIcon_sel("http://192.168.111.20/drawable-hdpi/icon_tab_two_sel.png");
item1.setTitle("tab2");
Item item2 =newItem();
item2.setIcon_nor("http://192.168.111.20/drawable-hdpi/icon_tab_three_nor.png");
item2.setIcon_sel("http://192.168.111.20/drawable-hdpi/icon_tab_three_sel.png");
item2.setTitle("tab3");
Item item3 =newItem();
item3.setIcon_nor("http://192.168.111.20/drawable-hdpi/icon_tab_four_nor.png");
item3.setIcon_sel("http://192.168.111.20/drawable-hdpi/icon_tab_four_sel.png");
item3.setTitle("tab4");
lists.add(item);
lists.add(item1);
lists.add(item2);
lists.add(item3);
}
然后需要通過js將數(shù)據(jù)傳遞給html頁面
webView.setWebViewClient(newWebViewClient() {
@Override
public booleanshouldOverrideUrlLoading(WebView view, String url) {
return super.shouldOverrideUrlLoading(view, url);
}
@Override
public voidonLoadResource(WebView view, String url) {
super.onLoadResource(view, url);
}
@Override
public voidonPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
webView.loadUrl("javascript:tranData("+javaScriptInterface.getImages(lists) +")");
//webView.loadUrl("javascript:setChecked('four')");}
});
建議在onPageFinished這個方法里面調(diào)用js方法,這樣不容易出錯。
至于原生是怎么處理tab頁面的切換的,我就不講了,有興趣的小伙伴可以把源碼下載下來看一看,當(dāng)然自己改改是最好的方式。
傳送門開啟:http://download.csdn.net/detail/zhimingshangyan/9648669
我的實現(xiàn)思路講完了,文章的開篇我提供了三種實現(xiàn)方式,第二和第三兩種方式我都實現(xiàn)了,當(dāng)然第二種方式我沒有整理,歡迎小伙伴提供你們的思路給我,同時有什么問題和值得改進(jìn)的地方小伙伴可以留言給我。