JS与APP原生控件交互

  “热更新”、“热部署”相信对于混合式开发的童鞋一定不陌生,那么APP怎么避免每次升级都要在APP应用商店发布呢?这里就用到了混合式开发的概念,对于电商网站尤其显得重要,不可能每次发布一个活动,都要发布一个现版本,当然这样对于Android还算可以,但是对于Ios呢?苹果应用商店每次审核的时间基本都在1~2周,这对于一个促销活动来说审核时间实在太长。而混合式开发正好可以解决这个问题,基本的原理时,通过原生控件实现APP的主体结构,借助H5开发对应的页面,这样每次发布活动,只需要在服务器端,将活动发布,便可以达到所有安装用户不升级便可查阅最新活动的效果。今天就为大家分享一下,如何实现JavaScript与APP原生控件交互。

  一、首先为大家介绍的是JS与Android交互,首先让大家看一下Android工程的目录结构:

  

  JSObject.java文件封装了JS调用Android原生控件的方法;MainActivity.java是调用WebView控件实现网页页面加载,以及进行控件调用JS方法的封装;test.html是我们加载的HTML页面。接下来我们具体看一下实现:

  MainActivity.java

package com.chinaonenet.mywebview;import android.annotation.SuppressLint;
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.view.View;
import android.view.View.OnClickListener;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.widget.Button;/*** SuppressLint一定要加上去!!!* 低版本可能没问题,高版本JS铁定调不了Android里面的方法*/
@SuppressLint("SetJavaScriptEnabled")
public class MainActivity extends Activity {private Button button1,button2;private WebView mWebView;private MyWebViewClient WVClient;private WebSettings webSettings;private MyWebChromeClient chromeClient;//封装接收js调用Android的方法类private JSObject  jsobject;//异步请求private Handler mHandler = new Handler();@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);init();initView();setButton();}private void setButton() {//无参调用button1.setOnClickListener(new OnClickListener() {@Overridepublic void onClick(View v) {mHandler.post(new Runnable() {@Overridepublic void run() {mWebView.loadUrl("javascript:showNoMessage()");}});}});//有参调用button2.setOnClickListener(new OnClickListener() {@Overridepublic void onClick(View v) {mHandler.post(new Runnable() {@Overridepublic void run() {mWebView.loadUrl("javascript:showMessage('顺带给JS传个参数')");}});}});}private void init() {mWebView = (WebView) findViewById(R.id.webview);button1 = (Button)findViewById(R.id.button1);button2 = (Button)findViewById(R.id.button2);WVClient = new MyWebViewClient();chromeClient = new MyWebChromeClient();jsobject = new JSObject(MainActivity.this); }private void initView() {webSettings = mWebView.getSettings();webSettings.setJavaScriptEnabled(true);webSettings.setBuiltInZoomControls(true);webSettings.setSavePassword(false);//支持多种分辨率,需要js网页支持webSettings.setUserAgentString("mac os");webSettings.setDefaultTextEncodingName("utf-8");//显示本地js网页mWebView.loadUrl("file:///android_asset/test.html");mWebView.setWebViewClient(WVClient);mWebView.setWebChromeClient(chromeClient);//注意第二个参数android,这个是JS网页调用Android方法的一个类似ID的东西mWebView.addJavascriptInterface(jsobject, "android");}
}

  页面的配置文件(activity_main.xml)

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"tools:context="com.chinaonenet.mywebview.MainActivity" ><Buttonandroid:id="@+id/button1"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_alignParentLeft="true"android:text="无参" /><Buttonandroid:id="@+id/button2"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_alignParentRight="true"android:text="有参" /><WebViewandroid:id="@+id/webview"android:layout_width="wrap_content"android:layout_height="match_parent"android:layout_alignParentLeft="true"android:layout_alignParentRight="true"android:layout_below="@+id/button2" /><TextViewandroid:id="@+id/textView1"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_alignBaseline="@+id/button1"android:layout_alignBottom="@+id/button1"android:layout_centerHorizontal="true"android:text="js与android交互" /></RelativeLayout>

  JSObject.java

package com.chinaonenet.mywebview;import org.json.JSONArray;
import org.json.JSONException;import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.webkit.JavascriptInterface;
import android.widget.Toast;/*** JS调用android的方法* @JavascriptInterface仍然必不可少*/
public class JSObject {private Context context;public JSObject(Context context){this.context = context;}//js调用无参方法
    @JavascriptInterfacepublic void callNull(){Toast.makeText(context, "JsCallAndroid", Toast.LENGTH_SHORT).show();}//js调用有参方法
    @JavascriptInterfacepublic void callMessage(String data){Toast.makeText(context, data, Toast.LENGTH_SHORT).show();}//js调用有参方法,参数类型:JSON
    @JavascriptInterfacepublic void callJson(String data) throws JSONException{JSONArray jsonArray = new JSONArray(data);Toast.makeText(context, jsonArray.toString(), Toast.LENGTH_SHORT).show();}//js调用有参方法,参数类型:JSON,获取电话号码拨打
    @JavascriptInterfacepublic void callPhone(String data){context.startActivity(new Intent(Intent.ACTION_CALL, Uri.parse("tel:" + data)));}
}

  加载的HTML页面:

<style>.main-wrap ul {width: 100%;display: inline-block;padding-top: 20px;}.main-wrap ul li {float: left;width: 100%;height: 40px;line-height: 40px;font-size: 14px;margin-bottom: 20px;background-color: #00D000;color: #fff;text-align: center;cursor: pointer;}.main-wrap ul li:active {opacity: 0.8;}
</style><div class="main-wrap"><ul class="postAndroid"><li onclick="jsCallAndroid('1')">不传参数调用原生控件</li><li onclick="jsCallAndroid('2')">传参数调用原生控件</li><li onclick="jsCallAndroid('3')">以JSON格式传参数调用原生控件</li><li onclick="jsCallAndroid('4')">调用打电话服务</li></ul>
</div><script>function jsCallAndroid(rel) {switch(rel){case "1":android.callNull();break;case "2":android.callMessage("javaScript操作Android原生");break;case "3":var json = "[{\"name\":\"满艺网\", \"phone\":\"4008366069\"}]";android.callJson(json);break;case "4":android.callPhone("4008366069");break;}}function showNoMessage() {alert("Android无参调用");}function showMessage(data) {alert("Android有参调用-data:" + data);}
</script>

  这里因为需要实现一个拨打电话的功能,所以需要在AndroidManifest.xml文件中添加拨打电话的权限:

<uses-permission android:name="android.permission.CALL_PHONE" />

  当然这里加载的页面是本地页面,当加载网络页面时需要添加请求网络权限:

<uses-permission android:name="android.permission.INTERNET" />

  好了关于JS与Android原生的控件进行相互调用的知识就介绍完了,最后附上DEML下载地址:http://pan.baidu.com/s/1eR6l7Fk  提取密码:4st0

  二、JS与IOS原生交互,这里的开发语言选用的是Swift语言,版本是2.2。首先上工程目录结构:

  

  ViewController.swift

import UIKit
import JavaScriptCoreclass ViewController: UIViewController {var context = JSContext()var jsContext: JSContext?@IBOutlet weak var webView: UIWebView!override func viewDidLoad() {super.viewDidLoad()webView.delegate = self//初始化webView
        loadJS()}/***加载html页面*/func loadJS() {let str = NSBundle.mainBundle().pathForResource("test", ofType: "html")let request = NSURLRequest(URL: NSURL(string: str!)!)webView.loadRequest(request)}//Swift 调用JS 方法 (无参数)
    @IBAction func swift_js_pargram(sender: AnyObject) {self.context.evaluateScript("Swift_JS1()")//self.webView.stringByEvaluatingJavaScriptFromString("Swift_JS1()") //此方法也可行
    }//Swift 调用JS 方法 (有参数)
    @IBAction func swift_js_nopargam(sender: AnyObject) {self.context.evaluateScript("Swift_JS2('Ios' ,'Swift')")//self.webView.stringByEvaluatingJavaScriptFromString("Swift_JS2('oc','swift')") //此方法也可行
    }//js调用Swift方法(无参)
    func menthod1() {print("JS调用了无参数swift方法")let title = "操作提示";let msg = "JS调用了无参数swift方法";//创建提示信息let alert = UIAlertController(title: title, message: msg, preferredStyle: .Alert)//确定按钮alert.addAction(UIAlertAction(title: "ok", style: .Default, handler: nil))//显示提示框self.presentViewController(alert, animated: true, completion: nil)}//js调用Swift方法(有参数)
    func menthod2(str1: String, str2: String) {print("JS调用了有参数swift方法:参数为\(str1),\(str2)")//创建提示信息let alert = UIAlertController(title: str1, message: str2, preferredStyle: .Alert)//确定按钮alert.addAction(UIAlertAction(title: "ok", style: .Default, handler: nil))//显示提示框self.presentViewController(alert, animated: true, completion: nil)}func webView(webView: UIWebView, didFailLoadWithError error: NSError) {print(error)}
}//js调用Swift方法注入
extension ViewController: UIWebViewDelegate {func webView(webView: UIWebView, shouldStartLoadWithRequest request: NSURLRequest, navigationType: UIWebViewNavigationType) -> Bool {let str = NSBundle.mainBundle().pathForResource("test", ofType: "html")let request = NSURLRequest(URL: NSURL(string: str!)!)let connecntion = NSURLConnection(request: request, delegate: self)connecntion?.start()return true}func webViewDidStartLoad(webView: UIWebView) {print("webViewDidStartLoad----")}func webViewDidFinishLoad(webView: UIWebView) {self.context = webView.valueForKeyPath("documentView.webView.mainFrame.javaScriptContext") as! JSContext//JS调用了无参数swift方法---menthod1let temp1: @convention(block) () ->() = {self.menthod1()}//forKeyedSubscript:参数为JS调用方法名self.context.setObject(unsafeBitCast(temp1, AnyObject.self), forKeyedSubscript: "test1")//JS调用了有参数swift方法---menthod2let temp2: @convention(block) () ->() = {let array = JSContext.currentArguments()//这里接到的array中的内容是JSValue类型for object in array {print("参数:" + object.toString())}self.menthod2(array[0].toString(), str2: array[1].toString())}//forKeyedSubscript:参数为JS调用方法名self.context.setObject(unsafeBitCast(temp2, AnyObject.self), forKeyedSubscript: "test2")//模型注入的方法let model = JSObjCModel()model.controller = selfmodel.jsContext = contextself.jsContext = context//这一步是将OCModel这个模型注入到JS中,JS就可以通过OCModel调用我们公暴露的方法了。self.jsContext?.setObject(model, forKeyedSubscript: "OCModel")let url = NSBundle.mainBundle().URLForResource("test", withExtension: "html")self.jsContext?.evaluateScript(try? String(contentsOfURL: url!, encoding: NSUTF8StringEncoding));self.jsContext?.exceptionHandler = {(context, exception) inprint("exception @", exception)}}
}@objc protocol JavaScriptSwiftDelegate: JSExport {func callSystemCamera()func showAlert(title: String, msg: String)func callWithDict(dict: [String: AnyObject])func jsCallObjcAndObjcCallJsWithDict(dict: [String: AnyObject])
}//js调用Swift模型方法
@objc class JSObjCModel: NSObject, JavaScriptSwiftDelegate {weak var controller: UIViewController?weak var jsContext: JSContext?//JS无参调用Swift方法并返回处理结果
    func callSystemCamera() {print("js call objc method: callSystemCamera");let jsFunc = self.jsContext?.objectForKeyedSubscript("jsFunc");jsFunc?.callWithArguments([]);}//JS有参调用Swift方法
    func showAlert(title: String, msg: String) {print("js call objc method: showAlert, title: %@", title, "   msg: %@", msg)dispatch_async(dispatch_get_main_queue()) { () -> Void inlet alert = UIAlertController(title: title, message: msg, preferredStyle: .Alert)alert.addAction(UIAlertAction(title: "ok", style: .Default, handler: nil))self.controller?.presentViewController(alert, animated: true, completion: nil)}}//JS有参调用Swift方法
    func callWithDict(dict: [String : AnyObject]) {print("js call objc method: callWithDict, args: %@", dict)let alert = UIAlertController(title: "消息提示", message: "查看控制台打印信息", preferredStyle: .Alert)alert.addAction(UIAlertAction(title: "ok", style: .Default, handler: nil))self.controller?.presentViewController(alert, animated: true, completion: nil)}//JS有参调用Swift方法并返回处理结果
    func jsCallObjcAndObjcCallJsWithDict(dict: [String : AnyObject]) {print("js call objc method: jsCallObjcAndObjcCallJsWithDict, args: %@", dict)let jsParamFunc = self.jsContext?.objectForKeyedSubscript("jsParamFunc");let dict = NSDictionary(dictionary: ["age": 2, "height": 178, "name": "满艺网"])jsParamFunc?.callWithArguments([dict])}
}extension ViewController: NSURLConnectionDelegate,NSURLConnectionDataDelegate {func connection(connection: NSURLConnection, didReceiveData data: NSData) {print("didReceiveData\(data)")}func connection(connection: NSURLConnection, willSendRequest request: NSURLRequest, redirectResponse response: NSURLResponse?) -> NSURLRequest? {print("request:\(request)response:\(response)")return request}func connection(connection: NSURLConnection, didFailWithError error: NSError) {}
}//MARK: - allowsAnyHTTPSCertificateForHost
extension NSURLRequest {static func allowsAnyHTTPSCertificateForHost(host: String) -> Bool {return true}
}

  test.html

<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /><title>JSAndIos</title></head><style>.main-wrap ul {width: 100%;display: inline-block;padding-top: 20px;}.main-wrap ul li {float: left;width: 100%;height: 40px;line-height: 40px;font-size: 14px;margin-bottom: 20px;background-color: #00D000;color: #fff;text-align: center;cursor: pointer;}.main-wrap ul li:active {opacity: 0.8;}</style><body bgcolor="#dddd"><div class="main-wrap"><ul class="postAndroid"><li onclick="JS_Swift1()">不传参数调用原生控件(常规方式)</li><li onclick="JS_Swift2()">传参数调用原生控件(常规方式)</li><li onclick="JS_Swift3()">不传参数调用原生控件,带返回结果处理(模型注入)</li><li onclick="JS_Swift4()">传参数调用原生控件(模型注入)</li><li onclick="JS_Swift5()">传对象格式参数调用原生控件(模型注入)</li><li onclick="JS_Swift6()">传对象格式参数调用原生控件,带返回结果处理(模型注入)</li></ul></div><script>function Swift_JS1() {alert("Swift调用Js无参方法");}function Swift_JS2(name,msg) {alert("Swift调用Js有参方法,name:"+name+";mes:"+msg);}function JS_Swift1() {test1();}function JS_Swift2() {test2('JsCallSwift', 'Js调用Swift方法');}function JS_Swift3() {OCModel.callSystemCamera();}//js调用Swift方法,处理返回结果function jsFunc() {alert('JS调用Swift方法,无返回值结果处理');}function JS_Swift4() {OCModel.showAlertMsg('js send title', 'js send message');}function JS_Swift5() {OCModel.callWithDict({ 'name': 'testname', 'age': 10, 'height': 170 });}function JS_Swift6() {OCModel.jsCallObjcAndObjcCallJsWithDict({ 'name': 'testname', 'age': 10, 'height': 170 });}//注意哦,如果JS写错,可能在OC调用JS方法时,都会出错哦。function jsParamFunc (argument) {alert("JS调用Swift方法,有返回值处理结果---name:"+argument['name'] + "    age:" + argument['age']);}</script></body>
</html>

  好了,对于JS与Ios原生控件之间进行相互调用的主要内容就分享完毕了,DEML下载地址:链接: https://pan.baidu.com/s/1bpEFzaR 密码: y5ms

  到这里关于JS与Android、IOS原生控件相互之间进行调用的知识就为大家分享完毕,欢迎留言讨论,相互学习。

转载于:https://www.cnblogs.com/AndroidJotting/p/6237407.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/456610.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

《脱颖而出——成功网店经营之道》一2.6 连横:返利模式的应用及分销

本节书摘来异步社区《脱颖而出——成功网店经营之道》一书中的第2章&#xff0c;第2.6节&#xff0c;作者&#xff1a; 何小健 责编&#xff1a; 赵轩, 更多章节内容可以访问云栖社区“异步社区”公众号查看。 2.6 连横&#xff1a;返利模式的应用及分销 脱颖而出——成功网店…

【python】os模块 递归删除文件夹所有文件 --笔记

os.environ 获取系统的环境变量 os.name nt -- windows \r\n | posix --- Linux \nos.path: import osprint(os.environ) print(os.environ[OS])print(os.path.abspath(t1/file01.py)) # 获取绝对路径 print(os.path.isabs(t1/file01.py)) # 判断所给的路径是否是一个绝对…

大数据之Spark集群安装及简单使用

目录1、Spark集群安装1.1. 安装 2、启动Spark Shell2.1、启动spark shell2.2、在spark shell中编写WordCount程序 目录 1、Spark集群安装 1.1. 安装 1.1.1. 机器部署 准备两台以上Linux服务器&#xff0c;安装好JDK1.7 1.1.2. 下载Spark安装包 下载地址&#xff1a;ht…

《C和C++代码精粹》——1.7 类型安全I/O

本节书摘来自异步社区出版社《C和C代码精粹》一书中的第1章第1.7节&#xff0c;作者&#xff1a; 【美】Chuck Allison&#xff0c;更多章节内容可以访问云栖社区“异步社区”公众号查看。 1.7 类型安全I/O C和C代码精粹当然每个C程序员都曾经使用过printf的错误格式描述符号。…

大数据之Spark简介及RDD说明

目录前言&#xff1a;1、Spark概述1.1、什么是Spark&#xff08;官网&#xff1a;http://spark.apache.org&#xff09;1.2、为什么要学Spark1.3、Spark特点 2、RDD概述2.1、什么是RDD2.2、RDD的属性2.3、创建RDD的两种方式2.4、RDD编程API2.5、RDD的依赖关系2.6、RDD的缓存2.7…

Python3中urlopen()详解

一. 简介 urllib.request.urlopen()函数用于实现对目标url的访问。 函数原型如下&#xff1a;urllib.request.urlopen(url, dataNone, [timeout, ]*, cafileNone, capathNone, cadefaultFalse, contextNone)  url: 需要打开的网址 data&#xff1a;Post提交的数据 timeo…

python面向对象实现简易银行管理员页面系统

银行管理员页面系统设计:card 类&#xff1a; cardId password moneyuser 类: username phone id cards[]银行的工作人员: 账号 密码 登录 展现管理系统所有功能Bank类:开户取钱存钱转账销户查账解锁... import pickle import random# 银行卡 import time#card 类…

单子模式

package test; class aceing { /** param args */ private static aceing ace; public aceing() { super(); System.out.println("aceing类中的无参构造器调用了"); } public static aceing getAceing() { if(ace null) { ace new aceing(); } return ace; }} pack…

《塑造互联网思维的企业》一一第4章 全球商务向社会化媒体的转变

第4章 全球商务向社会化媒体的转变 塑造互联网思维的企业社会化媒体在发达国家的广泛采用已是我们所熟知的&#xff0c;它在社会和全球文化产生的大范围变革中至关重要。为了完全了解其中的原因&#xff0c;有必要探究一下那些推动社会化商务的复杂且相互关联的因素的共同影响…

大数据之SparkSQL简介及DataFrame的使用

目录前言&#xff1a;1、Spark SQL1.1、Spark SQL概述1.2、DataFrames1.3、DataFrame常用操作 总结&#xff1a; 目录 前言&#xff1a; 本文主要介绍下SparkSQL以及SparkSQL的简单使用。这里只是做了一个非常简单的介绍&#xff0c;后续工作中如果有用到相关的知识&#xff…

python的各种推导式(列表推导式、字典推导式、集合推导式)

python的各种推导式&#xff08;列表推导式、字典推导式、集合推导式&#xff09; 推导式comprehensions&#xff08;又称解析式&#xff09;&#xff0c;是Python的一种独有特性。推导式是可以从一个数据序列构建另一个新的数据序列的结构体。 共有三种推导&#xff0c;在Pyt…

原生js实现jquery库中选择器的功能(jquery库封装一)

今天是2017.1.1&#xff0c;新的一天&#xff0c;新的一年&#xff0c;新的一年里继续夯实基础知识&#xff0c;在工作中多些项目&#xff0c;多思考&#xff0c;多总结&#xff0c;前端是不断更新&#xff0c;在更新的过程中也是发现乐趣和挑战自我的过程&#xff0c;希望年轻…

Ubantu下使用vi时,方向键变字母输出、退格键无法删除字符的解决办法

目录前言&#xff1a;一、编辑/etc/vim/vimrc.tiny二、安装vim full版本三、添加”.vimrc”文件 目录 前言&#xff1a; 最近由于要玩TensorFlow&#xff0c;所以把尘封已久的Ubantu给打开了&#xff0c;不过配置网络的时候&#xff0c;算是一团糟&#xff0c;出现了在插入模…

《Python爬虫开发与项目实战》——第3章 初识网络爬虫 3.1 网络爬虫概述

本节书摘来自华章计算机《Python爬虫开发与项目实战》一书中的第3章&#xff0c;第3.1节&#xff0c;作者&#xff1a;范传辉著&#xff0c;更多章节内容可以访问云栖社区“华章计算机”公众号查看 第3章 初识网络爬虫 从本章开始&#xff0c;将正式涉及Python爬虫的开发。本章…

【python】解决:TypeError: can't send non-None value to a just-started generator

在一个生成器函数未启动之前&#xff0c;是不能传递数值进去。必须先传递一个None进去或者调用一次next(g)方法&#xff0c;才能进行传值操作 def product(c):# c.send(None)for i in range(5):print("生产者产生数据%d"%i)r c.send(str(i))print("消费者消费了…

Shell脚本与vi编辑器:vi启动与退出、工作模式、命令大全

Vi简介 Vi是一种广泛存在于各种UNIX和Linux系统中的文本编辑程序。Vi不是排版程序&#xff0c;只是一个纯粹的文本编辑程序。Vi是全屏幕文本编辑器&#xff0c;它没有菜单&#xff0c;只有命令。Vi不是基于窗口的&#xff0c;所以&#xff0c;这个多用途编辑程序可以用于在任何…

windows环境下,如何在Pycharm下安装TensorFlow环境

文章目录目录前言&#xff1a;1.安装Anaconda2.安装Tensorflow3.其他问题4.在pycharm中使用tensorflow目录 前言&#xff1a; 最近由于工作需要要使用TensorFlow&#xff0c;所以只能狂补相关的知识。本来博主打算在Ubantu上玩&#xff0c;但是由于一些原因还是放弃了这个想法…

python面向对象实现简易银行管理员页面系统【导入自定义模块】

main.py 人 类名&#xff1a;Person 属性&#xff1a;姓名 身份证号 电话 卡 行为&#xff1a;卡 类名&#xff1a;Card 属性&#xff1a;卡号 密码 余额 行为&#xff1a;银行 类名&#xff1a;Bank 属性&#xff1a;用户列表 提款机提款机 类名&#xff1a;ATM 属性&#xf…

[转] dpkg-deb命令

点击此处阅读原文 软件包管理 dpkg-deb命令是Debian Linux下的软件包管理工具&#xff0c;它可以对软件包执行打包和解包操作以及提供软件包信息。 语法dpkg-deb(选项)(参数) 选项-c&#xff1a;显示软件包中的文件列表&#xff1b; -e&#xff1a;将主控信息解压&#xff1b;…

《Effective Debugging:软件和系统调试的66个有效方法》一第5条:在能够正常运作的系统与发生故障的系统之间寻找差别...

本节书摘来自华章出版社《Effective Debugging&#xff1a;软件和系统调试的66个有效方法》一书中的第1章&#xff0c;第1.5节&#xff0c;作&#xff3b;希&#xff3d;迪欧米迪斯斯宾奈里斯&#xff08;Diomidis Spinellis&#xff09;&#xff0c;更多章节内容可以访问云栖社…