合开发的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 classHostJsScope{
/**
* 短暂气泡提醒
* @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('sharetimeline');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 msgWrapif (_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等功能,这个方法也简单好用。

Android Hybird App开发相关推荐

  1. android和chrome的发展与未来[j].移动通信,基于Android手机app开发与设计 毕业设计 开题报告...

    基于Android 手机app开发 电子同学录的设计与开发 四.检索与本课题有关参考文献资料的简要说明 [1] 成刚编. Eclipse 从入门到精通[M] . 北京:清华大学出版社,2005 [2] ...

  2. android手机APP开发入门心得

    android手机APP开发心得 http://blog.csdn.net/fubin0000 首先安装好开发环境(这个过程网络上资料太多了,我相信你搜一搜搭建好环境应该是小菜一碟),然后就是在ecl ...

  3. Android音乐App开发准备( 简易音乐 一 )

    Android音乐App开发准备( 简易音乐 一 ) 关于 第一步,获取音乐来源 第二步,开发环境 第三步,引用三方 第四步,添加唯一进程App.java 关于   年前学习网易云因为app,想着学习 ...

  4. Android音乐App开发准备( 简易音乐 二 )

    Android音乐App开发准备( 简易音乐 二 ) 关于 效果: 编写Activity基类-BaseActivity 修改启动页 复用的标题栏 关于   第一篇看Android音乐App开发准备( ...

  5. Android移动APP开发笔记——最新版Cordova 5.3.1(PhoneGap)搭建开发环境

    引言 简单介绍一下Cordova的来历,Cordova的前身叫PhoneGap,自被Adobe收购后交由Apache管理,并将其核心功能开源改名为Cordova.它能让你使用HTML5轻松调用本地AP ...

  6. [Android] [Hybrid APP开发简述]

    Hybrid APP Hybrid App(混合模式移动应用)是指介于web-app.native-app这两者之间的app,兼具"Native App良好用户交互体验的优势"和& ...

  7. 【Android】App开发-动画效果篇

    在我们玩手机的过程中,如果我们点击某一个页面时,会出现一个页面动画加载或者动画效果的现象.现在我们就来看看App开发中是如何实现动画效果的. 目录 动画的分类 逐帧动画: 补间动画: 动画的分类 在常 ...

  8. Android应用App开发工具

    我们知道,开发一个软件产品,最怕的就是没有辅助IDE工具.如果要用文本编辑器,一行一行的coding,效率太低了.现在的应用非常复杂,一个应用包括资源文件.源码文件.签名加密文件等,非常多.好在And ...

  9. android应用app开发

    开发android 的app是用java写的, 开发ios的app是用obj-c写的,类似c++, 对于这两种语言都不了解的情况下去做app开发成本还是比较大的,还好,市场上有些拖拽就可以建应用的工具 ...

最新文章

  1. CVPR'21 | Involution:超越convolution和self-attention的神经网络新算子
  2. ideaspringboot项目上传服务器_nuxt+pm2 自动化部署及打包后文件自动上传阿里云 oss
  3. apk图标存放位置_安卓系统下安装完apk程序后,具体的文件夹位置在哪里呢?
  4. 移动端图片上传后进行压缩功能
  5. 虚拟机VMware下安装Linux系统,Python3.7之TensorFlow安装
  6. Linux VFS的主要的数据结构
  7. 公司服务器文档管理制度,文件服务器管理制度
  8. Windows Server 2008 R2 RODC(只读域控制器)
  9. Django 安装使用
  10. 电脑无线网络显示红叉_Maxidix Wifi Suite下载_无线网络管理软件中文版v14.5.8
  11. 干货 | 携程平台化常态化数据治理之路
  12. 神经网络方法研究及应用,神经网络算法应用案例
  13. 举例说明国内云计算厂商代表有哪些?
  14. 迪杰斯特拉(Dijkstra)算法最通俗易懂的讲解
  15. Java/计算银行利率
  16. 2021真无线耳机推荐,必须了解的真无线蓝牙耳机
  17. php WEB报表工具的设计
  18. 我自己对英语学习的心得与体会
  19. DBUS入门与C编程
  20. NLP深入学习——过滤停用词(Filtering stop words)

热门文章

  1. CentOS 7下Nginx安装配置nginx-module-vts 监控模块
  2. #yyds干货盘点# Golang 有趣的 logo
  3. 嵌入式软件学习路线图!
  4. TF卡及SD卡接口规范
  5. Gson解析json对象,json数组
  6. PADS,ADOBE,CAD工具栏重置(复位)
  7. 通用定时器输出PWM波实现呼吸灯
  8. MySQL写循环语句的方法
  9. 迅达云入选国际通信展“云计算年度优秀行业应用”三强 !
  10. 测试一个显示器有拖影的软件,游戏娱乐测试:拖影、延迟都是问题