至少有一个Python版本可以在Android上运行
[Scripting Layer for Android(SL4A)][1]允许在任何设备上运行Python
SL4A支持Python 2,而不是Python 3
如何解决Python版本问题?
如果把用户交互移到智能手机上,这样模型和部分控制器代码会保留在服务器上(仍然运行Python 3),而视图代码和其余控制器代码将移到智能手机上,就需要重写这些代码从而在Python 2上运行
Google提供了一个跨平台的[Android模拟器][2],允许跟据需要完成手机应用开发
配置SDK和模拟器:
AVD是一个模拟的Android手机
安装和配置Android脚本环境:
为SL4A安装增加Python:
测试安装环境的脚本mydroidtest.py:
import android
app = android.Android()
msg = "Hello from Head First Python on Android"
app.makeToast(msg)
将脚本复制到模拟器的SD卡(在终端窗口执行):
tools/adb push mydroidtest.py /sdcard/sl4a/scripts
JSON中的JS代表JavaScript,ON代表对象记法,是Web上使用最广泛的数据交换格式之一
通过使用你喜欢的编程语言所提供的JSON库,就能创建可以交换的数据。如果可以读取一个JSON数据流,还可以在必要时重新创建数据
为什么使用JSON?
>>> import json #导入JSON库
>>> names = ['John', ['Johnny', 'Jack'], 'Michael', ['Mike', 'Mikey', 'Mick']]
>>> names
['John', ['Johnny', 'Jack'], 'Michael', ['Mike', 'Mikey', 'Mick']]
>>> to_transfer = json.dumps(names) #将Python多重列表转换为一个JSON多重列表
>>> to_transfer
'["John", ["Johnny", "Jack"], "Michael", ["Mike", "Mikey", "Mick"]]'
>>> from_transfer = json.loads(to_transfer) #将JSON多重列表转换回Python格式
>>> from_transfer
['John', ['Johnny', 'Jack'], 'Michael', ['Mike', 'Mikey', 'Mick']]
>>> names
['John', ['Johnny', 'Jack'], 'Michael', ['Mike', 'Mikey', 'Mick']]
增加一个新函数,返回选手名列表:
def get_names_from_store():
athletes = get_from_store
response = [athletes[each_ath].name for each_ath in athletes]
return(response)
将数据作为一个JSON数据流返回给Web请求者;
import json
import athletemodel
import yate
names = athletemodel.get_names_from_store()
print(yate.start_response('application/json')) #Content-type行使用application/json
print(json.dumps(sorted(names)))
测试JSON生成的脚本时看到的行为可能存在差异,这取决于你使用的浏览器
SL4A技术为底层Android API提供了一个高层API
通过6个Android API调用,可以在对话框中创建一个选项列表,并创建相应的正负按钮,用来指示用户所做的选择
import android
app = android.Android() #创建一个Android应用对象
app.dialogCreateAlert("Select an athlete:")
app.dialogSetSingleChoiceItems(['Mikey', 'Sarah', 'James', 'Julie'])
app.dialogSetPositiveButtonText("Select")
app.dialogSetNegativeButtonText("Quit")
app.dialogShow() #在应用上显示对话框
resp = app.dialogGetResponse().result #等待用户的响应
向Web服务器查询名字列表,作为一个JSON数组返回,并在智能手机上显示这个列表:
import android
import json
import time
from urllib import urlencode #这些库可以提供Web客户端功能
from urllib2 import urlopen
hello_msg = 'Welcome to Coach Kelly's Timing App'
list_title = 'Here is your list of athletes:'
quit_msg = 'Quitting Coach Kelly's App.'
web_server = 'http://192.168.1.33.8080' #Web服务器所在的Web地址
get_names_cgi = '/cgi-bin/generate_names.py'
def send_to_server(url, post_data=None):
"""这个函数取一个Web地址和一些可选数据,向Web服务器发送一个Web请求,Web响应返回给调用者"""
if post_data:
page = urlopen(url, urlencode(post_data))
else:
page = urlopen(url)
return(page.read().decode("utf8"))
app = android.Android() #创建一个Android应用对象
def status_update(msg, how_long=2):
"""这个函数用来在手机上显示简短的消息"""
app.makeToast(msg)
time.sleep(how_long)
status_update(hello_msg) #显示欢迎消息
athlete_names = sorted(json.loads(send_to_server(web_server + get_names_cgi))) #把Web请求发送到服务器,然后将JSON响应转换为一个有序列表
app.dialogCreateAlert(title_list)
app.dialogSetSingleChoiceItems(athlete_names)
app.dialogSetPositiveButtonText("Select")
app.dialogSetNegativeButtonText("Quit")
app.dialogShow()
resp = app.dialogGetResponse().result #等待用户点击一个按钮,然后赋值给resp
status_update(quit_msg) #显示退出消息
如果点击的是第一个按钮,dialogGetResponse()调用的结果会设置为positive,如果点击的是第二个按钮,则会设置为negative
dialogGetSelectedItems()调用会返回所选列表项的索引值
get_data_cgi = '/cgi-bin/generate_data.py' #提供运行时的CGI的名
if resp['which'] in ('positive'):
selected_athlete = app.dialogGetSelectedItems().result[0] #索引值对应对话框返回列表的第一个元素
which_athlete = athlete_names[selected_athlete] #使用索引值查找选手名
athlete = json.loads(send_to_server(web_server + get_data_cgi, {'which_athlete':which_athlete})) #向服务器发送一个新的Web请求,获取这个选手的数据
athlete_title = which_athlete + 'top 3 times:'
app.dialogCreateAlert(athlete_title)
app.dialogSetItems(athlete['Top3'])
app.dialogSetPositiveButtonText('OK'))
app.dialogShow()
resp = app.dialogGetResponse().result
cgi-bin/generate_data.py CGI脚本:
import cgi
import json
import athletemodel
import yate
athletes = athletemodel.get_from_store() #从模型得到所有的数据
form_data = cgi.FieldStorage() #处理随请求发送的数据
athlete_name = form_data['which_athlete'].value
print(yate.start_response('application/json')) #启动一个Web请求,数据类型为JSON
print(json.dumps(athletes[athlete_name])) #在Web响应中包含制定选手的数据,采用JSON格式
CGI机制默认地会捕获脚本发送到标准输出(STDOUT)的所有输出
将调试消息发送到Web服务器的控制台,显示到标准错误输出(STDERR):
import sys #从标准库中导入sys
print(json.dumps(athletes[athlete_name]), file=sys.stderr) #将输出重定向到stderr
现在可以在Web服务器的控制台看到Web响应发送的数据
标准库的JSON库只能处理Python的内置类型,无法处理AthleteList对象
为AthleteList增加一个方法,将数据转换为一个字典:
@property
def to_dict(self):
return({'Name': self.name, 'DOB': self.dob, 'Top3':self.top3})
使用@property修饰这个方法,对类用户来说这个方法就像一个新的属性
为什么使用@property?
to_dict()方法并没有以任何方法改变对象数据的状态,只是把对象的属性数据作为一个字典返回。尽管to_dict()是一个方法,但它表现得更像一个属性,使用@property修饰符可以指出这一点。类的用户并不需要知道它们访问to_dict属性时实际上在运行一个方法,他们看到的只是一个统一的接口:属性访问数据,而方法用来管理数据
将代码复制到一个真正的设备的多种选择:
通过WiFi使用文件传输工具: