在这篇文章中,我们将带领您了解Android4.4开发之电池低电量告警提示原理与实现方法分析的全貌,包括安卓低电量提示的相关情况。同时,我们还将为您介绍有关AndroidJSBridge原理与实现、A
在这篇文章中,我们将带领您了解Android4.4开发之电池低电量告警提示原理与实现方法分析的全貌,包括安卓低电量提示的相关情况。同时,我们还将为您介绍有关Android JSBridge原理与实现、Android JSBridge的原理与实现、Android popupWindow弹出窗体实现方法分析、Android QQ、微信聊天消息界面设计原理与实现的知识,以帮助您更好地理解这个主题。
本文目录一览:- Android4.4开发之电池低电量告警提示原理与实现方法分析(安卓低电量提示)
- Android JSBridge原理与实现
- Android JSBridge的原理与实现
- Android popupWindow弹出窗体实现方法分析
- Android QQ、微信聊天消息界面设计原理与实现
Android4.4开发之电池低电量告警提示原理与实现方法分析(安卓低电量提示)
本文实例讲述了Android4.4电池低电量告警提示原理与实现方法。分享给大家供大家参考,具体如下:
之前版本的电池电量低是通过发送 intent ACTION_BATTERY_LOW来实现的,而在android4.4中,通过发送intent ACTION_BATTERY_CHANGED,也就是电池电量只要变化就检查是否需要低电量告警,并且实现挪到了PowerUI中。
路径:
frameworks/base/packages/systemUI/src/com/android/systemUI/power/PowerUI.java
public void start() { ...... //注册intent,主要关注的是ACTION_BATTERY_CHANGED消息。 // Register for Intent broadcasts for... IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_BATTERY_CHANGED); filter.addAction(Intent.ACTION_SCREEN_OFF); filter.addAction(Intent.ACTION_SCREEN_ON); mContext.registerReceiver(mIntentReceiver,filter,null,mHandler); }
当电池电量发生变化的时候,BatteryService就会发送 ACTION_BATTERY_CHANGED的 intent.
接着如下函数就会被调用:
private broadcastReceiver mIntentReceiver = new broadcastReceiver() { @Override public void onReceive(Context context,Intent intent) { String action = intent.getAction(); if (action.equals(Intent.ACTION_BATTERY_CHANGED)) { ...... mBatteryStatus = intent.getIntExtra(BatteryManager.EXTRA_STATUS,BatteryManager.BATTERY_STATUS_UNKNowN); ...... if (!plugged && (bucket < oldBucket || oldplugged) && mBatteryStatus != BatteryManager.BATTERY_STATUS_UNKNowN && bucket < 0) { showLowBatteryWarning(); ...... } } };
如果不想要显示低电量对话框, 只要不调用showLowBatteryWarning()
即可。
我遇到的问题在于插上充电座之后开机仍然显示低电量告警,提示要插入充电器,出现这个问题的原因在于: 电池是另外一块板子供的,所以没有插拔信息,并且battery status默认情况下也是BATTERY_STATUS_UNKNowN。
因此只要将status改成BATTERY_STATUS_CHARGING就能修复此问题了~
更多关于Android相关内容感兴趣的读者可查看本站专题:《Android硬件相关操作与应用总结》、《Android文件操作技巧汇总》、《Android开发入门与进阶教程》、《Android资源操作技巧汇总》、《Android视图View技巧总结》及《Android控件用法总结》
希望本文所述对大家Android程序设计有所帮助。
Android JSBridge原理与实现
在Android中,JSBridge已经不是什么新鲜的事物了,各家的实现方式也略有差异。大多数人都知道WebView存在一个漏洞,详细信息见你不知道的 Android WebView 使用漏洞,虽然该漏洞已经在Android 4.2上修复了,即使用@JavascriptInterface代替addJavascriptInterface,但是由于兼容性和安全性问题,基本上我们不会再利用Android系统为我们提供的addJavascriptInterface方法或者@JavascriptInterface注解来实现,所以我们只能另辟蹊径,去寻找既安全,又能实现兼容Android各个版本的方案。
首先我们来了解一下为什么要使用JSBridge,在开发中,为了追求开发的效率以及移植的便利性,一些展示性强和性能要求不是很高的页面,我们会偏向于使用h5来完成,功能性强的页面我们会偏向于使用native来完成,而一旦使用了h5,为了在h5中尽可能的得到native的体验,我们native层需要暴露一些方法给js调用,比如,弹Toast提醒,弹Dialog,分享等等,有时候甚至把h5的网络请求放着native去完成,而JSBridge做得好的一个典型就是微信,微信给开发者提供了JSSDK,该SDK中暴露了很多微信native层的方法,比如支付,定位等。
那么,怎么去实现一个兼容Android各版本又具有一定安全性的JSBridge呢?我们知道,在WebView中,如果java要调用js的方法,是非常容易做到的,Android4.4以前使用WebView.loadUrl(“javascript:function()”)。Android4.4以后,使用以下方式
webView.evaluateJavascript(“javascript:function()”, new ValueCallback<String>() {
@Override
public void onReceiveValue(String value) {
Toast.makeText(MainActivity.this, "onReceiveValue Java To JS", Toast.LENGTH_SHORT).show();
}
});
,这样,就做到了JSBridge的native层调用h5层的通信,但是h5层如何调native层呢,我们需要寻找这么一个通道,仔细回忆一下,WebView有一个方法,叫setWebChromeClient,可以设置WebChromeClient对象,而这个对象中有三个方法,分别是onJsAlert,onJsConfirm,onJsPrompt,当js调用window对象的对应的方法,即window.alert,window.confirm,window.prompt,WebChromeClient对象中的三个方法对应的就会被触发,我们是不是可以利用这个机制,自己做一些处理呢?答案是肯定的。
至于js这三个方法的区别,可以详见w3c JavaScript 消息框 。一般来说,我们是不会使用onJsAlert的,为什么呢?因为js中alert使用的频率还是非常高的,一旦我们占用了这个通道,alert的正常使用就会受到影响,而confirm和prompt的使用频率相对alert来说,则更低一点。那么到底是选择confirm还是prompt呢,其实confirm的使用频率也是不低的,比如你点一个链接下载一个文件,这时候如果需要弹出一个提示进行确认,点击确认就会下载,点取消便不会下载,类似这种场景还是很多的,因此不能占用confirm。而prompt则不一样,在Android中,几乎不会使用到这个方法,就是用,也会进行自定义,所以我们完全可以使用这个方法。该方法就是弹出一个输入框,然后让你输入,输入完成后返回输入框中的内容。因此,占用prompt是再完美不过了。
到这一步,我们已经找到了JSBridge双向通信的一个通道了,接下来就是如何实现的问题了。本文中实现的只是一个简单的demo,如果要在正式项目中下使用,还需要自己做一层封装。
要进行正常的通信,通信协议的制定是必不可少的。我们回想一下熟悉的http请求url的组成部分。形如http://host:port/path?param=value,我们参考http,制定JSBridge的组成部分,我们的JSBridge需要传递给native什么信息,native层才能完成对应的功能,然后将结果返回呢?显而易见我们native层要完成某个功能就需要调用某个类的某个方法,我们需要将这个类名和方法名传递过去,此外,还需要方法调用所需的参数,为了通信方便,native方法所需的参数我们规定为json对象,我们在js中传递这个json对象过去,native层拿到这个对象再进行解析即可。为了区别于http协议,我们的jsbridge使用jsbridge协议,为了简单起见,问号后面不适用键值对,我们直接跟上我们的json字符串,于是就有了形如下面的这个uri
jsbridge://className:port/methodName?jsonObj
假设我们需要调用native层的Logger类的log方法,当然这个类以及方法肯定是遵循某种规范的,不是所有的java类都可以调用,不然就跟文章开头的WebView漏洞一样了,参数是msg,执行完成后js层要有一个回调,那么地址就如下
sbridge://Logger:callbackAddress/log?{"msg":"native log"}
至于这个callback对象的地址,可以存储到js中的window对象中去。至于怎么存储,后文再讲述。
上面是js向native的通信协议,那么另一方面,native向js的通信协议也需要制定,一个必不可少的元素就是返回值,这个返回值和js的参数做法一样,通过json对象进行传递,该json对象中有状态码code,提示信息msg,以及返回结果result,如果code为非0,则执行过程中发生了错误,错误信息在msg中,返回结果result为null,如果执行成功,返回的json对象在result中。下面是两个例子,一个成功调用,一个调用失败。
{
"code":500,
"msg":"method is not exist",
"result":null
}
{
"code":0,
"msg":"ok",
"result":{
"key1":"returnValue1",
"key2":"returnValue2",
"key3":{
"nestedKey":"nestedValue"
"nestedArray":["value1","value2"]
}
}
}
那么这个结果如何返回呢,native调用js暴露的方法即可,然后将js层传给native层的port一并带上,进行调用即可,调用的方式就是通过WebView.loadUrl方式来完成,如下。
mWebView.loadUrl("javascript:JSBridge.onFinish(port,jsonObj);");
关于JsBridge.onFinish方法的实现,后面再叙述。前面我们提到了native层的方法必须遵循某种规范,不然就非常不安全了。在native中,我们需要一个JSBridge统一管理这些暴露给js的类和方法,并且能实时添加,这时候就需要这么一个方法
JSBridge.register("jsName",javaClass.class)
这个javaClass就是满足某种规范的类,该类中有满足规范的方法,我们规定这个类需要实现一个空接口,为什么呢?主要作用就混淆的时候不会发生错误,还有一个作用就是约束JSBridge.register方法第二个参数必须是该接口的实现类。那么我们定义这个接口
public interface IBridge{
}
类规定好了,类中的方法我们还需要规定,为了调用方便,我们规定类中的方法必须是static的,这样直接根据类而不必新建对象进行调用了(还要是public的),然后该方法不具有返回值,因为返回值我们在回调中返回,既然有回调,参数列表就肯定有一个callback,除了callback,当然还有前文提到的js传来的方法调用所需的参数,是一个json对象,在java层中我们定义成JSONObject对象;方法的执行结果需要通过callback传递回去,而java执行js方法需要一个WebView对象,于是,满足某种规范的方法原型就出来了。
public static void methodName(WebView web view,JSONObject jsonObj,Callback callback){
}
js层除了上文说到的JSBridge.onFinish(port,jsonObj);方法用于回调,应该还有一个方法提供调用native方法的功能,该函数的原型如下
JSBridge.call(className,methodName,params,callback)
在call方法中再将参数组合成形如下面这个格式的uri
jsbridge://className:callbackAddress/methodName?jsonObj
然后调用window.prompt方法将uri传递过去,这时候java层就会收到这个uri,再进一步解析即可。
万事具备了,只欠如何编码了,别急,下面我们一步一步的来实现,先完成js的两个方法。新建一个文件,命名为JSBridge.js
(function (win) {
var hasOwnProperty = Object.prototype.hasOwnProperty;
var JSBridge = win.JSBridge || (win.JSBridge = {});
var JSBRIDGE_PROTOCOL = ''JSBridge'';
var Inner = {
callbacks: {},
call: function (obj, method, params, callback) {
console.log(obj+" "+method+" "+params+" "+callback);
var port = Util.getPort();
console.log(port);
this.callbacks[port] = callback;
var uri=Util.getUri(obj,method,params,port);
console.log(uri);
window.prompt(uri, "");
},
onFinish: function (port, jsonObj){
var callback = this.callbacks[port];
callback && callback(jsonObj);
delete this.callbacks[port];
},
};
var Util = {
getPort: function () {
return Math.floor(Math.random() * (1 << 30));
},
getUri:function(obj, method, params, port){
params = this.getParam(params);
var uri = JSBRIDGE_PROTOCOL + ''://'' + obj + '':'' + port + ''/'' + method + ''?'' + params;
return uri;
},
getParam:function(obj){
if (obj && typeof obj === ''object'') {
return JSON.stringify(obj);
} else {
return obj || '''';
}
}
};
for (var key in Inner) {
if (!hasOwnProperty.call(JSBridge, key)) {
JSBridge[key] = Inner[key];
}
}
Android JSBridge的原理与实现
原文出处: 安卓弟(@_安卓弟)
在Android中,JSBridge已经不是什么新鲜的事物了,各家的实现方式也略有差异。大多数人都知道WebView存在一个漏洞,见WebView中接口隐患与手机挂马利用,虽然该漏洞已经在Android 4.2上修复了,即使用@JavascriptInterface代替addJavascriptInterface,但是由于兼容性和安全性问题,基本上我们不会再利用Android系统为我们提供的addJavascriptInterface方法或者@JavascriptInterface注解来实现,所以我们只能另辟蹊径,去寻找既安全,又能实现兼容Android各个版本的方案。
首先我们来了解一下为什么要使用JSBridge,在开发中,为了追求开发的效率以及移植的便利性,一些展示性强的页面我们会偏向于使用h5来完成,功能性强的页面我们会偏向于使用native来完成,而一旦使用了h5,为了在h5中尽可能的得到native的体验,我们native层需要暴露一些方法给js调用,比如,弹Toast提醒,弹Dialog,分享等等,有时候甚至把h5的网络请求放着native去完成,而JSBridge做得好的一个典型就是微信,微信给开发者提供了JSSDK,该SDK中暴露了很多微信native层的方法,比如支付,定位等。
那么,怎么去实现一个兼容Android各版本又具有一定安全性的JSBridge呢?我们知道,在WebView中,如果java要调用js的方法,是非常容易做到的,使用WebView.loadUrl(“javascript:function()”)即可,这样,就做到了JSBridge的native层调用h5层的单向通信,但是h5层如何调native层呢,我们需要寻找这么一个通道,仔细回忆一下,WebView有一个方法,叫setWebChromeClient,可以设置WebChromeClient对象,而这个对象中有三个方法,分别是onJsAlert,onJsConfirm,onJsPrompt,当js调用window对象的对应的方法,即window.alert,window.confirm,window.prompt,WebChromeClient对象中的三个方法对应的就会被触发,我们是不是可以利用这个机制,自己做一些处理呢?答案是肯定的。
至于js这三个方法的区别,可以详见w3c JavaScript 消息框 。一般来说,我们是不会使用onJsAlert的,为什么呢?因为js中alert使用的频率还是非常高的,一旦我们占用了这个通道,alert的正常使用就会受到影响,而confirm和prompt的使用频率相对alert来说,则更低一点。那么到底是选择confirm还是prompt呢,其实confirm的使用频率也是不低的,比如你点一个链接下载一个文件,这时候如果需要弹出一个提示进行确认,点击确认就会下载,点取消便不会下载,类似这种场景还是很多的,因此不能占用confirm。而prompt则不一样,在Android中,几乎不会使用到这个方法,就是用,也会进行自定义,所以我们完全可以使用这个方法。该方法就是弹出一个输入框,然后让你输入,输入完成后返回输入框中的内容。因此,占用prompt是再完美不过了。
到这一步,我们已经找到了JSBridge双向通信的一个通道了,接下来就是如何实现的问题了。本文中实现的只是一个简单的demo,如果要在生产环境下使用,还需要自己做一层封装。
要进行正常的通信,通信协议的制定是必不可少的。我们回想一下熟悉的http请求url的组成部分。形如http://host:port/path?param=value,我们参考http,制定JSBridge的组成部分,我们的JSBridge需要传递给native什么信息,native层才能完成对应的功能,然后将结果返回呢?显而易见我们native层要完成某个功能就需要调用某个类的某个方法,我们需要将这个类名和方法名传递过去,此外,还需要方法调用所需的参数,为了通信方便,native方法所需的参数我们规定为json对象,我们在js中传递这个json对象过去,native层拿到这个对象再进行解析即可。为了区别于http协议,我们的jsbridge使用jsbridge协议,为了简单起见,问号后面不适用键值对,我们直接跟上我们的json字符串,于是就有了形如下面的这个uri
Java
1 |
jsbridge://className:port/methodName?jsonObj |
有人会问,这个port用来干嘛,其实js层调用native层方法后,native需要将执行结果返回给js层,不过你会觉得通过WebChromeClient对象的onJsPrompt方法将返回值返回给js不就好了吗,其实不然,如果这么做,那么这个过程就是同步的,如果native执行异步操作的话,返回值怎么返回呢?这时候port就发挥了它应有的作用,我们在js中调用native方法的时候,在js中注册一个callback,然后将该callback在指定的位置上缓存起来,然后native层执行完毕对应方法后通过WebView.loadUrl调用js中的方法,回调对应的callback。那么js怎么知道调用哪个callback呢?于是我们需要将callback的一个存储位置传递过去,那么就需要native层调用js中的方法的时候将存储位置回传给js,js再调用对应存储位置上的callback,进行回调。于是,完整的协议定义如下:
Java
1 |
jsbridge://className:callbackAddress/methodName?jsonObj |
假设我们需要调用native层的Logger类的log方法,当然这个类以及方法肯定是遵循某种规范的,不是所有的java类都可以调用,不然就跟文章开头的WebView漏洞一样了,参数是msg,执行完成后js层要有一个回调,那么地址就如下
Java
1 |
jsbridge://Logger:callbackAddress/log?{"msg":"native log"} |
至于这个callback对象的地址,可以存储到js中的window对象中去。至于怎么存储,后文会慢慢倒来。
上面是js向native的通信协议,那么另一方面,native向js的通信协议也需要制定,一个必不可少的元素就是返回值,这个返回值和js的参数做法一样,通过json对象进行传递,该json对象中有状态码code,提示信息msg,以及返回结果result,如果code为非0,则执行过程中发生了错误,错误信息在msg中,返回结果result为null,如果执行成功,返回的json对象在result中。下面是两个例子,一个成功调用,一个调用失败。
Java
1 2 3 4 5 |
{ "code":500, "msg":"method is not exist", "result":null } |
Java
1 2 3 4 5 6 7 8 9 10 11 12 |
{ "code":0, "msg":"ok", "result":{ "key1":"returnValue1", "key2":"returnValue2", "key3":{ "nestedKey":"nestedValue" "nestedArray":["value1","value2"] } } } |
那么这个结果如何返回呢,native调用js暴露的方法即可,然后将js层传给native层的port一并带上,进行调用即可,调用的方式就是通过WebView.loadUrl方式来完成,如下。
Java
1 |
mWebView.loadUrl("javascript:JSBridge.onFinish(port,jsonObj);"); |
关于JsBridge.onFinish方法的实现,后面再叙述。前面我们提到了native层的方法必须遵循某种规范,不然就非常不安全了。在native中,我们需要一个JSBridge统一管理这些暴露给js的类和方法,并且能实时添加,这时候就需要这么一个方法
Java
1 |
JSBridge.register("jsName",javaClass.class) |
这个javaClass就是满足某种规范的类,该类中有满足规范的方法,我们规定这个类需要实现一个空接口,为什么呢?主要作用就混淆的时候不会发生错误,还有一个作用就是约束JSBridge.register方法第二个参数必须是该接口的实现类。那么我们定义这个接口
Java
1 2 |
public interface IBridge{ } |
类规定好了,类中的方法我们还需要规定,为了调用方便,我们规定类中的方法必须是static的,这样直接根据类而不必新建对象进行调用了(还要是public的),然后该方法不具有返回值,因为返回值我们在回调中返回,既然有回调,参数列表就肯定有一个callback,除了callback,当然还有前文提到的js传来的方法调用所需的参数,是一个json对象,在java层中我们定义成JSONObject对象;方法的执行结果需要通过callback传递回去,而java执行js方法需要一个WebView对象,于是,满足某种规范的方法原型就出来了。
Java
1 2 3 |
public static void methodName(WebView web view,JSONObject jsonObj,Callback callback){
} |
js层除了上文说到的JSBridge.onFinish(port,jsonObj);方法用于回调,应该还有一个方法提供调用native方法的功能,该函数的原型如下
Java
1 |
JSBridge.call(className,methodName,params,callback) |
在call方法中再将参数组合成形如下面这个格式的uri
Java
1 |
jsbridge://className:callbackAddress/methodName?jsonObj |
然后调用window.prompt方法将uri传递过去,这时候java层就会收到这个uri,再进一步解析即可。
万事具备了,只欠如何编码了,别急,下面我们一步一步的来实现,先完成js的两个方法。新建一个文件,命名为JSBridge.js
Java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
(function (win) { var hasOwnProperty = Object.prototype.hasOwnProperty; var JSBridge = win.JSBridge || (win.JSBridge = {}); var JSBRIDGE_PROTOCOL = ''JSBridge''; var Inner = { callbacks: {}, call: function (obj, method, params, callback) { console.log(obj+" "+method+" "+params+" "+callback); var port = Util.getPort(); console.log(port); this.callbacks[port] = callback; var uri=Util.getUri(obj,method,params,port); console.log(uri); window.prompt(uri, ""); }, onFinish: function (port, jsonObj){ var callback = this.callbacks[port]; callback & callback(jsonObj); delete this.callbacks[port]; }, }; var Util = { getPort: function () { return Math.floor(Math.random() * (1 30)); }, getUri:function(obj, method, params, port){ params = this.getParam(params); var uri = JSBRIDGE_PROTOCOL + ''://'' + obj + '':'' + port + ''/'' + method + ''?'' + params; return uri; }, getParam:function(obj){ if (obj & typeof obj === ''object'') { return JSON.stringify(obj); } else { return obj || ''''; } } }; for (var key in Inner) { if (!hasOwnProperty.call(JSBridge, key)) { JSBridge[key] = Inner[key]; } } })(window); |
可以看到,我们里面有一个Util类,里面有三个方法,getPort()用于随机生成port,getParam()用于生成json字符串,getUri()用于生成native需要的协议uri,里面主要做字符串拼接的工作,然后有一个Inner类,里面有我们的call和onFinish方法,在call方法中,我们调用Util.getPort()获得了port值,然后将callback对象存储在了callbacks中的port位置,接着调用Util.getUri()将参数传递过去,将返回结果赋值给uri,调用window.prompt(uri, “”)将uri传递到native层。而onFinish()方法接受native回传的port值和执行结果,根据port值从callbacks中得到原始的callback函数,执行callback函数,之后从callbacks中删除。最后将Inner类中的函数暴露给外部的JSBrige对象,通过一个for循环一一赋值即可。
当然这个实现是最最简单的实现了,实际情况要考虑的因素太多,由于本人不是很精通js,所以只能以java的思想去写js,没有考虑到的因素姑且忽略吧,比如内存的回收等等机制。
这样,js层的编码就完成了,接下来实现java层的编码。
上文说到java层有一个空接口来进行约束暴露给js的类和方法,同时也便于混淆
Java
1 2 |
public interface IBridge { } |
首先我们要将js传来的uri获取到,编写一个WebChromeClient子类。
Java
1 2 3 4 5 6 7 |
public class JSBridgeWebChromeClient extends WebChromeClient { @Override public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) { result.confirm(JSBridge.callJava(view, message)); return true; } } |
之后不要忘记了将该对象设置给WebView
Java
1 2 3 4 5 |
WebView mWebView = (WebView) findViewById(R.id.webview); WebSettings settings = mWebView.getSettings(); settings.setJavaScriptEnabled(true); mWebView.setWebChromeClient(new JSBridgeWebChromeClient()); mWebView.loadUrl("file:///android_asset/index.html"); |
核心的内容来了,就是JSBridgeWebChromeClient中调用的JSBridge类的实现。前文提到该类中有这么一个方法提供注册暴露给js的类和方法
Java
1 |
JSBridge.register("jsName",javaClass.class) |
该方法的实现其实很简单,从一个Map中查找key是不是存在,不存在则反射拿到对应的Class中的所有方法,将方法是public static void 类型的,并且参数是三个参数,分别是Webview,JSONObject,Callback类型的,如果满足条件,则将所有满足条件的方法put进去,整个实现如下
Java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
public class JSBridge { private static Map> exposedMethods = new HashMap();
public static void register(String exposedName, Class extends IBridge> clazz) { if (!exposedMethods.containsKey(exposedName)) { try { exposedMethods.put(exposedName, getAllMethod(clazz)); } catch (Exception e) { e.printStackTrace(); } } }
private static HashMapgetAllMethod(Class injectedCls) throws Exception { HashMap mMethodsMap = new HashMap(); Method[] methods = injectedCls.getDeclaredMethods(); for (Method method : methods) { String name; if (method.getModifiers() != (Modifier.PUBLIC | Modifier.STATIC) || (name = method.getName()) == null) { continue; } Class[] parameters = method.getParameterTypes(); if (null != parameters & parameters.length == 3) { if (parameters[0] == WebView.class && parameters[1] == JSONObject.class && parameters[2] == JSCallback.class) { mMethodsMap.put(name, method); } } } return mMethodsMap; } } |
而至于JSBridge类中的callJava方法,就是将js传来的uri进行解析,然后根据调用的类名别名从刚刚的map中查找是不是存在,存在的话拿到该类所有方法的methodMap,然后根据方法名从methodMap拿到方法,反射调用,并将参数传进去,参数就是前文说的满足条件的三个参数,即WebView,JSONObject,Callback。
Java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
public static String callJava(WebView webView, String uriString) { String methodName = ""; String className = ""; String param = "{}"; String port = ""; if (!TextUtils.isEmpty(uriString) & uriString.startsWith("JSBridge")) { Uri uri = Uri.parse(uriString); className = uri.getHost(); param = uri.getQuery(); port = uri.getPort() + ""; String path = uri.getPath(); if (!TextUtils.isEmpty(path)) { methodName = path.replace("/", ""); } }
if (exposedMethods.containsKey(className)) { HashMapString, Method> methodHashMap = exposedMethods.get(className);
if (methodHashMap != null & methodHashMap.size() != 0 && methodHashMap.containsKey(methodName)) { Method method = methodHashMap.get(methodName); if (method != null) { try { method.invoke(null, webView, new JSONObject(param), new Callback(webView, port)); } catch (Exception e) { e.printStackTrace(); } } } } return null; } |
看到该方法中使用了 new Callback(webView, port)进行新建对象,该对象就是用来回调js中回调方法的java对应的类。这个类你需要将js传来的port传进来之外,还需要将WebView的引用传进来,因为要使用到WebView的loadUrl方法,为了防止内存泄露,这里使用弱引用。如果你需要回调js的callback,在对应的方法里调用一下callback.apply()方法将返回数据传入即可,
Java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
public class Callback { private static Handler mHandler = new Handler(Looper.getMainLooper()); private static final String CALLBACK_JS_FORMAT = "javascript:JSBridge.onFinish(''%s'', %s);"; private String mPort; private WeakReference mWebViewRef;
public Callback(WebView view, String port) { mWebViewRef = new WeakReference(view); mPort = port; }
public void apply(JSONObject jsonObject) { final String execJs = String.format(CALLBACK_JS_FORMAT, mPort, String.valueOf(jsonObject)); if (mWebViewRef != null & mWebViewRef.get() != null) { mHandler.post(new Runnable() { @Override public void run() { mWebViewRef.get().loadUrl(execJs); } });
}
} } |
唯一需要注意的是apply方法我把它扔在主线程执行了,为什么呢,因为暴露给js的方法可能会在子线程中调用这个callback,这样的话就会报错,所以我在方法内部将其切回主线程。
编码完成的差不多了,那么就剩实现IBridge即可了,我们来个简单的,就来显示Toast为例好了,显示完给js回调,虽然这个回调没有什么意义。
Java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
public class BridgeImpl implements IBridge { public static void showToast(WebView webView, JSONObject param, final Callback callback) { String message = param.optString("msg"); Toast.makeText(webView.getContext(), message, Toast.LENGTH_SHORT).show(); if (null != callback) { try { JSONObject object = new JSONObject(); object.put("key", "value"); object.put("key1", "value1"); callback.apply(getJSONObject(0, "ok", object)); } catch (Exception e) { e.printStackTrace(); } } }
private static JSONObject getJSONObject(int code, String msg, JSONObject result) { JSONObject object = new JSONObject(); try { object.put("code", code); object.put("msg", msg); object.putOpt("result", result); return object; } catch (JSONException e) { e.printStackTrace(); } return null; } } |
你可以往该类中扔你需要的方法,但是必须是public static void且参数列表满足条件,这样才能找到该方法。
不要忘记将该类注册进去
Java
1 |
JSBridge.register("bridge", BridgeImpl.class); |
进行一下简单的测试,将之前实现好的JSBridge.js文件扔到assets目录下,然后新建index.html,输入
Java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
html> head> meta charset="utf-8"> title>JSBridgetitle> meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1, user-scalable=no"/> script src="file:///android_asset/JSBridge.js" type="text/javascript">script> script type="text/javascript">
script> style>
style> head>
body> div> h3>JSBridge 测试h3> div> ul class="list"> li> div> button onclick="JSBridge.call(''bridge'',''showToast'',{''msg'':''Hello JSBridge''},function(res){alert(JSON.stringify(res))})"> 测试showToast button> div> li> br/> ul> body> html> |
很简单,就是按钮点击时调用JSBridge.call()方法,回调函数是alert出返回的结果。
接着就是使用WebView将该index.html文件load进来测试了
Java
1 |
mWebView.loadUrl("file:///android_asset/index.html"); |
效果如下图所示
可以看到整个过程都走通了,然后我们测试下子线程回调,在BridgeImpl中加入测试方法
Java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
public static void testThread(WebView webView, JSONObject param, final Callback callback) { new Thread(new Runnable() { @Override public void run() { try { Thread.sleep(3000); JSONObject object = new JSONObject(); object.put("key", "value"); callback.apply(getJSONObject(0, "ok", object)); } catch (InterruptedException e) { e.printStackTrace(); } catch (JSONException e) { e.printStackTrace(); } } }).start(); } |
在index.html中加入
Java
1 2 3 4 5 6 7 8 9 10 |
ul class="list"> li> div> button onclick="JSBridge.call(''bridge'',''testThread'',{},function(res){alert(JSON.stringify(res))})"> 测试子线程回调 button> div> li> br/> ul> |
理想的效果应该是3秒钟之后回调弹出alert显示
很完美,代码也不多,就实现了功能。如果你需要使用到生成环境中去,上面的代码你一定要再自己封装一下,因为我只是简单的实现了功能,其他因素并没有考虑太多。
当然你也可以参考一个开源的实现
Safe Java-JS WebView Bridge
最后还是惯例,贴上代码
http://download.csdn.net/detail/sbsujjbcy/9446915
问啊-定制化IT教育平台,牛人一对一服务,有问必答,开发编程社交头条 官方网站:www.wenaaa.com
QQ群290551701 聚集很多互联网精英,技术总监,架构师,项目经理!开源技术研究,欢迎业内人士,大牛及新手有志于从事IT行业人员进入!
Android popupWindow弹出窗体实现方法分析
本文实例讲述了Android popupWindow弹出窗体实现方法。分享给大家供大家参考,具体如下:
1. 建立popupwindow显示的布局页面(普通的view任意布局)
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="80dip" android:background="@drawable/popup_yellow_window_bg" android:orientation="horizontal" > <TextView android:id="@+id/popupwindow_app_uninstall_text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:layout_marginBottom="5dip" android:layout_marginTop="5dip" android:drawabletop="@drawable/kn_malware_scan_deep_click" android:text="卸 载"/> </LinearLayout>
2. activity中布局加载以及填充,建立popupwindow对象,设置相应参数或属性
View contentView = View.inflate(getApplicationContext(),R.layout.popup_window,null); LinearLayout ll_uninstall = (LinearLayout) contentView.findViewById(R.id.ll_uninstall); //设置popupwindow内布局组件的监听(与其他组件相似) MyOnClickListener l = new MyOnClickListener(position); ll_uninstall.setonClickListener(l); PopupWindow mPopupWindow = new PopupWindow(contentView,ViewGroup.LayoutParams.WRAP_CONTENT,70); int[] arrayOfInt = new int[2]; view.getLocationInWindow(arrayOfInt); int x = arrayOfInt[0] + 60; int y = arrayOfInt[1]; //1 指定popupwindow的背景 2 popupwindow能够获得焦点 mPopupWindow.setBackgroundDrawable(new BitmapDrawable()); mPopupWindow.setFocusable(true); mPopupWindow.showAtLocation(view,Gravity.LEFT|Gravity.TOP,x,y); //在合适位置取消popupwindow显示 mPopupWindow.dismiss();
更多关于Android相关内容感兴趣的读者可查看本站专题:《Android窗口相关操作技巧总结》、《Android开发入门与进阶教程》、《Android调试技巧与常见问题解决方法汇总》、《Android基本组件用法总结》、《Android视图View技巧总结》、《Android布局layout技巧总结》及《Android控件用法总结》
希望本文所述对大家Android程序设计有所帮助。
Android QQ、微信聊天消息界面设计原理与实现
Android QQ、微信聊天消息界面设计原理与实现
原理:Android平台上,典型的以腾讯的QQ、微信这些聊天消息界面通常可以采用ListView设计与实现,需要使用ListView 适配器Adapter的getItemViewType()和getViewTypeCount()。
在ListView的适配器中,每一次getView时候,首先要判断view的类型getItemViewType(),然后根据不同的类型加载不同的布局view。
至于底部发送消息的窗口,每次发送完消息,需要将ListView滚动到底部,以免输入键盘遮挡住数据而致使用户看不到刚刚发送的消息。
现在给出一个Android平台上简单的设计与实现方案。效果如图所示:
测试的主Activity MainActivity.java:
package zhangphil.chat;
import java.util.ArrayList;
import java.util.HashMap;
import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.view.ContextMenu;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.ContextMenu.ContextMenuInfo;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.TextView;
public class MainActivity extends Activity {
private final int VIEW_TYPE = 0xb01;
private final int VIEW_TYPE_LEFT = -10;
private final int VIEW_TYPE_RIGHT = -11;
private final int MESSAGE = 0xb02;
private ArrayList<HashMap<Integer, Object>> items = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final ListView listView = (ListView) findViewById(android.R.id.list);
items = new ArrayList<HashMap<Integer, Object>>();
for (int i = 0; i < 8; i++) {
if (i % 2 == 0) {
HashMap<Integer, Object> map = new HashMap<Integer, Object>();
map.put(VIEW_TYPE, VIEW_TYPE_LEFT);
map.put(MESSAGE, "对方说的消息" + i);
items.add(map);
} else {
HashMap<Integer, Object> map = new HashMap<Integer, Object>();
map.put(VIEW_TYPE, VIEW_TYPE_RIGHT);
map.put(MESSAGE, "我说的消息" + i);
items.add(map);
}
}
final MyAdapter adapter = new MyAdapter(this, -1);
listView.setAdapter(adapter);
final EditText msgEditText = (EditText) findViewById(R.id.msgEditText);
Button button = (Button) findViewById(R.id.msgSend);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String msg = msgEditText.getText() + "";
HashMap<Integer, Object> map = new HashMap<Integer, Object>();
map.put(VIEW_TYPE, VIEW_TYPE_RIGHT);
map.put(MESSAGE, msg);
items.add(map);
adapter.notifyDataSetChanged();
// 发送后清空输入框内容
msgEditText.setText(null);
// 输入框发送消息后将ListView滚动到最底部
listView.setSelection(ListView.FOCUS_DOWN);
}
});
}
private class MyAdapter extends ArrayAdapter {
private LayoutInflater layoutInflater;
public MyAdapter(Context context, int resource) {
super(context, resource);
layoutInflater = LayoutInflater.from(context);
}
@Override
public View getView(int pos, View convertView, ViewGroup parent) {
int type = getItemViewType(pos);
String msg = getItem(pos);
switch (type) {
case VIEW_TYPE_LEFT:
convertView = layoutInflater.inflate(R.layout.left, null);
TextView textLeft = (TextView) convertView.findViewById(R.id.textView);
textLeft.setText(msg);
break;
case VIEW_TYPE_RIGHT:
convertView = layoutInflater.inflate(R.layout.right, null);
TextView textRight = (TextView) convertView.findViewById(R.id.textView);
textRight.setText(msg);
break;
}
return convertView;
}
@Override
public String getItem(int pos) {
String s = items.get(pos).get(MESSAGE) + "";
return s;
}
@Override
public int getCount() {
return items.size();
}
@Override
public int getItemViewType(int pos) {
int type = (Integer) items.get(pos).get(VIEW_TYPE);
return type;
}
@Override
public int getViewTypeCount() {
return 2;
}
}
}
MainActivity.java需要的布局文件activity_main.xml:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<ListView
android:id="@android:id/list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_above="@+id/commentLinearLayout"
android:layout_alignParentTop="true"
android:divider="@android:color/transparent"
android:dividerHeight="15dip"
android:scrollbars="none" />
<LinearLayout
android:id="@+id/commentLinearLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:background="#e0e0e0"
android:orientation="horizontal" >
<EditText
android:id="@+id/msgEditText"
android:layout_width="0dip"
android:layout_height="wrap_content"
android:layout_weight="8"
android:hint="发送消息" />
<Button
android:id="@+id/msgSend"android:layout_width="0dip"
android:layout_height="wrap_content"
android:layout_weight="2"
android:text="发送" />
</LinearLayout>
</RelativeLayout>
ListView适配器Adapter在每一次getView时候,首先判断view的type,然后根据不同的view type加载不同的布局view。
left.xml表示是对方说的消息在ListView界面的左边:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal" >
<ImageView
android:id="@+id/imageView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_centerVertical="true"
android:singleLine="false"
android:src="@drawable/ic_launcher" />
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_toRightOf="@+id/imageView"
android:background="#ff5252"
android:text="left" />
</RelativeLayout>
right.xml表示是自己说的消息,在消息聊天界面的右边:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal" >
<ImageView
android:id="@+id/imageView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:src="@drawable/ic_launcher" />
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_toLeftOf="@+id/imageView"
android:background="#2196f3"
android:singleLine="false"
android:text="right" />
</RelativeLayout>
另外,QQ、微信这些聊天消息界面的消息背景是一个气泡,这个气泡其实是一个.9.png图片,将这个气泡的.9.png作为TextView的背景衬图衬上去即可,QQ、微信的聊天气泡.9.png不是本文要着重探讨的内容,在次不再展开叙述。
附参考文章:
《Android ListView Adapter的getItemViewType和getViewTypeCount多种布局》文章链接地址:http://blog.csdn.net/zhangphil/article/details/46984367
《Android基于PinnedSectionListView实现联系人通讯录》文章链接地址:http://blog.csdn.net/zhangphil/article/details/47271741
今天的关于Android4.4开发之电池低电量告警提示原理与实现方法分析和安卓低电量提示的分享已经结束,谢谢您的关注,如果想了解更多关于Android JSBridge原理与实现、Android JSBridge的原理与实现、Android popupWindow弹出窗体实现方法分析、Android QQ、微信聊天消息界面设计原理与实现的相关知识,请在本站进行查询。
本文标签: