-
Notifications
You must be signed in to change notification settings - Fork 0
/
content.json
1 lines (1 loc) · 63.6 KB
/
content.json
1
{"meta":{"title":"加贝龙","subtitle":null,"description":null,"author":"Holo Wang","url":"http://yoursite.com"},"pages":[{"title":"about","date":"2016-09-24T03:29:12.000Z","updated":"2016-09-24T03:29:27.000Z","comments":true,"path":"about/index.html","permalink":"http://yoursite.com/about/index.html","excerpt":"","text":""},{"title":"categories","date":"2016-09-24T03:27:02.000Z","updated":"2016-09-24T05:11:17.000Z","comments":true,"path":"categories/index.html","permalink":"http://yoursite.com/categories/index.html","excerpt":"","text":""},{"title":"tags","date":"2016-09-24T03:28:33.000Z","updated":"2016-09-24T03:28:46.000Z","comments":true,"path":"tags/index.html","permalink":"http://yoursite.com/tags/index.html","excerpt":"","text":""}],"posts":[{"title":"Cordova : 自定义Plugin","slug":"Cordova_custom_plugin","date":"2017-04-08T07:18:44.000Z","updated":"2017-04-08T07:22:18.000Z","comments":true,"path":"2017/04/08/Cordova_custom_plugin/","link":"","permalink":"http://yoursite.com/2017/04/08/Cordova_custom_plugin/","excerpt":"Cordova之自定义Plugin本文通过自定义一个简单的Toast插件来介绍一下如何从0开始自定义自己的plugin,基于cordova6.2.1版本。在这之前你需要创建一个Cordova项目,如何创建一个Corodva项目可以参考Cordova:Plugin中的1-5步骤,这里不在赘述。 1.原生插件开发首先创建一个ToastPlugin,我把他放在了主工程中,所有自定义的Plugin都是继承自CordovaPlugin,然后重写CordovaPlugin的execute方法来实现插件的功能,CordovaPlugin中定义了三个execute,这里我选择重写方法public boolean execute(String action, JSONArray args, CallbackContext callbackContext),至于重写哪个好,全凭自己喜欢。下面看下ToastPlugin的代码:","text":"Cordova之自定义Plugin本文通过自定义一个简单的Toast插件来介绍一下如何从0开始自定义自己的plugin,基于cordova6.2.1版本。在这之前你需要创建一个Cordova项目,如何创建一个Corodva项目可以参考Cordova:Plugin中的1-5步骤,这里不在赘述。 1.原生插件开发首先创建一个ToastPlugin,我把他放在了主工程中,所有自定义的Plugin都是继承自CordovaPlugin,然后重写CordovaPlugin的execute方法来实现插件的功能,CordovaPlugin中定义了三个execute,这里我选择重写方法public boolean execute(String action, JSONArray args, CallbackContext callbackContext),至于重写哪个好,全凭自己喜欢。下面看下ToastPlugin的代码: 12345678910111213141516171819202122232425262728293031323334public class ToastPlugin extends CordovaPlugin { private String ACTION_TOAST = \"toast\"; /** * @param action 具体要执行的动作,由H5传入 * @param args 参数列表,H5传入 * @param callbackContext 回调 * @return true:有效的action false:无效的action * @throws JSONException */ @Override public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException { if (ACTION_TOAST.equals(action)) { toast(args.getString(0), callbackContext); return true; } return false; } /** * 处理toast逻辑 * * @param content toast弹出的内容 * @param callbackContext 回调 */ private void toast(String content, CallbackContext callbackContext) { Toast.makeText(cordova.getActivity(), content, Toast.LENGTH_LONG).show(); PluginResult pluginResult = new PluginResult(PluginResult.Status.OK, \"this is toast callback content !\"); callbackContext.sendPluginResult(pluginResult); }} 上面就是一个简单的插件,execute是入口,接收H5发送过来的请求,根据具体的action来执行不同的逻辑,并根据结果来通过callbackContext发送回调结果。参数理解:action: 应该很好理解的,比如我们现在写的是一个对话框插件,可能就会有按钮操作,那么需要定义三个action:alert, cancel, confirm。根据不同的action执行不同的逻辑。args: 参数,是一个数组的形式,H5在调用的时候会把参数封装成一个json数组格式的字符串。callbackContext: 原生插件通过这个参数来给H5发送反馈,当然这个不是必须的,根据需要来写。发送的数据类型为PluginResult,状态PluginResult.Status.OK表示成功,状态’PluginResult.Status.ERROR’表示失败,当然还是有其他类型的状态,可以查看luginResult.Status的具体定义。 2.注册原生插件第1步中创建了一个原生插件,下一步就是注册插件,只有注册了插件,在Cordova初始化的时候才能根据注册信息来获取该插件并实例化,后面调用的时候才能找得到它。配置的地方就是res目录下的xml目录下的config.xml文件。在其中加一个节点:12345678910111213<?xml version='1.0' encoding='utf-8'?><widget id=\"com.helong.cordova\" version=\"1.0.0\" xmlns=\"http://www.w3.org/ns/widgets\" xmlns:cdv=\"http://cordova.apache.org/ns/1.0\"> <feature name=\"Whitelist\"> <param name=\"android-package\" value=\"org.apache.cordova.whitelist.WhitelistPlugin\" /> <param name=\"onload\" value=\"true\" /> </feature> <feature name=\"Toast\"> <param name=\"android-package\" value=\"com.helong.plugin.ToastPlugin\" /> </feature> .....此处为节省空间省略</widget> 一个feature代表一个原生插件,属性name是这个原生插件的名字,param的android-package用来指定这个插件的具体实现类。OK,配置完毕,这样原生部分的工作已经完成了。下面看js方面的工作。 3.js插件开发首先在assets目录下的plugins目录下创建一个目录,作为新js插件的目录,目录名cordova-plugin-toast,然后在此目录下创建新的目录,目录名www,最后在www目录下创建我们的js插件toast.js,看图:接下来是具体的实现:1234567891011cordova.define(\"cordova-plugin-toast.toast\", function(require, exports, module) { var exec = require('cordova/exec'); var platform = require('cordova/platform'); module.exports = { toast: function(message, completeCallback) { var _message = (typeof message === \"string\" ? message : JSON.stringify(message)); //success, fail, service, action, args exec(completeCallback, null, \"Toast\", \"toast\", [_message]); }, };}); 代码中define的第一个参数”cordova-plugin-toast.toast”是这个plugin的id,在后面会用到。插件的实现部分在module.exports部分,这里面定义了一个函数名字叫toast,有两个参数分别是要展示的内容以及回调,通过exec执行,将内容以及回调传递给原生插件,也就是步骤1中我们写的原生插件。exec说明:exec就是函数function androidExec(success, fail, service, action, args),具体实现可以看exec.js,在assets/www/corodva-js-src目下,接收的五个参数具体说一下。 success是成功回调,对应步骤1中PluginResult的Status.OK fail是失败回调,对应PluginResult的Status.ERROR service就是我们原生插件名称,也就是步骤2中注册原生插件中feature节点的name属性定义的名字。 action就是我们原生插件实现是定义的一系列action,步骤一中也有说明,原生插件接到请求根据service找到插件,然后再根据action执行具体的逻辑。 args就是参数,json数组格式,步骤一中也有说明好了,js插件开发完毕,下一步跟原生一样,注册插件。 3.注册js插件找到此文件assets/www/cordova_plugins.js,我们的js插件需要在这里进行注册了才能被访问,因为cordova.js在加载的时候会从这里面读取信息。12345678910111213141516171819cordova.define('cordova/plugin_list', function(require, exports, module) {module.exports = [ { \"id\": \"cordova-plugin-toast.toast\", \"file\": \"plugins/plugin-toast/www/toast.js\", \"pluginId\": \"cordova-plugin-toast\", \"clobbers\": [ \"toast123\" ] }];module.exports.metadata = // TOP OF METADATA{ \"cordova-plugin-whitelist\": \"1.3.2\", \"cordova-plugin-toast\": \"0.0.1\"};// BOTTOM OF METADATA}); 这里的module.exports是一个Json数组,每一项代表一个插件,说一下插件配置每一项的意思:id: 就是定义js插件define的第一个参数,在步骤3中已经说明。file: 很好理解,就是我们js插件所在的路径pluginId: 插件的pluginId,下面在设置版本信息的时候会用到,其他地方有没有用到,暂时没遇到clobbers: 这个类似于实例,我们在后面用到js插件的时候就是通过这名称来调用插件里面的函数的,是个数组,可以定义成多个名字。还有另外一种方式,就是使用merges定义,代表允许不同的js插件可以取一个相同的实例名称,例如cordova官方的一个demo如下。12345678910111213141516{ \"id\": \"cordova-plugin-dialogs.notification\", \"file\": \"plugins/cordova-plugin-dialogs/www/notification.js\", \"pluginId\": \"cordova-plugin-dialogs\", \"merges\": [ \"navigator.notification\" ]},{ \"id\": \"cordova-plugin-dialogs.notification_android\", \"file\": \"plugins/cordova-plugin-dialogs/www/android/notification.js\", \"pluginId\": \"cordova-plugin-dialogs\", \"merges\": [ \"navigator.notification\" ]} OK ,js插件已经注册完毕,下面就是使用了吧。 4.调用js插件这里用一个比较简单的例子,就是在设备准备好的时候执行这个js。我们在assets/www/js目录下新建一个ready.js文件,然后编码1234567function success(message) { alert(message);}function onDeviceReady(){ toast123.toast('Hello My Plaugin !',success);}document.addEventListener(\"deviceready\", onDeviceReady, false); 嗯,就是监听deviceready事件,然后进行响应,捕获到此事件后进行toast,并接收回调。 5.加载js文件插件的开发以及调用都已经开发完毕,最后一步就是执行了吧。这里使用assets/www/index.html,app启动时默认加载这个html,我们就再这里调用4中的ready.js文件。在原先的html中加入js引用<script type="text/javascript" src="js/ready.js"></script>。万事俱备,只差运行了吧。 6.运行看效果图,下面是toast的内容,上面的对话框里的内容就是我们原生插件设置callback时 的内容。 一个简单的插件完成了,算是入门了,剩下的就要靠自己了。","categories":[{"name":"Cordova","slug":"Cordova","permalink":"http://yoursite.com/categories/Cordova/"}],"tags":[{"name":"Cordova","slug":"Cordova","permalink":"http://yoursite.com/tags/Cordova/"}],"keywords":[{"name":"Cordova","slug":"Cordova","permalink":"http://yoursite.com/categories/Cordova/"}]},{"title":"Android项目集成小米、华为、个推","slug":"JingoalPush_2016-11-25","date":"2016-11-25T11:16:33.000Z","updated":"2016-11-25T11:28:42.000Z","comments":true,"path":"2016/11/25/JingoalPush_2016-11-25/","link":"","permalink":"http://yoursite.com/2016/11/25/JingoalPush_2016-11-25/","excerpt":"为了项目中方便使用,首先要把这三家push sdk集成到一个library项目中,并将通过广播接收到的推送消息统一分发到一个广播中,在主项目中直接引用此项目即可。项目地址:JingoalPushDemo集成很简单: 1.添加项目依赖项目中导入pushlibrary,在主项目的build.gradle添加对libray项目的依赖:compile project(':pushlibrary') 2.申请帐号在三个平台注册开发者帐号,并创建自己的应用,获取所需要的appid、appkey等信息,华为的需要打包签名文件的SHA256值,这个在新建华为帐号的时候会有说明。小米推送需要信息:APPID , APPKEY个推推送需要信息:APPID , APPKEY,APPSECRET华为推送需要信息:华为推送客户端不需要设置这些参数,只要保证包名以及打包签名文件的SHA256信息跟开发者平台配置的一样即可。","text":"为了项目中方便使用,首先要把这三家push sdk集成到一个library项目中,并将通过广播接收到的推送消息统一分发到一个广播中,在主项目中直接引用此项目即可。项目地址:JingoalPushDemo集成很简单: 1.添加项目依赖项目中导入pushlibrary,在主项目的build.gradle添加对libray项目的依赖:compile project(':pushlibrary') 2.申请帐号在三个平台注册开发者帐号,并创建自己的应用,获取所需要的appid、appkey等信息,华为的需要打包签名文件的SHA256值,这个在新建华为帐号的时候会有说明。小米推送需要信息:APPID , APPKEY个推推送需要信息:APPID , APPKEY,APPSECRET华为推送需要信息:华为推送客户端不需要设置这些参数,只要保证包名以及打包签名文件的SHA256信息跟开发者平台配置的一样即可。 3.配置个推so库下载个推的so库文件,放入主项目的libs目录下,并在主项目的build.gradle项目中添加依赖配置,位于android节点里面:12345sourceSets { main { jniLibs.srcDirs = ['libs'] }} 4.配置小米、个推的APPID、APPKEY信息在defaultConfig节点下添加配置信息,下面配置中的**我是为了隐藏自己的信息的,有一个地方需要注意,小米的APPID和APPKEY都是纯数字,在配置的时候需要在前面加上\\\\0,切记。1234567891011121314defaultConfig { ...(省略默认配置) //第三方推送所需要的APPID、APPKEY的配置 manifestPlaceholders = [ GETUI_APP_ID : \"lHv2qKvQNK9FCSlcL****\", GETUI_APP_KEY : \"Tcd6DHuozvAu7iQEl****\", GETUI_APP_SECRET: \"SaeqLmuJcy5xjNLz****\", //因为小米的appkey 和 appid 为纯数字,必须要在前面加上\\0, //程序中才能正确从meta-data中读取,下面的\\\\0的第一个\\为转义字符 MI_PUSH_APPID: \"\\\\028823037615175****\", MI_PUSH_APPKEY: \"\\\\0597175232****\", PACKAGE_NAME : applicationId ]} 5.设置debug签名文件这个是为了方便测试华为推送而设置的,如果你把默认签名文件的SHA256设置到华为推送配置中,就不用设置了。将debug.keystore拷贝到主项目目录下:在build.gradle的android节点下添加配置:12345678910111213141516signingConfigs { debug { storeFile file(\"debug.keystore\") }}buildTypes { debug { minifyEnabled false signingConfig signingConfigs.debug } release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' }} 6.清单文件中添加权限配置以及广播接收器添加以下权限:123456789101112<!-- 自定义权限 --><uses-permission android:name=\"getui.permission.GetuiService.${PACKAGE_NAME}\"/><!--替换为第三方应用的包名--><permission android:name=\"getui.permission.GetuiService.${PACKAGE_NAME}\" android:protectionLevel=\"normal\"/><permission android:name=\"${PACKAGE_NAME}.permission.MIPUSH_RECEIVE\" android:protectionLevel=\"signature\"/><uses-permission android:name=\"${PACKAGE_NAME}.permission.MIPUSH_RECEIVE\"/><uses-permission android:name=\"android.permission.VIBRATE\"/> 配置meta属性:123456789101112131415161718<!-- 配置的第三方参数属性 --><!-- 个推Push配置 --><meta-data android:name=\"PUSH_APPID\" android:value=\"${GETUI_APP_ID}\"/><meta-data android:name=\"PUSH_APPKEY\" android:value=\"${GETUI_APP_KEY}\"/><meta-data android:name=\"PUSH_APPSECRET\" android:value=\"${GETUI_APP_SECRET}\"/><!-- 小米Push配置 --><meta-data android:name=\"MI_PUSH_APPID\" android:value=\"${MI_PUSH_APPID}\"/><meta-data android:name=\"MI_PUSH_APPKEY\" android:value=\"${MI_PUSH_APPKEY}\"/> 添加广播接收器:123456789101112<receiver android:name=\"com.jingoal.push.receiver.GeTuiReceiver\" android:exported=\"false\"> <intent-filter> <action android:name=\"com.igexin.sdk.action.${GETUI_APP_ID}\"/> </intent-filter></receiver><provider android:name=\"com.igexin.download.DownloadProvider\" android:authorities=\"downloads.${PACKAGE_NAME}\" android:exported=\"true\" android:process=\":igepushservice\"/> 7.接收消息以上0-5的配置完毕之后就剩下最后一步了,在项目中创建一个广播接收器用来接收推送消息!1234567891011121314151617181920212223242526272829303132333435import android.content.Context;import android.util.Log;import com.jingoal.push.receiver.JingoalReceiver;import com.jingoal.push.sdk.JingoalPushManager;import com.jingoal.pushsdk3.MainActivity;public class MyJingoalReceiver extends JingoalReceiver { @Override public void onReceivePassThroughMessage(Context context, String message) { Log.i(\"holo_push\", \"========================\"); Log.i(\"holo_push\", \"透传消息: \" + message); if(MainActivity.getInstance()!=null) { MainActivity.getInstance().setRecvPushMessage(message); } Log.i(\"holo_push\", \"========================\"); } @Override public void onNotificationMessageClicked(Context context, String message) { Log.i(\"holo_push\", \"========================\"); Log.i(\"holo_push\", \"小米消息: \" + message); if(MainActivity.getInstance()!=null) { MainActivity.getInstance().setRecvPushMessage(message); } Log.i(\"holo_push\", \"========================\"); } @Override public void onCommandResult(Context context, String command, boolean success) { Log.i(\"holo_push\", \"command :\" + command); Log.i(\"holo_push\", \"command Result :\" + success); if(JingoalReceiver.COMMAND_REGISTER.equals(command)) { Log.i(\"holo_push\", \"clientType: \" + JingoalPushManager.getClientType() + \"\"); } }} onReceivePassThroughMessage:用来接收华为和个推的透传消息。onNotificationMessageClicked:因为小米推送在程序杀掉后无法接收透传消息,只能接收通知栏消息,所以这里集成的是通知栏自定义动作消息,就是在点击通知栏消息时我们客户端才能拿到真正的消息内容。onCommandResult:用来接收客户端注册push,设置别名,删除别名等command的结果的回调,因为华为官方给出服务端的API已经不支持标签和别名,所以这个方法对华为无意义。 接收的Receiver也创建完毕,在清单文件中配置一下就OK啦:12345<receiver android:name=\".receiver.MyJingoalReceiver\"> <intent-filter> <action android:name=\"com.jingoal.push.receiver.jingoalreceiver\"/> </intent-filter></receiver> OK,现在运行项目,通过各开发者平台发送消息看能不能接收到吧。注意:华为的开发者平台推送消息时要通过手机的IMEI号来推送,集成到项目中时,服务端要通过token来推送。","categories":[{"name":"Push","slug":"Push","permalink":"http://yoursite.com/categories/Push/"}],"tags":[],"keywords":[{"name":"Push","slug":"Push","permalink":"http://yoursite.com/categories/Push/"}]},{"title":"Cordova:Plugin","slug":"Cordova_Plugin_2016-11-25","date":"2016-11-25T06:37:51.000Z","updated":"2016-11-25T07:09:51.000Z","comments":true,"path":"2016/11/25/Cordova_Plugin_2016-11-25/","link":"","permalink":"http://yoursite.com/2016/11/25/Cordova_Plugin_2016-11-25/","excerpt":"跟着Cordova:HelloWord搞定了怎么创建一个Android项目之后,看看怎么使用Cordova的Plugin吧。因为使用命令行创建的项目导图AS时报错,有问题,所以这里直接创建一个项目,然后将需要的文件拷贝到项目中去即可。 1.添加插件 首先要通过cordova命令添加所需要的插件,此次测试一个比较简单的插件dialog,添加的命令cordova plugin add cordova-plugin-dialogs,看下图:OK,添加成功了,从上图中可以看到进入到android/assets/www/plugins目录中已经有我们刚才添加的插件了,下面开始看怎么使用他吧。","text":"跟着Cordova:HelloWord搞定了怎么创建一个Android项目之后,看看怎么使用Cordova的Plugin吧。因为使用命令行创建的项目导图AS时报错,有问题,所以这里直接创建一个项目,然后将需要的文件拷贝到项目中去即可。 1.添加插件 首先要通过cordova命令添加所需要的插件,此次测试一个比较简单的插件dialog,添加的命令cordova plugin add cordova-plugin-dialogs,看下图:OK,添加成功了,从上图中可以看到进入到android/assets/www/plugins目录中已经有我们刚才添加的插件了,下面开始看怎么使用他吧。 2.创建新工程 下面是心创建的一个全新的项目HelloCordova 3.引入CordovaLib库 3.1首先创建一个Library 3.2拷贝CordovaLib源文件Library创建完成后将我们需要的代码拷贝进来,要拷贝的代码在此目录下:直接拷贝到项目中: 3.3拷贝Plugin源代码Plugin的源代码在android/src目录下:拷贝后:Device是创建项目的时候就有的,dialogs就是我们刚才添加的。 3.4拷贝Cordova配置文件下面要拷贝的是res目录下的config文件,同样是拷贝到cordovalib的res目录下: config.xml的内容感兴趣的可以自己搜一下,里面主要是配置一些插件信息以及项目信息 4.拷贝H5资源 H5资源位于android/assets目录下: H5资源拷贝到主工程同样的位置: 5.Activity加载 首先要在主工程的build.gradle中添加对cordovalib的依赖: 直接使用android/src下的MainActivity:1234567891011121314151617public class MainActivity extends CordovaActivity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // enable Cordova apps to be started in the background Bundle extras = getIntent().getExtras(); if (extras != null && extras.getBoolean("cdvStartInBackground", false)) { moveTaskToBack(true); } Log.e("holo_cordova", "launchUrl: " + launchUrl); // Set by <content src="index.html" /> in config.xml loadUrl(launchUrl); }} 看注释loadUrl(launchUrl);中的launchUrl是在res/xml/config.xml中配置的,对应的是assets/www/index.html.OK,基本搞定了80%,下面就是对index.html改造,引入我们刚才添加的Plugin dialogs. 6.在js文件中使用dialogs Plugin 首先我们在assets/js目录下创建一个js文件,就叫dialogsplugin.js,然后开始编码:12345678910111213141516171819function onConfirm(buttonIndex) { console.log('confirm dialog cancel ..........'); navigator.notification.alert('You selected button ' + buttonIndex); console.log('confirm dialog cancel __________');}function onDeviceReady(){ console.log('will alert confirm dialog'); navigator.notification.confirm( 'You are the winner xx!', // message onConfirm, // callback to invoke with index of button pressed 'Game Over', // title ['Restart','Exit'] // buttonLabels ); console.log('confirm dialog alert end');}document.addEventListener(\"deviceready\", onDeviceReady, false); js编码完成,在html中引入就大功告成了1<script type="text/javascript" src="js/dialogsplugin.js"></script> 然后运行项目,看效果图:","categories":[{"name":"Cordova","slug":"Cordova","permalink":"http://yoursite.com/categories/Cordova/"}],"tags":[{"name":"Cordova","slug":"Cordova","permalink":"http://yoursite.com/tags/Cordova/"}],"keywords":[{"name":"Cordova","slug":"Cordova","permalink":"http://yoursite.com/categories/Cordova/"}]},{"title":"Cordova : HelloWord","slug":"Cordova_Helloword_2016-11-22","date":"2016-11-22T10:50:33.000Z","updated":"2016-11-22T11:06:31.000Z","comments":true,"path":"2016/11/22/Cordova_Helloword_2016-11-22/","link":"","permalink":"http://yoursite.com/2016/11/22/Cordova_Helloword_2016-11-22/","excerpt":"","text":"开发环境Mac OS,另外Android的开发环境也需要提前搭建好。 1.安装Node.js 官网下载最新的http://nodejs.cn/download/安装包,并安装,安装成功后命令行执行npm -version会打印出当前Node的版本号。 2.安装Cordova 到Cordova官网,也有安装的步骤:http://cordova.apache.org/#getstartedsudo npm install -g cordova,这一步可能会比较慢,耐心等待。安装完成后命令行执行cordova -v会打印当前Cordova的版本信息。 3.创建项目 上面的两个步骤安装完成就可以通过cordova命令创建一个Cordova项目,看一下怎么创建,执行命令:cordova create --help查看帮助信息。123456789101112131415161718192021➜ Cordova cordova create --help Synopsis cordova create <PATH> [ID [NAME [CONFIG]]] [options]Create a Cordova project PATH ......................... Where to create the project ID ........................... Reverse-domain-style package name - used in <widget id> NAME ......................... Human readable name CONFIG ....................... json string whose key/values will be included in [PATH]/.cordova/config.jsonOptions --template=<PATH|NPM PACKAGE|GIT URL> ... use a custom template located locally, in NPM, or GitHub. --copy-from|src=<PATH> .................. deprecated, use --template instead. --link-to=<PATH> ........................ symlink to custom www assets without creating a copy. Example cordova create myapp com.mycompany.myteam.myapp MyApp 根据最后Example我来创建一个HelloWord的项目:cordova create helloword com.holo.helloword HelloWord控制台信息:1234➜ Cordova cordova create helloword com.holo.helloword HelloWordUsing detached cordova-createCreating a new cordova project.➜ Cordova 说明创建成功了,进入到项目目录下,并看一下helloword有哪些目录123456➜ Cordova lsasApps helloword myApp3 pluginDemos➜ Cordova cd helloword ➜ helloword lsconfig.xml hooks platforms plugins www➜ helloword 这时候只是创建了一些基本信息,还不算一个项目,因为要创建的是一个Android项目,所以需要添加android平台,执行cordova platform add android,执行之前看一下platforms目录,是空的。123➜ helloword cd platforms➜ platforms ls➜ platforms 执行cordova platform add android:123456789101112131415161718192021222324252627282930➜ platforms ls➜ platforms cd ..➜ helloword cordova platform add android Adding android project...Creating Cordova project for the Android platform: Path: platforms/android Package: com.holo.helloword Name: HelloWord Activity: MainActivity Android target: android-24Subproject Path: CordovaLibAndroid project created with [email protected] plugin "cordova-plugin-whitelist" in config.xml. Adding it to the projectFetching plugin "cordova-plugin-whitelist@1" via npmInstalling "cordova-plugin-whitelist" for androidANDROID_HOME=/Users/a1/Library/Android/sdkJAVA_HOME=/Library/Java/JavaVirtualMachines/jdk1.8.0_111.jdk/Contents/HomeSubproject Path: CordovaLibStarting a new Gradle Daemon for this build (subsequent builds will be faster).Incremental java compilation is an incubating feature.:clean:CordovaLib:cleanBUILD SUCCESSFULTotal time: 8.353 secs This plugin is only applicable for versions of cordova-android greater than 4.0. If you have a previous platform version, you do *not* need this plugin since the whitelist will be built in. ➜ helloword OK ,android项目创建成功了,如果中间遇到什么问题,请根据控制台中输出的错误信息修正就行了,我刚开始的时候是没安装Android sdk platfrom 24 就报错了。再次进入platforms文件夹:1234➜ helloword cd platforms➜ platforms lsandroid platforms.json➜ platforms 会有一个android目录,这个就是一个继承了Cordova的安卓项目了。1234➜ android ls AndroidManifest.xml android.json build.gradle gradle libs project.properties settings.gradleCordovaLib assets cordova gradlew platform_www res src➜ android 下面编译一下:android cordova build android --info.编译成功后进入下面的目录:1234➜ android cd build/outputs/apk ➜ apk lsandroid-debug.apk➜ apk 会有一个android-debug.apk文件,安装到手机上之后是下面这个样子:既然显示的是HTML文件,那这个是哪个呢?1234➜ android cd assets/www/ ➜ www lscordova-js-src cordova.js cordova_plugins.js css img index.html js➜ www 就是assets/www目录下的index.html,这个是在res/xml/config.xml中配置的。OK,第一步走的很艰难啊,看似简单,但是从0开始到运行一个HelloWord还是经过了很多心血的,不过第一步成功了,后面就好办多了^_^。 参考:http://www.cnblogs.com/zhoujg/p/4560998.html","categories":[{"name":"Cordova","slug":"Cordova","permalink":"http://yoursite.com/categories/Cordova/"}],"tags":[{"name":"Cordova","slug":"Cordova","permalink":"http://yoursite.com/tags/Cordova/"}],"keywords":[{"name":"Cordova","slug":"Cordova","permalink":"http://yoursite.com/categories/Cordova/"}]},{"title":"操作符Create","slug":"RxJava_Create_2016-09-24","date":"2016-09-24T08:22:49.000Z","updated":"2016-09-24T08:22:49.000Z","comments":true,"path":"2016/09/24/RxJava_Create_2016-09-24/","link":"","permalink":"http://yoursite.com/2016/09/24/RxJava_Create_2016-09-24/","excerpt":"1.Observables 先认识几个重要的概念 Observable:一个可观察对象或者被观察者 Observer:观察者(订阅者) Subscribe:Observer的子类,比Observer多了onStart和unsubscribe两个方法 在RxJava中,一个实现了Observer接口的对象可以订阅(subscribe)一个Observable 类的实例。订阅者(subscriber)对Observable发射(emit)的任何数据或数据序列作出响应。","text":"1.Observables 先认识几个重要的概念 Observable:一个可观察对象或者被观察者 Observer:观察者(订阅者) Subscribe:Observer的子类,比Observer多了onStart和unsubscribe两个方法 在RxJava中,一个实现了Observer接口的对象可以订阅(subscribe)一个Observable 类的实例。订阅者(subscriber)对Observable发射(emit)的任何数据或数据序列作出响应。 2.操作符Create 2.1 概要步骤: 创建被观察者 Observable observable = new Observable(); 创建观察者 Observer observer = new Observer(); 观察者订阅被观察者 observable.subscribe(observer); 2.2 实际使用: 1.创建被观察者 Observable<String> observable = Observable.create(new Observable.OnSubscribe<String>(){ @Override public void call(Subscriber<? super String> subscriber) { // TODO(HeLong.W): todo somthing }}); 2.创建观察者 Observer<String> observer = new Observer<String>() { @Override public void onCompleted() { } @Override public void onError(Throwable e) { } @Override public void onNext(String o) { } }; 3.观察者订阅被观察者observable.subscribe(observer); 综合1、2、3来写: Observable.create(new Observable.OnSubscribe<String>() { @Override public void call(Subscriber<? super String> subscriber) { subscriber.onNext(\"Hello\"); subscriber.onCompleted(); } }).subscribe(new Subscriber<String>() { @Override public void onCompleted() { System.out.println(\"onCompleted exe\"); } @Override public void onError(Throwable e) { } @Override public void onNext(String s) { System.out.println(s); } }); 2.3 源码分析 先看一下Observable.create()了什么 1234/*Observable.class*/public static <T> Observable<T> create(OnSubscribe<T> f) { return new Observable<T>(hook.onCreate(f));} create方法很简单,就是调用了默认的构造器,并且传入一个构造器需要的参数,参数值由hook.onCreate(f)返回,先看看这个hook.onCreate(f)是什么鬼: 1234567/*Observable.class 中定义的hookd变量*/RxJavaObservableExecutionHook hook = RxJavaPlugins.getInstance().getObservableExecutionHook();/*RxJavaObservableExecutionHook.class*/public <T> OnSubscribe<T> onCreate(OnSubscribe<T> f) { return f;} 从上面的代码可以看到hook.onCreate(f)什么也没做,就是直接返回f,接下来看看默认的构造器干什么了: 123protected Observable(OnSubscribe<T> f) { this.onSubscribe = f;} 也是很简单的给自己的成员变量onSubscribe赋值而已。那么接下来看看这个OnSubscribe是什么呢?从上面的代码看OnSubscribe是Observable的一个内部类: 123public interface OnSubscribe<T> extends Action1<Subscriber<? super T>> { // cover for generics insanity} 额,原来是一个接口,看他的超类定义: 1234567/** * A one-argument action. * @param <T> the first argument type */public interface Action1<T> extends Action { void call(T t);} 也很简洁,就是一个带有一个泛型参数的接口,那么上面的子类OnSubscribe就是指定了这个泛型的类型为Subscriber类型的,而指定的这个Subscriber就是后面要说的观察者。 看完了被观察者,再来看看观察者,一个接口类,提供了三个接口: 12345678public interface Observer<T> { void onCompleted(); void onError(Throwable e); void onNext(T t);} 我们上面设置观察者的时候使用的是Subscriber,那么Subscriber跟Observer的关系是什么? 123456789101112131415public abstract class Subscriber<T> implements Observer<T>, Subscription{ @Override public final void unsubscribe() { subscriptions.unsubscribe(); } @Override public final boolean isUnsubscribed() { return subscriptions.isUnsubscribed(); } public void onStart() { // do nothing by default }} 看上去有点复杂,多了几个方法,onStart , unsubscribe , isUnsubscribed.不多做说明,后面会说到,只需要知道Subscriber和Observer是继承关系即可,都可以作为观察者。接下来看最关键的一部分了,观察者与被观察者关联的过程: 1234567/*Observable.class*/ public final Subscription subscribe(final Observer<? super T> observer) { if (observer instanceof Subscriber) { return subscribe((Subscriber<? super T>)observer); } return subscribe(new ObserverSubscriber<T>(observer)); } 虽然有个if判断,但是两个分支最后走的是同一个方法,所以直接看这个Subscription subscribe(Subscriber<? super T> subscriber)实现: 1234/*Observable.class*/public final Subscription subscribe(Subscriber<? super T> subscriber) { return Observable.subscribe(subscriber, this);} 额,还没走到真正处理的地方,继续跟,一大波代码来临,终于看见了曙光: 12345678910111213141516171819static <T> Subscription subscribe(Subscriber<? super T> subscriber, Observable<T> observable) { if (subscriber == null) { throw new IllegalArgumentException(\"subscriber can not be null\"); } if (observable.onSubscribe == null) { throw new IllegalStateException(\"onSubscribe function can not be null.\"); } subscriber.onStart(); if (!(subscriber instanceof SafeSubscriber)) { subscriber = new SafeSubscriber<T>(subscriber); } try { hook.onSubscribeStart(observable, observable.onSubscribe).call(subscriber); return hook.onSubscribeReturn(subscriber); } catch (Throwable e) { //异常处理.... return Subscriptions.unsubscribed(); }} 又碰到了hook,看看hook.onSubscribeStart(observable, observable.onSubscribe)这个干嘛的: 1234public <T> OnSubscribe<T> onSubscribeStart(Observable<? extends T> observableInstance, final OnSubscribe<T> onSubscribe) { // pass through by default return onSubscribe;} 依然什么也没做,直接返回了第二个参数onSubscribe,那hook.onSubscribeStart(observable, observable.onSubscribe).call(subscriber);可以替换成observable.onSubscribe.call(subscriber);,接下来关键就看这个call()做了什么?先看看调用这个call的是谁?是observable.onSubscribe,那这个observable.onSubscribe又是谁?他就是我们第一步create时设置的onSubscribe!所以这里的call做了什么,取决于我们!一般我们就在call里面先处理数据,然后调用onNext方法即可。最后看一下还有一个return hook.onSubscribeReturn(subscriber),看一下内部实现: 1234public <T> Subscription onSubscribeReturn(Subscription subscription) { // pass through by default return subscription;} 依然什么也没做,把参数直接返回了。那么返回的这个Subscription是干嘛用的,我们来看看: 1234public interface Subscription { void unsubscribe(); boolean isUnsubscribed();} 原来是用来取消订阅观察的,如果调用了unsubscribe()后,这个观察者就不再起作用了。","categories":[{"name":"RxJava","slug":"RxJava","permalink":"http://yoursite.com/categories/RxJava/"}],"tags":[{"name":"Rxjava_Create","slug":"Rxjava-Create","permalink":"http://yoursite.com/tags/Rxjava-Create/"}],"keywords":[{"name":"RxJava","slug":"RxJava","permalink":"http://yoursite.com/categories/RxJava/"}]},{"title":"Android MediaCodec 硬编码H264格式","slug":"2016-03-09-mediacodec","date":"2016-09-23T16:56:16.000Z","updated":"2016-09-24T08:09:23.000Z","comments":true,"path":"2016/09/24/2016-03-09-mediacodec/","link":"","permalink":"http://yoursite.com/2016/09/24/2016-03-09-mediacodec/","excerpt":"最近在研究EasyDarwin的Push库EasyPuhser,EasyPuhser可以推送H264视频到Easydarwin服务器,终端可以通过rtsp协议访问该实时流,达到手机直播的功能,延迟基本在2秒以内。EasyDarwinQQ群:496258327本文主要记录一下最近研究的关于Android手机如何获取实时画面,并将数据编码为H264的格式的视频流,编码使用的是Android自带的MediaCodec,也就是硬解。本demo的下载地址:MediaCodecDemo","text":"最近在研究EasyDarwin的Push库EasyPuhser,EasyPuhser可以推送H264视频到Easydarwin服务器,终端可以通过rtsp协议访问该实时流,达到手机直播的功能,延迟基本在2秒以内。EasyDarwinQQ群:496258327本文主要记录一下最近研究的关于Android手机如何获取实时画面,并将数据编码为H264的格式的视频流,编码使用的是Android自带的MediaCodec,也就是硬解。本demo的下载地址:MediaCodecDemo MediaCodec是Android在4.1中加入的新的API,目前也有很多文章介绍MediaCodec的用法,但是很多时候很多手机都失败,主要问题出现在调用dequeueOutputBuffer的时候总是返回-1,让你以为No buffer available !这里介绍一个开源项目libstreaming,我们借助此项目中封装的一个工具类EncoderDebugger,来初始化MediaCodec会很好的解决此问题,目前为止测试了几个手机都可以成功,包括小米华为Moto。看一下怎么使用的 12EncoderDebugger debugger = EncoderDebugger.debug(getApplicationContext(), width, height);MediaCodec mMediaCodec = MediaCodec.createByCodecName(debugger.getEncoderName()); 嗯,就这样。当然了,后面还是要根据需要对mMediaCodec设置其他参数的,看一下本demo中设置参数的过程吧 123456789101112131415161718192021222324252627private void initMediaCodec() { int dgree = getDgree(); framerate = 15; bitrate = 2 * width * height * framerate / 20; EncoderDebugger debugger = EncoderDebugger.debug(getApplicationContext(), width, height); mConvertor = debugger.getNV21Convertor(); try { mMediaCodec = MediaCodec.createByCodecName(debugger.getEncoderName()); MediaFormat mediaFormat; if (dgree == 0) { //dree==0的时候,需要将画面旋转90度,所以这里编码的时候需要将宽和高颠倒, //否则编码后的会面会出现四重画面并且花屏 mediaFormat = MediaFormat.createVideoFormat(\"video/avc\", height, width); } else { mediaFormat = MediaFormat.createVideoFormat(\"video/avc\", width, height); } mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, bitrate); mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, framerate); mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, debugger.getEncoderColorFormat()); mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1); mMediaCodec.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); mMediaCodec.start(); } catch (IOException e) { e.printStackTrace(); }} 编码之前先看一下要编码的数据怎么获取吧,这个当然是来自Camera。首先是创建SurfaceView用于预览视频画面,并设置回调,来监控生命周期。 1234surfaceView = (SurfaceView) findViewById(R.id.sv_surfaceview);surfaceView.getHolder().addCallback(this);surfaceView.getHolder().setFixedSize(getResources().getDisplayMetrics().widthPixels,getResources().getDisplayMetrics().heightPixels); 然后是创建Camera的方法: 1234567891011121314151617181920212223242526272829303132333435363738394041424344private boolean ctreateCamera(SurfaceHolder surfaceHolder) { try { //mCameraId=Camera.CameraInfo.CAMERA_FACING_BACK mCamera = Camera.open(mCameraId); Camera.Parameters parameters = mCamera.getParameters(); Camera.CameraInfo camInfo = new Camera.CameraInfo(); Camera.getCameraInfo(mCameraId, camInfo); int cameraRotationOffset = camInfo.orientation; //设置预览格式NV21,他属于YUV420SP parameters.setPreviewFormat(ImageFormat.NV21); parameters.setPreviewSize(width, height); mCamera.setParameters(parameters); mCamera.autoFocus(null); //计算preview画面需要旋转的角度。目前木有做横竖屏切换的时候无缝旋转画面,后面再搞。 int displayRotation = (cameraRotationOffset - getDgree() + 360) % 360; mCamera.setDisplayOrientation(displayRotation); mCamera.setPreviewDisplay(surfaceHolder); return true; } catch (Exception e) { destroyCamera(); e.printStackTrace(); return false; }}private int getDgree() { int rotation = getWindowManager().getDefaultDisplay().getRotation(); int degrees = 0; switch (rotation) { case Surface.ROTATION_0: degrees = 0; break; // Natural orientation case Surface.ROTATION_90: degrees = 90; break; // Landscape left case Surface.ROTATION_180: degrees = 180; break;// Upside down case Surface.ROTATION_270: degrees = 270; break;// Landscape right } return degrees;} 摄像头创建完毕,就是开启预览 1234567891011121314151617/** * 开启预览 */public synchronized void startPreview() { if (mCamera != null && !started) { mCamera.startPreview(); int previewFormat = mCamera.getParameters().getPreviewFormat(); Camera.Size previewSize = mCamera.getParameters().getPreviewSize(); int size = previewSize.width * previewSize.height * ImageFormat.getBitsPerPixel(previewFormat) / 8; mCamera.addCallbackBuffer(new byte[size]); mCamera.setPreviewCallbackWithBuffer(previewCallback); started = true; btnSwitch.setText(\"停止\"); }} 上面就是设置了预览回调的方式,回调中将预览画面一帧一帧的返回给我们,给我们的数据就是NV21格式的,根据需要决定是否需要对数据进行旋转,旋转之后,就是转换,将NV21数据转为YUV420P格式的数据,然后就可以编码为H264数据了。 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071Camera.PreviewCallback previewCallback = new Camera.PreviewCallback() { //mSpsPps用来存储sps pps数据,后面遇到关键帧(I帧),必须将spspps数据加到I帧前面 byte[] mSpsPps = new byte[0]; @Override public void onPreviewFrame(byte[] data, Camera camera) { if (data == null) { return; } ByteBuffer[] inputBuffers = mMediaCodec.getInputBuffers(); ByteBuffer[] outputBuffers = mMediaCodec.getOutputBuffers(); byte[] dst = new byte[data.length]; Camera.Size previewSize = mCamera.getParameters().getPreviewSize(); if (getDgree() == 0) { //手机竖屏的时候要将获取的数据顺时针旋转90度,否则画面不是正着的,而是逆时针90度 dst = Util.rotateNV21Degree90(data, previewSize.width, previewSize.height); } else { dst = data; } try { int bufferIndex = mMediaCodec.dequeueInputBuffer(5000000); if (bufferIndex >= 0) { inputBuffers[bufferIndex].clear(); //将YUV420SP数据转换成YUV420P的格式,并将结果存入inputBuffers[bufferIndex] mConvertor.convert(dst, inputBuffers[bufferIndex]); mMediaCodec.queueInputBuffer(bufferIndex, 0, inputBuffers[bufferIndex].position(), System.nanoTime() / 1000, 0); MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo(); int outputBufferIndex = mMediaCodec.dequeueOutputBuffer(bufferInfo, 0); while (outputBufferIndex >= 0) { ByteBuffer outputBuffer = outputBuffers[outputBufferIndex]; byte[] outData = new byte[bufferInfo.size]; //从buff中读取数据到outData中 outputBuffer.get(outData); //记录pps和sps,pps和sps数据开头是0x00 0x00 0x00 0x01 0x67, // 0x67对应十进制103 if (outData[0] == 0 && outData[1] == 0 && outData[2] == 0 && outData[3] == 1 && outData[4] == 103) { mSpsPps = outData; } else if (outData[0] == 0 && outData[1] == 0 && outData[2] == 0 && outData[3] == 1 && outData[4] == 101) { //关键帧开始规则是0x00 0x00 0x00 0x01 0x65,0x65对应十进制101 //在关键帧前面加上pps和sps数据 byte[] iframeData = new byte[mSpsPps.length + outData.length]; System.arraycopy(mSpsPps, 0, iframeData, 0, mSpsPps.length); System.arraycopy(outData, 0, iframeData, mSpsPps.length, outData.length); outData = iframeData; } //至此,这一帧的数据已经经过MediaCodec编码完毕,这个outData就是我们需要的数据了, //因为EasyDarwin可以自动将H264打包为RTP, //所以EasyPusher只需要负责将outData推给EasyDarwin就OK了 //保存H264数据到本地文件easy.h264 Util.save(outData, 0, outData.length, path, true); mMediaCodec.releaseOutputBuffer(outputBufferIndex, false); outputBufferIndex = mMediaCodec.dequeueOutputBuffer(bufferInfo, 0); } } else { Log.e(\"easypusher\", \"No buffer available !\"); } } catch (Exception e) { StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); e.printStackTrace(pw); String stack = sw.toString(); Log.e(\"save_log\", stack); e.printStackTrace(); } finally { mCamera.addCallbackBuffer(dst); } }}; 保存之后的文件easy.h264我用VLC播放器打开,截屏如下: OK,基本上完毕了,该注意的地方都写在代码中了 需要Demo的请到这里https://github.com/kidloserme/MediaCodecDemo","categories":[{"name":"DEFAULT","slug":"DEFAULT","permalink":"http://yoursite.com/categories/DEFAULT/"}],"tags":[{"name":"MediaCodec","slug":"MediaCodec","permalink":"http://yoursite.com/tags/MediaCodec/"}],"keywords":[{"name":"DEFAULT","slug":"DEFAULT","permalink":"http://yoursite.com/categories/DEFAULT/"}]},{"title":"EventBus源码阅读之事件的注册","slug":"2016-03-17-EventBus","date":"2016-09-23T16:56:16.000Z","updated":"2016-09-24T08:09:41.000Z","comments":true,"path":"2016/09/24/2016-03-17-EventBus/","link":"","permalink":"http://yoursite.com/2016/09/24/2016-03-17-EventBus/","excerpt":"之前看过EventBus的源码,不是很深入,导致有些模糊,此次仔细阅读了一下,记录笔记,方便以后熟悉。本篇主要说一下register的过程: 12345678private synchronized void register(Object subscriber, boolean sticky, int priority) { //查找subscriber(执行register的类)中注册事件的方法,onEvent开头,参数只允许一个,超过一个将被忽略 List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriber.getClass()); for (SubscriberMethod subscriberMethod : subscriberMethods) { //根据事件类型以及订阅类进行数据处理 subscribe(subscriber, subscriberMethod, sticky, priority); }}","text":"之前看过EventBus的源码,不是很深入,导致有些模糊,此次仔细阅读了一下,记录笔记,方便以后熟悉。本篇主要说一下register的过程: 12345678private synchronized void register(Object subscriber, boolean sticky, int priority) { //查找subscriber(执行register的类)中注册事件的方法,onEvent开头,参数只允许一个,超过一个将被忽略 List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriber.getClass()); for (SubscriberMethod subscriberMethod : subscriberMethods) { //根据事件类型以及订阅类进行数据处理 subscribe(subscriber, subscriberMethod, sticky, priority); }} findSubscriberMethods这个方法很长,大致要做的事情就是查找出该类以及其父类中声明的所有方法,根据规则(只能public修饰onEvent开头且只有一个参数)筛选出订阅方法,一起看一下吧,具体就在代码中写说明了,删除了部分代码限制篇幅: 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) { //省略部分代码 subscriberMethods = new ArrayList<SubscriberMethod>(); Class<?> clazz = subscriberClass; HashSet<String> eventTypesFound = new HashSet<String>(); StringBuilder methodKeyBuilder = new StringBuilder(); while (clazz != null) { String name = clazz.getName(); //忽略系统类 if (name.startsWith(\"java.\") || name.startsWith(\"javax.\") || name.startsWith(\"android.\")) { break; } //获取该类中声明的所有方法 Method[] methods = clazz.getDeclaredMethods(); for (Method method : methods) { String methodName = method.getName(); //是否以onEvent开头 if (methodName.startsWith(ON_EVENT_METHOD_NAME)) { int modifiers = method.getModifiers(); //修饰符只能以public开头 if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0) { Class<?>[] parameterTypes = method.getParameterTypes(); //只能有一个参数 if (parameterTypes.length == 1) { String modifierString = methodName.substring(ON_EVENT_METHOD_NAME.length()); ThreadMode threadMode; //获取线程执行方式 if (modifierString.length() == 0) { threadMode = ThreadMode.PostThread; } else if (modifierString.equals(\"MainThread\")) { threadMode = ThreadMode.MainThread; } else if (modifierString.equals(\"BackgroundThread\")) { threadMode = ThreadMode.BackgroundThread; } else if (modifierString.equals(\"Async\")) { threadMode = ThreadMode.Async; } else { //省略部分代码 } Class<?> eventType = parameterTypes[0]; methodKeyBuilder.setLength(0); methodKeyBuilder.append(methodName).append('>').append(eventType.getName()); String methodKey = methodKeyBuilder.toString(); //检查是否已经添加过 if (eventTypesFound.add(methodKey)) { // Only add if not already found in a sub class //添加到列表中 subscriberMethods.add(new SubscriberMethod(method, threadMode, eventType)); } } } else if (!skipMethodVerificationForClasses.containsKey(clazz)) { Log.d(EventBus.TAG, \"Skipping method (not public, static or abstract): \" + clazz + \".\" + methodName); } } } //获取父类并且继续查找父类中的订阅方法 clazz = clazz.getSuperclass(); } //省略部分代码} 接下来看subscribe(subscriber, subscriberMethod, sticky, priority);这个方法是干啥的,总结来说就是把所有事件类型为eventType的订阅者放入List列表中,并放入Map集合中。然后再根据订阅事件的类构造一个订阅事件列表,用来判断某个类是否已经注册过事件,看一下代码: 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960private void subscribe(Object subscriber, SubscriberMethod subscriberMethod, boolean sticky, int priority) { //获取订阅事件中的时间类型 Class<?> eventType = subscriberMethod.eventType; //根据事件类型查找对应的所有订阅者(由类、方法、优先级组成) CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType); //创建一个新的订阅者 Subscription newSubscription = new Subscription(subscriber, subscriberMethod, priority); if (subscriptions == null) { subscriptions = new CopyOnWriteArrayList<Subscription>(); //将订阅者列表放入Map中,后面post的时候会会根据这个eventType来获取该订阅者列表,然后来触发事件 subscriptionsByEventType.put(eventType, subscriptions); } else { //不允许重复注册 if (subscriptions.contains(newSubscription)) { throw new EventBusException(\"Subscriber \" + subscriber.getClass() + \" already registered to event \" + eventType); } } // Starting with EventBus 2.2 we enforced methods to be public (might change with annotations again) // subscriberMethod.method.setAccessible(true); //根据优先级将新的订阅者插入到已有的订阅者列表中 int size = subscriptions.size(); for (int i = 0; i <= size; i++) { if (i == size || newSubscription.priority > subscriptions.get(i).priority) { subscriptions.add(i, newSubscription); break; } } //根据订阅类查找该订阅类中订阅事件类型,isRegister方法会用到此Map,判断是否已经注册过事件 List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber); if (subscribedEvents == null) { subscribedEvents = new ArrayList<Class<?>>(); typesBySubscriber.put(subscriber, subscribedEvents); } subscribedEvents.add(eventType); //粘性事件 if (sticky) { //指定粘性事件是否只触发订阅了当前事件类型的子类的订阅者 if (eventInheritance) { //stickyEvents中key为事件类名,value为事件类的实例 Set<Map.Entry<Class<?>, Object>> entries = stickyEvents.entrySet(); for (Map.Entry<Class<?>, Object> entry : entries) { Class<?> candidateEventType = entry.getKey(); //eventType类是否是candidateEventType类的父类 if (eventType.isAssignableFrom(candidateEventType)) { Object stickyEvent = entry.getValue(); //触发事件 checkPostStickyEventToSubscription(newSubscription, stickyEvent); } } } else { //获取所有黏性事件并触发 Object stickyEvent = stickyEvents.get(eventType); checkPostStickyEventToSubscription(newSubscription, stickyEvent); } }} post有两种,一种是在UI线程中,一种是子线程中执行,需要注意的是如果你post的事件类型为A,那么所有订阅过A的超类的事件也同样会被触发,时间原因,源码不分析,后续有时间接着写,记此笔记,方便自己查阅!","categories":[{"name":"DEFAULT","slug":"DEFAULT","permalink":"http://yoursite.com/categories/DEFAULT/"}],"tags":[{"name":"EventBus","slug":"EventBus","permalink":"http://yoursite.com/tags/EventBus/"}],"keywords":[{"name":"DEFAULT","slug":"DEFAULT","permalink":"http://yoursite.com/categories/DEFAULT/"}]},{"title":"知识点总结","slug":"2016-03-17-Points","date":"2016-09-23T16:56:16.000Z","updated":"2016-09-24T08:09:51.000Z","comments":true,"path":"2016/09/24/2016-03-17-Points/","link":"","permalink":"http://yoursite.com/2016/09/24/2016-03-17-Points/","excerpt":"HaspMap原理根据key的hashCode与Enty[]长度取模获取index来决定放入Enty中的位置,index重复则使用Entry.next在同一个index中放入多个值具体可看:http://www.cnblogs.com/xwdreamer/archive/2012/05/14/2499339.html 介绍 HashMap和HashTable的区别HaspMap继承AbstractMap ,HashTable继承DictionaryHashMap的方法不是同步的,HashTable的方法是同步的HashMap允许key和value为Null,HashTable不允许key和value为null详见:http://blog.csdn.net/shohokuf/article/details/3932967","text":"HaspMap原理根据key的hashCode与Enty[]长度取模获取index来决定放入Enty中的位置,index重复则使用Entry.next在同一个index中放入多个值具体可看:http://www.cnblogs.com/xwdreamer/archive/2012/05/14/2499339.html 介绍 HashMap和HashTable的区别HaspMap继承AbstractMap ,HashTable继承DictionaryHashMap的方法不是同步的,HashTable的方法是同步的HashMap允许key和value为Null,HashTable不允许key和value为null详见:http://blog.csdn.net/shohokuf/article/details/3932967 HashMap TreeMap LinkedHashMap 元素顺序HashMap不保证元素的插入顺序,TreeMap默认会按照key的升序排序TreeMap支持自定义排序,LinkedHashMap按照插入顺序排序 List子类ArrayList LinkedList VectorLinkedList在 add和remove 上更快,而在get上更慢.List接口下一共实现了三个类:ArrayList,Vector,LinkedList。LinkedList就不多说了,它一般主要用在保持数据的插入顺序的时候。ArrayList和Vector都是用数组实现的,主要有这么三个区别: 1、Vector是多线程安全的,而ArrayList不是,这个可以从源码中看出,Vector类中的方法很多有synchronized进行修饰,这样就导致了Vector在效率上无法与ArrayList相比; 2、两个都是采用的线性连续空间存储元素,但是当空间不足的时候,两个类的增加方式是不同的,很多网友说Vector增加原来空间的一倍,ArrayList增加原来空间的50%,其实也差不多是这个意思,不过还有一点点问题可以从源码中看出,一会儿从源码中分析。 3、Vector可以设置增长因子,而ArrayList不可以,最开始看这个的时候,我没理解什么是增量因子,不过通过对比一下两个源码理解了这个,先看看两个类的构造方法: HashSetHashSet内部实际上是一个HashMap,看代码 1234567transient HashMap<E, HashSet<E>> backingMap;看添加代码:@Overridepublic boolean add(E object) { return backingMap.put(object, this) == null;}//所以Set几何不允许重复的元素存在 Map List Set Map内部是一个Entry数组; List内部是一个Object数组; Set内部是一个Map集合,map的key集合就是Set的Value集合 关键字transient: 1)一旦变量被transient修饰,变量将不再是对象持久化的一部分,该变量内容在序列化后无法获得访问。 2)transient关键字只能修饰变量,而不能修饰方法和类。注意,本地变量是不能被transient关键字修饰的。变量如果是用户自定义类变量,则该类需要实现Serializable接口。 3)被transient关键字修饰的变量不再能被序列化,一个静态变量不管是否被transient修饰,均不能被序列化。 一个关于线程的问题假如有Thread1、Thread2、Thread3、Thread4四条线程分别统计C、D、E、F四个盘的大小,所有线程都统计完毕交给Thread5线程去做汇总,应当如何实现?可以使用并发包下的CountDownLatch实现http://www.cnblogs.com/dolphin0520/p/3920397.html wait和notify:执行wait、notify必须在同步块内部,这两个动作都是针对某一个对象的,比如对象A在线程1中执行了wait,那么线程1就会停留在此处,类似于阻塞,然后A在线程2中调用了notify,并且线程2执行结束之后线程1继续执行,如果对象A不在某处调用notify,线程1会一直停留在wait那一行处不继续执行。或者A调用wait的时候传一个等待时间,如果在这个等待时间内notify没有被调用,线程1会恢复执行。如果对象A在多个线程调用wait,那么必须执行notifyAll才能唤醒所有等待的线程,否则只会喊醒其中一个。 线程通信线程之间的通信机制:(共享内存、消息传递)深入理解Java内存模型http://www.infoq.com/cn/articles/java-memory-model-1 Volatile的官方定义Java语言规范第三版中对volatile的定义如下: java编程语言允许线程访问共享变量,为了确保共享变量能被准确和一致的更新,线程应该确保通过排他锁单独获得这个变量。Java语言提供了volatile,在某些情况下比锁更加方便。如果一个字段被声明成volatile,java线程内存模型确保所有线程看到这个变量的值是一致的 线程池为什么要使用线程池避免频繁地创建和销毁线程,达到线程对象的重用。另外,使用线程池还可以根据项目灵活地控制并发的数目。从Java5开始,Java提供了自己的线程池。每次只执行指定数量的线程,java.util.concurrent.ThreadPoolExecutorpublic ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue workQueue,RejectedExecutionHandler handler); 参数介绍: corePoolSize 核心线程数,指保留的线程池大小(不超过maximumPoolSize值时,线程池中最多有corePoolSize 个线程工作)。 maximumPoolSize 指的是线程池的最大大小(线程池中最大有corePoolSize 个线程可运行)。 keepAliveTime 指的是空闲线程结束的超时时间(当一个线程不工作时,过keepAliveTime 长时间将停止该线程)。 unit 是一个枚举,表示 keepAliveTime 的单位(有NANOSECONDS, MICROSECONDS, MILLISECONDS, SECONDS, MINUTES, HOURS, DAYS,7个可选值)。 workQueue 表示存放任务的队列(存放需要被线程池执行的线程队列)。 handler 拒绝策略(添加任务失败后如何处理该任务) 要点: 1、线程池刚创建时,里面没有一个线程。任务队列是作为参数传进来的。不过,就算队列里面有任务,线程池也不会马上执行它们。 2、当调用 execute() 方法添加一个任务时,线程池会做如下判断: a. 如果正在运行的线程数量小于 corePoolSize,那么马上创建线程运行这个任务; b. 如果正在运行的线程数量大于或等于 corePoolSize,那么将这个任务放入队列。 c. 如果这时候队列满了,而且正在运行的线程数量小于 maximumPoolSize,那么还是要创建线程运行这个任务; d. 如果队列满了,而且正在运行的线程数量大于或等于 maximumPoolSize,那么线程池会抛出异常,告诉调用者“我不能再接受任务了”。 3、当一个线程完成任务时,它会从队列中取下一个任务来执行。 4、当一个线程无事可做,超过一定的时间(keepAliveTime)时,线程池会判断,如果当前运行的线程数大于corePoolSize,那么这个线程就被停掉。所以线程池的所有任务完成后,它最终会收缩到 corePoolSize 的大小。 这个过程说明,并不是先加入任务就一定会先执行。假设队列大小为4,corePoolSize为2,maximumPoolSize为6,那么当加入15个任务时,执行的顺序类似这样:首先执行任务 1、2,然后任务3~6被放入队列。这时候队列满了,任务7、8、9、10 会被马上执行,而任务 11~15则会抛出异常。最终顺序是:1、2、7、8、9、10、3、4、5、6。当然这个过程是针对指定大小的ArrayBlockingQueue来说,如果是LinkedBlockingQueue,因为该队列无大小限制,所以不存在上述问题。 线程池创建Executors.newCachedThreadPool(); //创建一个缓冲池,缓冲池容量大小为Integer.MAX_VALUEExecutors.newSingleThreadExecutor(); //创建容量为1的缓冲池Executors.newFixedThreadPool(int); //创建固定容量大小的缓冲池","categories":[{"name":"DEFAULT","slug":"DEFAULT","permalink":"http://yoursite.com/categories/DEFAULT/"}],"tags":[],"keywords":[{"name":"DEFAULT","slug":"DEFAULT","permalink":"http://yoursite.com/categories/DEFAULT/"}]},{"title":"Hello World","slug":"hello-world","date":"2016-09-23T16:35:54.000Z","updated":"2016-09-24T08:12:24.000Z","comments":true,"path":"2016/09/24/hello-world/","link":"","permalink":"http://yoursite.com/2016/09/24/hello-world/","excerpt":"Welcome to Hexo! This is your very first post. Check documentation for more info. If you get any problems when using Hexo, you can find the answer in troubleshooting or you can ask me on GitHub. Quick StartCreate a new post1$ hexo new \"My New Post\"","text":"Welcome to Hexo! This is your very first post. Check documentation for more info. If you get any problems when using Hexo, you can find the answer in troubleshooting or you can ask me on GitHub. Quick StartCreate a new post1$ hexo new \"My New Post\" More info: Writing Run server1$ hexo server More info: Server Generate static files1$ hexo generate More info: Generating Deploy to remote sites1$ hexo deploy More info: Deployment","categories":[],"tags":[],"keywords":[]}]}