Android自动更新:这里的更新静悄悄~

Android自动更新:这里的更新静悄悄~
产品:APP的底部按钮能够做到自动更新吗?

累人猿:有些麻烦(无辜脸)~

产品:那京东、美团是怎么做到的?

累人猿:……

心疼自己十分钟~

经常看到有什么活动的时候,京东、淘宝、美团这些应用都不需要更新应用就能够实现应用UI的更新,特别是底部菜单按钮的图标,及时的营造出活动的气氛。作为一个移动开发者都想弄明白他们是怎么做到的,我所了解的方式有以下三种:

1、使用前端框架,如果是这种情况的话,整个应用都没有多少原生的东西,比较火的框架:React Native、ionic等;

2、纯原生,这种方式是通过图片下载,本地图片读取,动态生成StateListDrawable,大致这样一个流程实现不更新应用就更新底部按钮的图标;

3、JS交互:将整个底部按钮做成H5,然后使用webview加载,这也是今天的重点。

其中第二和第三种方式,必须得做好容错处理,比如网络问题导致图片下载失败,本地图片丢失等问题,为了避免这些问题导致应用的不正常运行,我们需要有备选方案,一旦问题出现,就放弃整个流程,从本地读取资源。

我并不是前端开发者,h5相关的东西学得不多,所以在实现这个功能的过程中还是花了一些功夫的,JS和CSS用得也不熟,所以在处理有些细节的时候,用得方式有些暴力,希望大伙儿能够理解,有考虑使用这种方式的小伙伴可以让h5开发的小伙伴来做。

整个功能是基于JS交互这个基础上的,至于需要怎么交互,我就不做过多的讲解了,不清楚的小伙伴可以去网上找一些资料来学学。

这里的排版没有弄好,小伙伴们可以到这里去看这篇文章:Android自动更新:这里的更新静悄悄~

一、CSS布局

整个底部按钮的界面,都是一些html标签,我使用了比较经典的四个按钮,如果有特别需求的小伙伴,可以自己增加或者删除几个标签,源码如下:

<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>

当然,要实现整个界面方式不止一种,我采用的是radio的方式,接着我们讲讲css中比较关键的地方,第一label的p标签设置了文字颜色为红色,是为了初始化,一般应进入应用第一个按钮是选中状态,当然这个工作可以放在JS中来做,和图片初始化一起做,如果设计不是红色的小伙伴就要注意了,在使用的时候需要修改成设计要求的颜色。

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%,如果按钮的个数修改一定要注意这个百分比的修改。

因为使用的webview加载h5的方式,细心的小伙伴会发现,我长按某个按钮的时候可以复制文本的,所以为了禁用复制功能,我们需要在CSS中做一些限制

/*禁止长按复制功能

*/*{

-webkit-touch-callout:none;

-webkit-user-select:none;

-khtml-user-select:none;

-moz-user-select:none;

-ms-user-select:none;

user-select:none;

}

其实就做了一件事,需要这么些代码是因为兼容不懂浏览器内核。关于div、p、body、input的CSS代码这里就不详细讲解了,这几个标签的CSS代码可以不做任何修改,直接使用,如果要改的话可以让h5的小伙伴帮忙改得更规范一些。

二、JS交互

不管是ios还是android都提供了Webview和h5交互的方法。

我们先来看看本地提供给JS的接口方法:

/*

js调用本地的方法改变底部按钮的选中状态原理:js调用本地方法,方法内部通过广播的方式传递选中按钮的编号

*/@JavascriptInterface

public voidchangeTab(String index) {

Intent intent =newIntent();

intent.setAction("ChangeTab");

intent.putExtra("index", index);

context.sendBroadcast(intent);

}

/*将Json数组传递给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调用本地的方法实现原生fragment的切换,我使用了广播的方式,来通知原生界面的切换操作。

第二个方法,是为了初始化h5界面,通过js调用这个方法,将数据传递给h5页面,达到初始化界面的效果。

其中Item是我将每个按钮封装成了一个对象,这样方便数据的读取和传递,源码如下:

public classItem {

privateStringicon_nor;//图标正常状态privateStringicon_sel;//图标选中状态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;

}

}

其实这个对象中还应该增加一个颜色变量,这样的话,我们不需要修改h5的颜色,直接通过传值来改变颜色,有兴趣的小伙伴可以尝试一下,一定要记得在Js中获取颜色和应用颜色。

不如我们先来看看应用截图吧!


运行截屏

这是最正常的情况,也就是说,服务器正常开启,图片地址正确传递。


服务器关闭截图

这种情况是服务器关闭的情况,看着并没有什么区别,因为我做了容错处理,其实这种情况图片是读取失败的,如果没有容错处理,图片会显示一个错误图片的图标。
图片地址丢失截图

这种情况是,我故意少传了一张图片的运行界面,细心的小伙伴会发现这张图中的tab后面的编号跟前面一张的图片不一样,其实这也算是一种保底的做法,一旦出错,就放弃方法,采用本地的不就方案,保证应用的运行没有问题。

继续回到源码分析,在点击某个tab的时候,相应ui也应该发生变化,还记得刚刚我们在原生提供的第一个方法吗,现在就是使用它的时候,

/*通过js调用本地方法,改变tab选中状态

*/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);

启动应用的时候,我们需要初始化整个tab界面,第二个方法排上用场了,

/*初始化数据,为了排除网络等不确定因素,需要在images文件夹下面放一套缺省图标

*/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;

}

}

这段处理有些复杂,因为需要考虑容错方法,所以我建议小伙伴们,把整个html放在app的assets文件夹下面,不要放在服务端,同时我们还要再assets文件夹下面放一套默认图标,我个人认为图标更新失败,比应用不能够正常使用要有好的多,建议采用如下的方式,使用这种方案,主要的思路就是不管什么原因导致远程图片读取失败,都直接使用本地的缺省图片,还要注意,是整套图片都使用本地的,不能够某一张读取失败了,只替换某一张噻~


目录结构

建议使用的小伙伴不要把你们的图片改成截图中的名字,这样就不需要再html源码中修改。
然后就是处理某个tab选中之后的样式修改了。

/*改变选中按钮的样式和状态

*/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的方法。

/*提供给原生的方法,原生调用该方法,实现设置某个tab选中

*/functionsetChecked(id) {

varbtn=document.getElementById(id);

btn.checked=true;

if(typeofappNative !=="undefined"&& appNative.changeTab) {

appNative.changeTab(id);

myFun("img_"+ id);

}

}

使用的时候只需要原生中调用,方法如下:

webView.loadUrl("javascript:setChecked('four')");

其中的”four”对应某个需要跳转到的tab编号,注意只能是“one”、“two”、“three”、“four”

前面就是整个功能的js代码,我知道,有些功能是完全可以用CSS来实现的,CSS我还不是很熟,所以暴力的使用了JS来处理细节,并且我并没有使用JQuery这些简单的语法结构,没有别的原因,我还不会~

三、原生调用

讲了CSS和JS,接着应该讲讲怎么在原生中调用。

首先加载整个tab的html页面

webView.loadUrl("file:///android_asset/radioGroup.html");

当然初始化数据是少不了的

/*将图片地址和按钮文案传递给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将数据传递给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这个方法里面调用js方法,这样不容易出错。

至于原生是怎么处理tab页面的切换的,我就不讲了,有兴趣的小伙伴可以把源码下载下来看一看,当然自己改改是最好的方式。

传送门开启:http://download.csdn.net/detail/zhimingshangyan/9648669

我的实现思路讲完了,文章的开篇我提供了三种实现方式,第二和第三两种方式我都实现了,当然第二种方式我没有整理,欢迎小伙伴提供你们的思路给我,同时有什么问题和值得改进的地方小伙伴可以留言给我。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,723评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,080评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,604评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,440评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,431评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,499评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,893评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,541评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,751评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,547评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,619评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,320评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,890评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,896评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,137评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,796评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,335评论 2 342

推荐阅读更多精彩内容

  • Android自动更新:这里的更新静悄悄~ (第一篇文章没有把样式弄好,所以重新弄了一遍,欢迎小伙伴读这篇文章:A...
    古川不爱笑阅读 3,997评论 1 10
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,368评论 25 707
  • 谦虚是每个人都该拥有的基本素质!不论在工作中还是生活中,如果没有谦虚都会得不到别人尊重和认可! 比如在工作中!有点...
    三车间王中伟阅读 282评论 0 2
  • 今天难得不用写作业,感觉真的好轻松,看来修炼还未到家。周末的节奏比上班的时候还来得紧凑,今天总算可以休息一下。晚上...
    小玲儿_阅读 1,240评论 1 9
  • 写毛啊!
    林然飘阅读 219评论 0 0