liunx 常用操作命令

1、设置让一个目录下的所有文件和下一级目录的权限都是777

sudo chmod -R 777 [your folder]

2、linux 查看文件权限

命令:ll 或者 ls -l

3、压缩为tar.gz文件,先写压缩后的文件名,后指定路径

tar -zcvf site.tar.gz ./*    打包目录下所有文件

4、解压tar.gz文件

tar -xzvf file.tar.gz //解压tar.gz

5、查看占用空间

du -sh : 查看当前目录总共占的容量。而不单独列出各子项占用的容量

du -lh –max-depth=1 : 查看当前目录下一级子文件和子目录占用的磁盘容量

6、删除文件

rm -rf 目录名:
把子目录及子目录中所有档案删除,并且不用一一确认

rm -f 文件名:
就是直接强行删除,不作任何提示的意思

7、查找文件并删除

以查找和删除mp3为扩展的文件为例:
find / -name "*.mp3" |xargs rm -rf
会删除所有以mp3为扩展的文件。操作的时候先:
find / -name "*.mp3"
会打印出匹配的文件,如果觉得正是想删除这些文件,再执行:
find / -name "*.mp3" |xargs rm -rf

谈谈Android App混合开发(转)

混合开发的App(Hybrid App)就是在一个App中内嵌一个轻量级的浏览器,一部分原生的功能改为Html 5来开发,这部分功能不仅能够在不升级App的情况下动态更新,而且可以在Android或iOS的App上同时运行,让用户的体验更好又可以节省开发的资源。

下面来谈谈Hybrid App开发中的技术问题。iOS方面的我不太了解,我就主要谈谈Android开发中的,其中可能会有很多说错的,请大家轻喷

Hybrid开发中关键问题是什么

想要在一个App中显示一个Html 5网页的功能,其实很简单,只要一个WebView就可以了。你可以点击链接来跳转网页。像这样的功能就能叫做Hybrid 开发了嘛?显然不是的。

我觉得一个Hybrid开发的App中必须要要有的功能就是Html 5页面和Native App怎么进行交互。比如,我点了一个Html 5页面上的一个按钮或链接,我能不能够跳转到Native App的某个页面;比如我点了Html 5页面上的分享按钮,我能不能调用Native App的分享功能;比如Html加载的时候能不能获取Native App的用户信息等等。

看下图,在网易云音乐中进入这个Html 5页面时,你点击 作者:空虚小编 你会进入他的主页,这个主页是Native页面,而你点击上面那个播放按钮时,云音乐会启动Native的播放界面播放音乐,你点击评论时,你会进入Native的评论页

Html 5和Native的交互

WebView 本来就支持js和Java相互调用,你只需要开启 WebView 的JavaScript脚本执行,然后通过代码 mWebView.addJavascriptInterface(new JsBridge(), "bxbxbai"); 向Html 5页面时注入一个Java对象,然后就可以在Html 5页面中调用Native的功能了

微信怎么做的

微信应该是Hybrid 开发做的最好的App之一,它是怎么做交互的呢?

答案就是 微信JS-SDK ,去微信开发者文档中可以看到,微信JS-SDK封装了各种微信的功能,比如分享到朋友圈,图像接口,音频接口,支付接口地理位置接口等等。开发者只需要调用微信JS-SDK中的函数,然后统一由JS-SDK来调用微信中的功能,这样好处就是我写了一个Html 5的应用或网页,在Android和iOS的微信中都可以正常运行了

下面会详细讲到

网易云音乐怎么做的

那么网易云音乐是怎么做的呢?我用黑科技知道了上图云音乐的界面Activity是 CommonSubjectActivity (名字好奇怪,如果要我从代码里找,我肯定找不到,因为还有一个类叫做 EmbedBrowserActivity ),我就在反编译后的云音乐代码中找相应的功能实现代码,实在没找到。不过我拿到了那个Html 5页面的地址: http://music.163.com/m/topic/194001

用Chrome打开后发现和App中显示的不一样,然后我用Charles截了进入那个Html 5的请求,发现云音乐加载的地址是 http://music.163.com/m/topic/194001?type=android ,就是加了手机系统类型

然后在我自己的App中加载这个Html 5页面就可以看到下图, @小比比说 这样的文字是可以点击跳转到个人,点击播放按钮是可以播放音乐的

从Html源代码中可以看到如下信息:

也就是说,当我点击一个用户名的时候就请求跳转到 orpheus://user/30868859 ,因为WebView可以拦截跳转的url,所以App在拦截每一个url,如果host是 orpheus 的话就启动用户首页

反编译代码后,在云音乐的代码中找到了 this.mWebView.setWebViewClient(new cf(this)); 这么一句代码,进入 cf 类,发现下面代码:

public boolean shouldOverrideUrlLoading(WebView webView, String url) {
	if (url.startsWith("orpheus://")) {
		RedirectActivity.a(this.activity, url);
		return true;
	}
	if ((url.toLowerCase().startsWith("http://")) || (url.toLowerCase().startsWith("https://"))) {
		return false;
	}
	try {
		this.activity.startActivity(new Intent("android.intent.action.VIEW", Uri.parse(url)));
		return true;
	} catch (ActivityNotFoundException localActivityNotFoundException) {
		localActivityNotFoundException.printStackTrace();
	}
	return true;
}

果然如此,再进入 RedirectActivity ,这是一个没有任何界面的Activity,专门用于处理页面跳转信息,它会调用一个方法 NeteaseMusicUtils.redirect(this, getIntent().getData().toString(), false) 来处理url, redirect 方法的名字是我自己写的,部分代码如下:

可以看到 orpheus://user/30868859 中的用户id被传入了 ProfileAcvitiy ,因此启动了用户首页显示了用户信息

然后我自己写了代码拦截Html 5的跳转,打印出的Log如下:

可以看到Html 5页面可以跳转到各种页面,比如用户首页、播放音乐、MV界面、评论页、电台节目等等

总结

一般来讲,也是我目前知道的两种主流的方式就是

  1. js调用Native中的代码
  2. Schema:WebView拦截页面跳转

第2种方式实现起来很简单,但是一个致命的问题就是这种交互方式是单向的,Html 5无法实现回调。像云音乐App中这种点击跳转到具体页面的功能,Schema的方式确实可以简单实现,而且也非常适合。如果需求变得复杂,假如Html 5需要获取Native App中的用户信息,那么最好使用js调用的方式。

js和Native进行交互

上面讲到WebViewbe本身就是支持js调用Native代码的,不过WebView的这个功能在Android 4.2(API 17)一下存在高危的漏洞。这个漏洞的原理就是Android系统通过 WebView.addJavascriptInterface(Object o, String interface) 方法注册可供js调用的Java对象,但是系统并没有对注册的Java对象方法调用做限制。导致攻击者可以利用反射调用未注册的其他任何Java对象,攻击者可以根据客户端的能力做任何事情。 这篇文章 详细的介绍了这个漏洞

出于安全考虑,Android 4.2以后的系统规定允许被js调用的Java方法必须以 @JavascriptInterface 进行注解

Cordova的解决方案

Cordova是一个广泛使用的Hybrid开发框架,它提供了一套js和Native交互规范

在Cordova的 SystemWebViewEngine 类中可以看到

private static void exposeJsInterface(WebView webView, CordovaBridge bridge) {
	if ((Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1)) {
		Log.i(TAG, "Disabled addJavascriptInterface() bridge since Android version is old.");
		// Bug being that Java Strings do not get converted to JS strings automatically.
		// This isn't hard to work-around on the JS side, but it's easier to just
		// use the prompt bridge instead.
		return;
	}
	webView.addJavascriptInterface(new SystemExposedJsApi(bridge), "_cordovaNative");
}

因此当Android系统高于4.2时,Cordova还是使用 addJavascriptInterface 这种方式,因为这个方法在高版本上安全而且简单,低于4.2的时候,用什么方法呢?

答案是 WebChromeClient.onJsPrompt 方法

WebView可以设置一个 WebChromeClient 对象,它可以处理js的3个方法

  • onJsAlert
  • onJsConfirm
  • onJsPrompt

这3个方法分别对应js的 alertconfirmprompt 方法,因为只有 prompt 接收返回值,所以js调用一个Native方法后可以等待Native返回一个参数。下面是 cordova.js 中的一段代码:

/**
* Implements the API of ExposedJsApi.java, but uses prompt() to communicate.
* This is used pre-JellyBean, where addJavascriptInterface() is disabled.
*/
module.exports = {
	exec: function(bridgeSecret, service, action, callbackId, argsJson) {
		return prompt(argsJson, 'gap:'+JSON.stringify([bridgeSecret, service, action, callbackId]));
	},
	setNativeToJsBridgeMode: function(bridgeSecret, value) {
		prompt(value, 'gap_bridge_mode:' + bridgeSecret);
	},
	retrieveJsMessages: function(bridgeSecret, fromOnlineEvent) {
		return prompt(+fromOnlineEvent, 'gap_poll:' + bridgeSecret);
	}
};

然后只要在 onJsPrompt 方法中使用 CordovaBridge 来处理js的prompt调用

/**
 * Tell the client to display a prompt dialog to the user. If the client returns true, WebView will assume that the client will handle the prompt dialog and call the appropriate JsPromptResult method.
 * <p/>
 * Since we are hacking prompts for our own purposes, we should not be using them for this purpose, perhaps we should hack console.log to do this instead!
 */
@Override
public boolean onJsPrompt(WebView view, String origin, String message, String defaultValue, final JsPromptResult result) {
	// Unlike the @JavascriptInterface bridge, this method is always called on the UI thread.
	String handledRet = parentEngine.bridge.promptOnJsPrompt(origin, message, defaultValue);
	if (handledRet != null) {
		result.confirm(handledRet);
	} else {
		dialogsHelper.showPrompt(message, defaultValue, new CordovaDialogsHelper.Result() {
			@Override
			public void gotResult(boolean success, String value) {
				if (success) {
					result.confirm(value);
				} else {
					result.cancel();
				}
			}
		});
	}
	return true;
}

一种开源的解决方案

Cordova是Apache的一个开源解决方案,不过它需要xml配置 CordovaPlugin 信息,使用会比较麻烦,而且这个框架很重,具体请自行搜索Cordova使用教程

下面这个开源项目是我个人觉得比较合理的解决方案,也比较轻量级,下图就是一个Demo

https://github.com/pedant/safe-java-js-webview-bridge

这个项目的原理就是使用 WebChromeClient.onJsPrompt 方法来进行交互,本质上都是js调用 prompt 函数,传输一些参数, onJsPrompt 方法拦截到prompt动作,然后解析数据,最后调用相应的Native方法

HostJsScope类中定义了所有可以被js调用的方法,这些方法都必须是静态方法,并且所有的方法第一个参数必须是WebView

/**
* HostJsScope中需要被JS调用的函数,必须定义成public static,且必须包含WebView这个参数
*/
public class HostJsScope {
	/**
	* 短暂气泡提醒
	* @param webView 浏览器
	* @param message 提示信息
	* */
	public static void toast(WebView webView, String message) {
		Toast.makeText(webView.getContext(), message, Toast.LENGTH_SHORT).show();
	}
	/**
	* 系统弹出提示框
	* @param webView 浏览器
	* @param message 提示信息
	* */
	public static void alert(WebView webView, String message) {
		// 构建一个Builder来显示网页中的alert对话框
		AlertDialog.Builder builder = new AlertDialog.Builder(webView.getContext());
		builder.setPositiveButton(android.R.string.ok, new AlertDialog.OnClickListener() {
			@Override
			public void onClick(DialogInterface dialog, int which) {
				dialog.dismiss();
			}
		});
		builder.setTitle("Hello world")
			.setMessage(message)
			.setCancelable(false)
			.create()
			.show();
	}
	// 其他代码
}

上面代码列举了最基本的点击Html 5按钮弹出对话框的功能

这个库中一个最关键的叫做 JsCallJava ,这个实现的就是js来调用Java方法的功能,这个类只用于 InjectedWebChromeClient

public class InjectedChromeClient extends WebChromeClient {
	private JsCallJava mJsCallJava;
	private boolean mIsInjectedJS;
	public InjectedChromeClient(String injectedName, Class injectedCls) {
		this(new JsCallJava(injectedName, injectedCls));
	}
	public InjectedChromeClient(JsCallJava jsCallJava) {
		mJsCallJava = jsCallJava;
	}
	// 处理Alert事件
	@Override
	public boolean onJsAlert(WebView view, String url, String message, final JsResult result) {
		result.confirm();
		return true;
	}
	@Override
	public void onProgressChanged(WebView view, int newProgress) {
		//为什么要在这里注入JS
		//1 OnPageStarted中注入有可能全局注入不成功,导致页面脚本上所有接口任何时候都不可用
		//2 OnPageFinished中注入,虽然最后都会全局注入成功,但是完成时间有可能太晚,当页面在初始化调用接口函数时会等待时间过长
		//3 在进度变化时注入,刚好可以在上面两个问题中得到一个折中处理
		//为什么是进度大于25%才进行注入,因为从测试看来只有进度大于这个数字页面才真正得到框架刷新加载,保证100%注入成功
		if (newProgress <= 25) {
			mIsInjectedJS = false;
		} else if (!mIsInjectedJS) {
			view.loadUrl(mJsCallJava.getPreloadInterfaceJS());
			mIsInjectedJS = true;
			StopWatch.log(" inject js interface completely on progress " + newProgress);
		}
		super.onProgressChanged(view, newProgress);
	}
	@Override
	public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
		result.confirm(mJsCallJava.call(view, message));
		StopWatch.log("onJsPrompt: " + view.toString() +", " + url +", " + message +", " + defaultValue + ", " + result) ;
		return true;
	}
}   

这个 InjectedWebChromeClient 是设给WebView的,这里一个非常重要的细节需要注意一下,在 onProgressChange 方法中,向WebView注入了一段js代码,这段js代码如下:

javascript: (function(b) {
	console.log("HostApp initialization begin");
	var a = {
		queue: [],
		callback: function() {
			var d = Array.prototype.slice.call(arguments, 0);
			var c = d.shift();
			var e = d.shift();
			this.queue[c].apply(this, d);
			if (!e) {
				delete this.queue[c]
			}
		}
	};
	a.alert = a.alert = a.alert = a.delayJsCallBack = a.getIMSI = a.getOsSdk = a.goBack = a.overloadMethod = a.overloadMethod 
		= a.passJson2Java = a.passLongType = a.retBackPassJson = a.retJavaObject = a.testLossTime = a.toast = a.toast = function() {
		var f = Array.prototype.slice.call(arguments, 0);
		if (f.length < 1) {
			throw "HostApp call error, message:miss method name"
		}
		var e = [];
		for (var h = 1; h < f.length; h++) {
			var c = f[h];
			var j = typeof c;
			e[e.length] = j;
			if (j == "function") {
				var d = a.queue.length;
				a.queue[d] = c;
				f[h] = d
			}
		}
		var g = JSON.parse(prompt(JSON.stringify({
			method: f.shift(),
			types: e,
			args: f
		})));
		if (g.code != 200) {
			throw "HostApp call error, code:" + g.code + ", message:" + g.result
		}
		return g.result
	};
	//有时候,我们希望在该方法执行前插入一些其他的行为用来检查当前状态或是监测
	//代码行为,这就要用到拦截(Interception)或者叫注入(Injection)技术了
	/**
	 * Object.getOwnPropertyName 返回一个数组,内容是指定对象的所有属性
	 *
	 * 其后遍历这个数组,分别做以下处理:
	 * 1. 备份原始属性;
	 * 2. 检查属性是否为 function(即方法);
	 * 3. 若是重新定义该方法,做你需要做的事情,之后 apply 原来的方法体。
	 */
	Object.getOwnPropertyNames(a).forEach(function(d) {
		var c = a[d];
		if (typeof c === "function" && d !== "callback") {
			a[d] = function() {
				return c.apply(a, [d].concat(Array.prototype.slice.call(arguments, 0)))
			}
		}
	});
	b.HostApp = a;
	console.log("HostApp initialization end")
})(window);

那么这段js代码是如何生成的呢?答案就在 JsCallJava 类的构造函数方法中,这个构造方法做的事情就是解析 HostJsScope 类中的方法,把每一个方法的签名都保持到 private Map<String, Method> mMethodsMap 中,再看上面那段js代码中

a.alert = a.alert = a.alert = a.delayJsCallBack = a.getIMSI = a.getOsSdk = a.goBack = a.overloadMethod = a.overloadMethod= a.passJson2Java = a.passLongType = a.retBackPassJson = a.retJavaObject = a.testLossTime = a.toast = a.toast = function()

这些都是 HostJsScope 类中定义的方法名

那么这个库的整个执行流程是这样的:

  1. JsCallJava 类解析了 HostJsScope 类中所有的静态方法,将它们放到一个Map中,并且生成一段js代码
  2. 向WebView设置 InjectedChromeClient ,在 onProgressChanged 方法中将那段js代码注入到Html5页面中,这个过程通俗点讲就是, Native告诉Html 5页面,我开放了什么功能给你,你就来调用我
  3. 这样js就可以调用Native提供的这些方法,那段js代码还会将js执行的方法转换成一段json字符串,通过js的prompt方法传到 onJsPrompt 方法中, JsCallJava 调用 call(WebView view, String msg) 解析json字符串,包括要执行的 方法名字参数类型方法参数 ,其中还会验证json中的方法参数类型和 HostJsScope 中同名方法参数类型是否一致等等。
  4. 最后,如果方法正确执行, call 方法就返回一个json字符串code=200,否则就传code=500,这个信息会通过 prompt 方法的返回值传给js,这样Html 5 代码就能知道有没有正确执行了

以上就是这个开源库的整个原理,我个人觉得非常适合用于Hybrid开发,这个解决方案中js可以收到Native的返回值,而且没有使用 addJavascriptInterface 方法,在低版本手机上也不会有安全问题,这个方法比Cordova的实现和配置简单

那么当我点击Html 5页面上的一个按钮,比如弹出对话框,这个过程的整体流程是怎么样的呢

微信的解决方案?

什么?你问我微信是怎么解决的?我也反编译了微信的代码,想研究一下他们是解决的,其实我非常好奇微信的这种js 调用Native,并且又返回的调用方法

首先,我去微信的js sdk官网看了一下js sdk提供的功能,提供了各种强大的功能,各位可以自己去看一下。那么问题来了,微信是怎么做到js 调用Native并且能够成功返回的呢?

带着疑问我反编译了微信Android客户端,在assers/jsapi中看到了 wxjs.js 文件,我想这个就是微信js sdk的源码了吧。。。

我首先说一下,我不太懂js的代码, 我只能连蒙带猜的看微信的js代码, 如果有js大神对这方面也感兴趣,希望可以一起(jian)探(fei)讨(zao)

wxjs.js 中看到了一下代码,我猜微信就是用这个 __WeixinJSBridge 当时js和Native进行通信的数据结构吧?

var __WeixinJSBridge = {
	// public
	invoke:_call,
	call:_call,
	on:_onfor3rd,
	env:_env,
	log:_log,
	// private
	// _test_start:_test_start,
	_fetchQueue: _fetchQueue,
	_handleMessageFromWeixin: _handleMessageFromWeixin,
	_hasInit: false,
	_continueSetResult: _continueSetResult
};

然后我又看到了下面的代码,我想应该是提供分享内容到朋友圈功能的吧

// share timeline
_on('menu:share:timeline',function(argv){
  _log('share timeline');
  var data;
  if (typeof argv.title === 'string') {
	data = argv;
	_call('shareTimeline',data);
  }else{
	data = {
		// "img_url": "",
		// "img_width": "",
		// "img_height": "",
		"link": document.documentURI || _session_data.init_url,
		"desc": document.documentURI || _session_data.init_url,
		"title": document.title
	};
	var shareFunc = function(_img){		  
	  if (_img) {
		  data['img_url'] = _img.src;
		  data['img_width'] = _img.width;
		  data['img_height'] = _img.height;						
	  }
	  _call('shareTimeline',data);
	};
	getSharePreviewImage(shareFunc);
  }
});

请注意最后这句: _call('shareTimeline',data); ,在看看 __WeixinJSBridge 中的 call 属性,接着我找到了 _call 方法。

function _call(func,params,callback) {
	var curFuncIdentifier = __WeixinJSBridge.call;
	if (curFuncIdentifier !== _callIdentifier) {
		return;
	}
	if (!func || typeof func !== 'string') {
		return;
	};
	if (typeof params !== 'object') {
		params = {};
	};
	var callbackID = (_callback_count++).toString();
	if (typeof callback === 'function') {
	  _callback_map[callbackID] = callback;
	};
	var msgObj = {'func':func,'params':params};
	msgObj[_MESSAGE_TYPE] = 'call';		
	msgObj[_CALLBACK_ID] = callbackID;
	_sendMessage(JSON.stringify(msgObj));
}

大致意思应该就是:就是将这个东西 _call('shareTimeline',data); 转换成一个json字符串吧,从这里看到微信的做法和上面那个开源库非常类似,简单并且安全。 _call 方法最后调用 _sendMessage 方法发送消息

//将消息添加到发送队列,iframe的准备队列为weixin://dispatch_message/
function _sendMessage(message) {
	_sendMessageQueue.push(message);
	_readyMessageIframe.src = _CUSTOM_PROTOCOL_SCHEME + '://' + _QUEUE_HAS_MESSAGE;
	// var ifm = _WXJS('iframe#__WeixinJSBridgeIframe')[0];
	// if (!ifm) {
	//   ifm = _createQueueReadyIframe(document);
	// }
	// ifm.src = _CUSTOM_PROTOCOL_SCHEME + '://' + _QUEUE_HAS_MESSAGE;
};

从上面代码可以看到微信的js sdk也是将js的方法调用换成一个类似 weixin://dispatch_message/ 这样的url,上面说的json封装的数据。那么我猜测微信的做法是类似网易云音乐的拦截url吗?如果真的是这样的话,就非常不安全了,随便一个Html 5页面可以伪造一个类似: weixin://dispatch_message/ 这样的url来调用微信的功能了,不过好在微信对每个js调用都必须带上appid。

在反编译后的微信代码,我看到了下面代码:

我想这写就是微信想Html 5开放的接口吧?不过对比了一下微信js sdk的官网,我看到好多App提供的功能在js sdk中并没有找到,这样也没有太大关系,以为微信只要升级js sdk就可以使用其他功能了,因为Native已经开放了嘛~

从上面 __WeixinJSBridge 可以看到有一个熟悉 _handleMessageFromWeixin ,这个就是js来处理Native的回调接口,我用这个字符串在微信代码中搜索,结果如下:

因此,我大致猜测,微信中的js调Native功能是用拦截url的方式,而Native回调的话是使用 evaluateJavascript 方法

我也在js sdk中找到了相应的函数:

function _handleMessageFromWeixin(message) {
    var curFuncIdentifier = __WeixinJSBridge._handleMessageFromWeixin;
    if (curFuncIdentifier !== _handleMessageIdentifier) {
        return '{}';
    }

    var ret;
    var msgWrap
    if (_isUseMd5 === 'yes') {
      var realMessage = message[_JSON_MESSAGE];
      var shaStr = message[_SHA_KEY];
      var arr = new Array;
      arr[0] = JSON.stringify(realMessage);
      arr[1] = _xxyy;
      var str = arr.join("");
      var msgSha = '';
        var shaObj = CryptoJS.SHA1(str);
        msgSha = shaObj.toString();
        if (msgSha !== shaStr) {
            _log('_handleMessageFromWeixin , shaStr : ' + shaStr + ' , str : ' + str + ' , msgSha : ' + msgSha);
            return '{}';

        }
        msgWrap = realMessage;
    }
    //省略很多代码

微信的做法应该说非常基础,使用了原生的功能,但是安全,由于微信客户端对每一个js调用都有验证(appid),因此这也增加了一定的安全性

以上说的都是建立在我的分析正确的情况下。

一些个人的想法

现在各种新的技术也在想办法解决Native开发的效率问题,想用技术来解决一套代码运行在Android和iOS客户端,我相信随着技术的发展这些问题都会解决。我也好期待Facebook即将推出的 React Native Android

Hybrid开发适用于哪些功能

本文讲的Hybrid开发就是Native客户端中嵌入了Html App的功能,这方面微信应该是做的最好的,由于Html 5的效率以及耗电问题,我个人觉得用户是不能满足Web App的体验的,Hybrid App也只适用于某些场景。一些基础的功能,比如调用手机的摄像头,获取地理位置,登录注册功能等等,做成Native的功能,让Html 5来调用更好,这样的体验也更好。

如果你把一个登录和注册功能也做成Html 5,在弱网络环境下,这个体验应该会非常的差,或许你等半天还没加载出页面。你可能会说,我可以预先加载Html 5的代码,打开App时直接加载,那么我说你在给自己找麻烦,如果要这样的话,Native开发或许更快一点。

那么什么情况适合Html 5开发呢?像一些活动页面,比如秒杀、团购等适合做Html 5,因为这些页面可能涉及的非常炫而且复杂,Html 5开发或许会简单点,关键是这些页面时效性短,更新更快,因为一个活动说不定就一周时间,下周换活动,如果这样的话,你还做Native是肯定不行的

总结

有那么一句 古老的箴言

如果你手里有一把锤子,所有东西看上去都想钉子

千万不要以为Hybrid开发能够夸平台运行,就使用Hybrid开发任何功能。其实Facebook早期也是这么想的,后来就是因为WebView渲染效率底下,把整个应用改为Native开发,请看 这里

引用Facebook的一段话:

Today, we’re releasing a new version of Facebook for Android that’s been rebuilt in native code to improve speed and performance. To support the unique complexity of Facebook stories across devices, we’re moving from a hybrid native/webview to pure native code, allowing us to optimize the Facebook experience for faster loading, new user interfaces, disk cache, and so on.

本文主要还是从技术上谈谈Hybrid开发中js和Native交互的技术实现原理。抛砖引玉,写的估计也有很多错的,希望技术大牛指出。

最后,我觉得那个开源的库是一个非常不错的解决方案,解决办法巧妙、简单而且安全。当时我debug了半天弄明白其中的原理后,我一拍大腿,这办法真好啊!!网易云音乐的解决办法适用于它的场景,不需要回调,Native只需要处理相应的信息,然后来实现页面跳转、播放音乐、播放MV等功能,这个方法也简单好用。

css精确控制表格列宽

table-layout: 默认值为 auto,即自动表格布局算法,列宽会根据其内容进行调整,即使我们显式指定了 width,也只是起到类似提示的作用。

将其设为 fixed,要明显可控一些。设置的宽会直接起作用,溢出行为 text-overflow 也表现正常。

使用时需要为表格元素指定一个宽度(哪怕是100%)。

table {
    table-layout: fixed;
    width:100%;
}

要让 text-overflow:ellipsis 发挥效用。还需要为那一列指定宽度。

demo地址:
http://dabblet.com/gist/7979af102a991cecfcdf

iOS下webview中JS调用原生OC

因项目需要,需要在页面中传递值给ios客户端。经过联调最后使用 JavaScriptCore 这种方法完成。

过程中开始使用 WebViewJavascriptBridge 这种方法,最后出现os只能调用本地的页面时才生效,在群友推荐下,改用JavaScriptCore这种方法。经过各种调试才算调通。

JavaScriptCore是webkit的一个重要组成部分,主要是对JS进行解析和提供执行环境。iOS7后苹果在iPhone平台推出,极大的方便了我们对js的操作。我们可以脱离webview直接运行我们的js,推荐使用

先定义通讯类js方法,等加载好后。再进行业务js的执行。
定义:

function testResponse(obj){
    native.testResponse(obj);
}

调用:

    // 调用oc方法传递信息
    testResponse(iosSdkAuthResult);

参考文章:
iOS下JS与原生OC互相调用(总结)
JavaScriptCore 使用

gulp-rev-append 小记

使用gulp-rev-append给页面的引用添加版本号,清除页面引用缓存。简单配置如下:

var gulp = require('gulp');
var rev = require('gulp-rev-append');

gulp.task('rev', function() {
 gulp.src('./index.html')
 .pipe(rev())
 .pipe(gulp.dest('./dest'));
});

针对相对路径使用,对于简单的页面来说没问题,但使用后端模板文件后路径变化时就不太适用了。

这是也只用使用 gulp-rev-collector 了,复杂度提高了。

常用es6特性学习

ES6给我们提供了许多的新语法和代码特性来提高javascript的体验。

定义函数

我们先来看一个基本的新特性,在javascript中,定义函数需要关键字function,但是在es6中,还有更先进的写法,我们来看:

es6写法:

var human = {
    breathe(name) {   //不需要function也能定义breathe函数。
        console.log(name + ' is breathing...');
    }
};
human.breathe('jarson');   //输出 ‘jarson is breathing...’

转成以前的js代码:

var human = {
    breathe: function(name) {
        console.log(name + 'is breathing...');
    }
};
human.breathe('jarson');

创建类

我们知道,javascript不像java是面向对象编程的语言,而只可以说是基于对象编程的语言。所以在js中,我们通常都是用function和prototype来模拟类这个概念。

但是现在有了es6,我们可以像java那样‘明目张胆’的创建一个类了:

class Human {
    // 构造方法,实例化的时候将会被调用,如果不指定,那么会有一个不带参数的默认构造函数。
    constructor(name) {
    	this.name = name;
    }
    // 是原型对象上的属性
    breathe() {
        console.log(this.name + " is breathing");
    }
}
var man = new Human("jarson");
man.breathe();    // jarson is breathing

man.hasOwnProperty("name"); // true
man.__proto__.hasOwnProperty('breathe'); // true

hasOwnProperty() 方法用来判断某个对象是否含有指定的自身属性。和 in 运算符不同,该方法会忽略掉那些从原型链上继承到的属性。
__proto__属性的值就是它的原型。

上面代码转为js格式:

function Human(name) {
    this.name = name;
    this.breathe = function() {
        console.log(this.name + ' is breathing');
    }
}
var man = new Human('jarson');
man.breathe();    //jarson is breathing

所以我们看到,我们可以像java那样语义化的去创建一个类。另外,js中的继承父类,需要用prototype来实现。那么在es6中,又有什么新的方法来实现类的继承呢?

继续看:

假如我们要创建一个Man类继承上面的Human类,es6代码:

class Man extends Human {
    constructor(name, sex) {
        // 子类必须要在constructor中指定 super 方法,否则在新建实例的时候会报错
        // 如果没有置顶consructor,默认带 super 方法的constructor将会被添加
        super(name); // 即调用父类的构造函数
        this.sex = sex;
    }
    info(){
        console.log(this.name + ' is ' + this.sex);
    }
}
var xx = new Man('jarson', 'boy');
xx.breathe();   // jarson is breathing
xx.info();   // jarson is boy

console.log(xx instanceof Human); // true
console.log(xx instanceof Man); // true

代码很简单,不作赘述,可以使用文章里提到的在线工具去试试效果就能明白了。需要注意的是: super() 是父类的构造函数。

instanceof 运算符用来测试一个对象在其原型链中是否存在一个构造函数的 prototype 属性。

let与const

在我看来,在es6新特性中,在定义变量的时候统统使用 let 来代替 var 就好了, const 则很直观,用来定义常量,即无法被更改值的变量。

for (let i=0;i<2;i++) {
    console.log(i);  //输出: 0,1
}

箭头函数

ES6中新增的箭头操作符 => 简化了函数的书写。操作符左边为输入的参数,而右边则是进行的操作以及返回的值,这样的写法可以为我们减少大量的代码,看下面的实例:

let arr = [6, 8, 10, 20, 15, 9];
arr.forEach((item, i) => console.log(item, i));
let newArr = arr.filter((item) => (item<10));
console.log(newArr); //[6, 8, 9];

上面的 (item, i) 就是参数,后面的 console.log(item, i) 就是回到函数要执行的操作逻辑。

上面代码转为js格式:

var arr = [6, 8, 10, 20, 15, 9];
arr.forEach(function(item, i) {
	return console.log(item, i);
});
var newArr = arr.filter(function(item) {
    return (item < 10);
});
console.log(newArr);

字符串模版

ES6中允许使用反引号 ` 来创建字符串,此种方法创建的字符串里面可以包含由美元符号加花括号包裹的变量${vraible}。看一下实例就会明白了:

//产生一个随机数
let num = Math.random();
//将这个数字输出到console
console.log(`your num is ${num}`);

 

摘录自:
http://www.open-open.com/lib/view/open1462951574598.html
https://segmentfault.com/a/1190000002904199

在JavaScript中创建命名空间的几种写法(转)

在JavaScript中全局变量经常会引起命名冲突,甚至有时侯重写变量也不是按照你想像中的顺序来的,可以看看下面的例子:
var sayHello = function() {
  return 'Hello var';
};

function sayHello(name) {
  return 'Hello function';
};

sayHello();
最终的输出为
> "Hello var"

为什么会这样,根据 StackOverFlow 的解释,实际上JavaScript的是按如下顺序解析的。

function sayHello(name) {
  return 'Hello function';
};

var sayHello = function() {
  return 'Hello var';
};

sayHello();
不带var的function声明被提前解析了,因此现代的JS写法建议你始终使用前置var声明所有变量;
避免全局变量名冲突的最好办法还是创建命名空间,下面是在JS中合建命名空间的几种常用方法。
通过函数(function)创建
这是一种比较常见的写法,通过声明一个function实现,函数里设置初始变量,公共方法写入prototype,如:
var NameSpace = window.NameSpace || {};
/*
Function
*/
NameSpace.Hello = function() {
  this.name = 'world';
};
NameSpace.Hello.prototype.sayHello = function(_name) {
  return 'Hello ' + (_name || this.name);
};
var hello = new NameSpace.Hello();
hello.sayHello();
这种写法比较冗长,不利于压缩代码(jQuery使用fn代替prototype),而且调用前需要先实例化(new)。使用Object写成JSON形式可以写得紧凑些:
通过JSON对象创建Object
/*
Object
*/
var NameSpace = window.NameSpace || {};
NameSpace.Hello = {
    name: 'world'
  , sayHello: function(_name) {
    return 'Hello ' + (_name || this.name);
  }
};
调用
NameSpace.Hello.sayHello('JS');
> Hello JS;
这种写法比较紧凑,缺点是所有变量都必须声明为公有(public)的,导致所有对这些变量的引用都需要加this指示作用域,写法也略有冗余。

通过闭包(Closure)和Object实现

在闭包中声明好所有变量和方法,并通过一个JSON Object返回公有接口:
var NameSpace = window.NameSpace || {};
NameSpace.Hello = (function() {
  //待返回的公有对象
  var self = {};
  //私有变量或方法
  var name = 'world';
  //公有方法或变量
  self.sayHello = function(_name) {
    return 'Hello ' + (_name || name);
  };
  //返回的公有对象
  return self;
}());
Object和闭包的改进型写法
上个例子在内部对公有方法的调用也需要添加self,如:self.sayHello(); 这里可以最后再返回所有公有接口(方法/变量)的JSON对象。
var NameSpace = window.NameSpace || {};
NameSpace.Hello = (function() {
  var name = 'world';
  var sayHello = function(_name) {
    return 'Hello ' + (_name || name);
  };
  return {
    sayHello: sayHello
  };
}());
Function的简洁写法
这是一种比较简洁的实现,结构紧凑,通过function实例,且调用时无需实例化(new),方案来自stackoverflow
var NameSpace = window.NameSpace || {};
NameSpace.Hello = new function() {
  var self = this;
  var name = 'world';
  self.sayHello = function(_name) {
    return 'Hello ' + (_name || name);
  };
};

调用

NameSpace.Hello.sayHello();
我喜欢的写法
var yourNamespace = {

    foo: function() {
    },

    bar: function() {
    }
};

...

yourNamespace.foo();

转自:http://ourjs.com/detail/538d8d024929582e6200000c

webpack基本配置

var webpack = require('webpack')
module.exports = {
    entry: {
        index: './src/app.js'
    },
    output: {
        path: './dist/',
        filename: '[name].js',
        publicPath: '/dist'
    },
    scripts: {
      "build": "webpack",
      "dev": "webpack-dev-server --devtool eval --progress --colors --content-base build"
    },
    module: {
        loaders: [{
            test: /\.js$/,
            exclude: /node_modules/,
            loader: 'babel',
            query: {
                presets: ['es2015', 'stage-0', 'react']
            }
        }]
    }
}

dev里各属性值的意思是:

  1. webpack-dev-server: 在 localhost:8080 建立一个 Web 服务器
  2. --devtool eval:为你的代码创建源地址。当有任何报错的时候可以让你更加精确地定位到文件和行号
  3. --progress: 显示合并代码进度
  4. --colors: 在命令行中显示颜色
  5. --content-base build:指向设置的输出目录

注:
publicPath: ‘/dist’
// webpack-dev-server 启动目录是 `/`, `/dist` 目录是打包的目标目录相对于启动目录的路径

webpack-dev-server 还提供了自动刷新功能,有两种模式。

Iframe 模式

修改访问的路径: http://localhost:8080/index.html -> http://localhost:8080/webpack-dev-server/index.html 。这个时候每次修改代码,打包完成过后都会自动刷新页面。

不需要额外配置,只用修改路径
应用被嵌入了一个 iframe 内部,页面顶部可以展示打包进度信息
因为 iframe 的关系,如果应用有多个页面,无法看到当前应用的 url 信息

inline 模式

启动 webpack-dev-server 的时候添加 –inline 参数

需要添加 –inline 配置参数
没有顶部信息提示条,提示信息在控制台中展现

参考:
React+Webpack快速上手指南
精益react-webpack

querySelector和querySelectorAll的功能及区别

HTML5向Web API新引入了document.querySelector以及document.querySelectorAll两个方法用来更方便地从DOM选取元素,功能类似于jQuery的选择器。这使得在编写原生JavaScript代码时方便了许多。

用法:

两个方法使用差不多的语法,都是接收一个字符串参数,这个参数需要是合法的CSS选择语法。

element = document.querySelector('selectors');
elementList = document.querySelectorAll('selectors');

其中参数selectors 可以包含多个CSS选择器,用逗号隔开。

element = document.querySelector('selector1,selector2,...');
elementList = document.querySelectorAll('selector1,selector2,...');

使用这两个方法无法查找带伪类状态的元素,比如querySelector(‘:hover’)不会得到预期结果。

querySelector

该方法返回满足条件的单个元素。按照深度优先和先序遍历的原则使用参数提供的CSS选择器在DOM进行查找,返回第一个满足条件的元素。

querySelectorAll

该方法返回所有满足条件的元素,结果是个nodeList集合。查找规则与前面所述一样。

摘自:http://www.cnblogs.com/Wayou/p/html5_web_api_queryselector.html#undefined

获取触摸事件坐标

	function findCoordinates(e) {
		// 如果需要用 pageX/Y 代替 clinetX/Y
		var x,y;
		if(e.changedTouches){
			x = e.changedTouches[0].clientX;
			y = e.changedTouches[0].clientY;
		}else{
			x = e.clientX;
			y = e.clientY;
		}
		console.log(e);
		$('#test').text("x:"+x+",y:"+y);
		return [x,y];
	}
    
    var el = document.getElementsByClassName('content')[0];
    console.log(el);

    el.addEventListener('touchstart', findCoordinates, false);

触摸事件对象有事件类型、时间目标对象、可以阻止默认行为。

其中 touchList 数组包含了每个触摸点的信息。
changedTouches 数组中第一个对象就是导致事件触发的那个触摸点对象。

clientX/Y 和 pageX/Y 的区别是,前者相对于视觉视口的左上角,后者相对于布局视口的左上角。

注:
关于视口的介绍