在之前的一篇文章当中,介绍了如何使用AutoCompleteTextView按照关键字从服务器上获得数据显示下拉列表。博主也是按照文中的思想在项目开发当中实际运作,使用效果不错,客户见了之后直呼效果很好。但是最近团队中其他人在使用时遇到一头痛的BUG,博主调试了半个小时才调试出来,在这里把BUG和解决方法贴出来,大家参考。
一、功能介绍:在使用AutoCompleteTextView按照关键字从服务器上获得人员信息JSON数据,并把JSON数据中人员名称显示在下来列表中,当用户选中下拉列表中的Item时,需要将选中的Item中从List<Item>中取出来,保存下来,以备后面使用。这就相当于一个新闻列表,我选中新闻时进入新闻的详细信息的操作。只不过这里是将选中的Item中的一部分值放入AutoCompleteTextView,将其他信息保存下来。
如下图,我有可能选中中国气象局,然后将其详细信息保存当javabean中。
package com.yang.testautocompletetextview; import java.util.ArrayList; import java.util.List; import org.apache.http.client.HttpClient; import org.apache.http.client.ResponseHandler; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.BasicResponseHandler; import org.apache.http.impl.client.DefaultHttpClient; import org.json.JSONArray; import android.app.Activity; import android.os.AsyncTask; import android.os.Bundle; import android.text.Editable; import android.text.TextWatcher; import android.util.Log; import android.view.View; import android.widget.AdapterView; import android.widget.AdapterView.OnItemClickListener; import android.widget.ArrayAdapter; import android.widget.AutoCompleteTextView; /** * @Title: WikiSuggestActivity.java * @Package com.yang.testautocompletetextview * @Description: * 参考http://stackoverflow.com/questions/8653260/autocomplete-in-android * -not-working-with-dynamic-data * @author Yang zhilong * @date 2012-8-12 下午2:59:54 * @version V1.0 */ public class WikiSuggestActivity extends Activity { public String data; public List<String> suggest; public AutoCompleteTextView autoComplete; public ArrayAdapter<String> aAdapter; private static final String TAG = "WikiSuggestActivity"; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); suggest = new ArrayList<String>(); autoComplete = (AutoCompleteTextView) findViewById(R.id.autoCompleteTextView1); autoComplete.setOnItemClickListener(itemClickListener); autoComplete.addTextChangedListener(new TextWatcher() { public void afterTextChanged(Editable editable) { } public void beforeTextChanged(CharSequence s, int start, int count, int after) { } public void onTextChanged(CharSequence s, int start, int before, int count) { String newText = s.toString(); new getJson().execute(newText); } }); } OnItemClickListener itemClickListener = new OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { int key = parent.getId();//错误发生在这里 Log.d(TAG, "key is " + key + " and R.id.autoCompleteTextView1 is " + R.id.autoCompleteTextView1); switch (key) { case R.id.autoCompleteTextView1: Log.d(TAG, "I am magic in place!"); break; } } }; class getJson extends AsyncTask<String, String, String> { @Override protected void onPostExecute(String result) { super.onPostExecute(result); aAdapter = new ArrayAdapter<String>(getApplicationContext(), R.layout.item, suggest); autoComplete.setAdapter(aAdapter); aAdapter.notifyDataSetChanged(); } @Override protected String doInBackground(String... key) { String newText = key[0]; newText = newText.trim(); newText = newText.replace(" ", "+"); try { HttpClient hClient = new DefaultHttpClient(); HttpGet hGet = new HttpGet( "http://en.wikipedia.org/w/api.php?action=opensearch&search=" + newText + "&limit=8&namespace=0&format=json"); ResponseHandler<String> rHandler = new BasicResponseHandler(); data = hClient.execute(hGet, rHandler); suggest = new ArrayList<String>(); JSONArray jArray = new JSONArray(data); for (int i = 0; i < jArray.getJSONArray(1).length(); i++) { String SuggestKey = jArray.getJSONArray(1).getString(i); suggest.add(SuggestKey); } } catch (Exception e) { Log.w("Error", e.getMessage()); } return null; } } }打印字符串如下:
10-25 20:06:28.830: D/WikiSuggestActivity(2155): key is -1 and R.id.autoCompleteTextView1 is 2131230720于是乎下面的代码
OnItemClickListener itemClickListener = new OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { int key = parent.getId();//错误发生在这里 Log.d(TAG, "key is " + key + " and R.id.autoCompleteTextView1 is " + R.id.autoCompleteTextView1); switch (key) { case R.id.autoCompleteTextView1: Log.d(TAG, "I am magic in place!"); break; } } };
1、socket编程
Packet套接字用于在MAC层上收发原始数据帧,这样就允许用户在用户空间完成MAC之上各个层次的实现。给无论是进行开发还是测试的人们带来了极大的便利性。
Packet套接字的定义方式与传送层的套接字定义类似,如下:
packet_socket=socket(PF_PACKET,int socket_type,int protocol);
(这个套接字的打开需要用户有root权限)
其中socket_type有两种类型,一种为SOCK_RAW,它是包含了MAC层头部信息的原始分组,当然这种类型的套接字在发送的时候需要自己加上一个MAC头部(其类型定义在linux/if_ether.h中,ethhdr),另一种是SOCK_DGRAM类型,它是已经进行了MAC层头部处理的,即收上的帧已经去掉了头部,而发送时也无须用户添加头部字段。
Protocol是指其送交的上层的协议号,如IP为0x0800,当其为htons(ETH_P_ALL)(其宏定义为0)时表示收发所有的协议。
创建好套接字后,就可以通过与UDP一样的recvfrom与sendto函数进行数据的收发,其目的地址结构为sockaddr_ll,这与传送层的地址结构定义是不一样的,其长度为20字节(在TCP/IP的链路层地址中使用了18字节),而传送层的地址结构长度为16字节。
Sockaddr_ll结构如下:
struct sockaddr_ll
{
unsigned short sll_family; /*总是 AF_PACKET */
unsigned short sll_protocol; /*物理层的协议 */
int sll_ifindex; /* 接口号 */
unsigned short sll_hatype; /*报头类型 */
unsigned char sll_pkttype; /*分组类型 */
unsigned char sll_halen; /* 地址长度 */
unsigned char sll_addr[8]; /*物理层地址 */
};
sll_protocol 是在 linux/if_ether.h头文件中定义的按网络层排序的标准的以太桢协议类型。sll_ifindex 是接口的索引号(参见netdevice(2));0匹配所有的接口(当然只有合法的才用于绑定)。 sll_hatype是在 linux/if_arp.h 中定义的 ARP硬件地址类型。 sll_pkttype 包含分组类型。有效的分组类型是:目标地址是本地主机的分组用的 PACKET_HOST,物理层广播分组用的 PACKET_BROADCAST,发送到一个物理层多路广播地址的分组用的 PACKET_MULTICAST,在混杂(promiscuous)模式下的设备驱动器发向其他主机的分组用的 PACKET_OTHERHOST,本源于本地主机的分组被环回到分组套接口用的 PACKET_OUTGOING。这些类型只对接收到的分组有意义。sll_addr和 sll_halen 包括物理层(例如 IEEE 802.3)地址和地址长度。精确的解释依赖于设备。(本段引于packet的用户手册)
当在多个网络接口的主机上使用这个套接字时,若要指定接收或发送的接口时可以使用bind进行绑定,这与TCP套接字的操作一样,但其内涵并不相同。绑定时将根据地址结构中的sll_protocal和sll_ifindex分别绑定收发的协议号和接口索引号,接口索引号sll_ifindex为0时表示使用有效的所有接口。接口的sll_ifindex值可以通过ioctl获得,如下面是获得名字为“eth0”的接口的索引号
strcpy(ifr.ifr_name,"eth0");
ioctl(fd_packet,SIOCGIFINDEX,&ifr);
取得的值保存在ifr结构体的ifr_ifindex中,ifr结构类型为“struct ifreq”
BTW,要获得接口的物理地址同样使用ioctl可以得到
ioctl(fd_packet,SIOCGIFHWADDR,&ifr);
以数据形式保存在ifr的ifr_hwaddr.sa_data中。
另外需要注意的是,在调用recvfrom函数时返回的地址长度信息为18字节,原因是在sockaddr_ll结构中的sll_addr[8]为8字节,MAC地址只使用了其中的前6字节。在用sendto发送时需要将目的地址结构体强制转换为struct sockaddr 类型,而且指定的长度必须为20字节,而不能是18或其它值。
我在使用中当指定了协议类型后可以准备接收该类型的数据帧,但有个问题一直困扰着我,就是无法过滤掉广播帧,必须要收到帧后判断目的地址是否为自己,然后如果用SOCK_DGRAM的时候又如何判断呢?本人正在探索中,一旦有新进展将第一时间与大家分享。
/*PF_PACKET接口可以操作链路层的数据。
isock = socket(PF_PACKET, SOCK_DGRAM, 0); //为0,之后会用setsockopt之类的设置
第二个参数,在使用SOCK_RAW, SOCK_DGRAM和SOCK_PACKET的区别:
(1)使用SOCK_RAW发送的数据必须包含链路层的协议头,接受得到的数据包,包含链路层协议头。而使用SOCK_DGRAM则都不含链路层的协议头。
(2)SOCK_PACKET也是可以使用的,但是已经废弃,以后不保证还能支持,不推荐使用。
(3)在使用SOCK_RAW或SOCK_DGRAM和SOCK_PACKET时,在sendto和recvfrom中使用的地址类型不同,前两者使用sockaddr_ll类型的地址,
而后者使用sockaddr类型的地址。
(4)如socket的第一个参数使用PF_INET,第二个参数使用SOCK_RAW,则可以得到原始的IP包。*/
2、htons函数
uint16_t htons(uint16_t hostshort);
htons的功能:将一个无符号短整型数值转换为网络字节序,即大端模式(big-endian)
参数u_short hostshort: 16位无符号整数
返回值:TCP / IP网络字节顺序.
3、struct ifreq 获取IP 和mac
struct ifreq {
char ifr_name[IFNAMSIZ];
union {
struct sockaddr ifru_addr;
struct sockaddr ifru_dstaddr;
struct sockaddr ifru_broadaddr;
short ifru_flags;
int ifru_metric;
caddr_t ifru_data;
} ifr_ifru;
};
4、strncpy函数
原型:char * strncpy(char *dest, char *src, size_t n);
功能:将字符串src中最多n个字符复制到字符数组dest中(它并不像strcpy一样只有遇到NULL才停止复制,而是多了一个条件停止,就是说如果复制到第n个字符还未遇到NULL,也一样停止),返回指向dest的指针。
5、sockaddr_ll是设备无关的物理层地址
struct sockaddr_ll {
unsigned short sll_family; /*总是 AF_PACKETPACKETPACKETPACKET */
unsigned short sll_protocol; /*物理层的协议 */
int sll_ifindex; /* 接口号 */
unsigned short sll_hatype; /*报头类型 */
unsigned char sll_pkttype; /*分组类型 */
unsigned char sll_halen; /* 地址长度 */
unsigned char sll_addr[8]; /*物理层地址 */
};
sll_protocol 是在 linuxlinuxlinuxlinux/if_ether.h头文件中定义的按网络层排序的标准的以太桢协议类型。sll_ifindex 是接口的索引号(参见 netdevice(2));0匹配所有的接口(当然只有合法的才用于绑定)。 sll_hatype是在 linuxlinuxlinuxlinux/if_arp.h 中定义的 ARP 硬件地址类型。 sll_pkttype包含分组类型。有效的分组类型是:目标地址是本地主机的分组用的 PACKETPACKETPACKETPACKET_HOST,物理层广播分组用的 PACKETPACKETPACKETPACKET_BROADCAST,发送到一个物理层多路广播地址的分组用的 PACKETPACKETPACKETPACKET_MULTICAST,在混杂(promiscuous)模式下的设备驱动器发向其他主机的分组用的 PACKETPACKETPACKETPACKET_OTHERHOST,本源于本地主机的分组被环回到分组套接口用的 PACKETPACKETPACKETPACKET_OUTGOING。这些类型只对接收到的分组有意义。sll_addr和 sll_halen 包括物理层(例如 IEEE 802.3)地址和地址长度。精确的解释依赖于设备。};
6、SNAP
Ethernet SNAP协议是IEEE为保证在802.2 LLC上支持更多的上层协议的同时更好地支持IP协议而发布的标准,与802.3/802.2 LLC一样802.3/802.2 SNAP也带有LLC头,但是扩展了LLC属性,新添加了一个2字节的协议类型域(同时将SAP的值置为AA),从而使其可以标识更多的上层协议类型;另外添加了一个3字节的厂商代码字段用于标记不同的组织。RFC 1042定义了IP报文在802.2网络中的封装方法和ARP协议在802.2 SANP中的实现方法。
7、Linux下的signal函数
signal信号函数,第一个参数表示需要处理的信号值(SIGHUP),第二个参数为处理函数或者是一个表示,这里,SIG_IGN表示忽略SIGHUP那个注册的信号。
功能:设置某一信号的对应动作
函数原型:
void (*signal(int signum,void(* handler)(int)))(int);
或者:typedef void(*sig_t) ( int ); sig_t signal(int signum,sig_t han
8、strtol函数
long int strtol(const char *nptr,char **endptr,int base);
这个函数会将参数nptr字符串根据参数base来转换成长整型数。参数base范围从2至36,或0。参数base代表采用的进制方式,如base值为10则采用10进制,若base值为16则采用16进制等。当base值为0时则是采用10进制做转换,但遇到如’0x’前置字符则会使用16进制做转换、遇到’0’前置字符而不是’0x’的时候会使用8进制做转换。一开始strtol()会扫描参数nptr字符串,跳过前面的空格字符,直到遇上数字或正负符号才开始做转换,再遇到非数字或字符串结束时('\0')结束转换,并将结果返回。若参数endptr不为NULL,则会将遇到不合条件而终止的nptr中的字符指针由endptr返回;若参数endptr为NULL,则会不返回非法字符串。
在BB10 Cascades Beta2中开发人员已经可以在QML中调用照相机组件,不过在项目配置方面有一些特殊的要求。到了BB10 Cascades Beta3版本,照相机组件的使用就很简单了,下面描述一下具体的步骤。
首先我们需要一个基本的应用框架,这个可以通过BB10 Cascades应用创建向导创建,选择模板的时候选择“Standard Empty Project”就可以了,对具体的创建过程有疑问的话请参考我之前的博文。
创建了应用框架后可以测试运行一下,会在机器上显示"Hello World"字样。
创建了应用框架以后需要修改一下应用的权限,让应用可以调用照相机组件。同时,应为照相机组件缺省将照片保存在共享文件中,所以需要添加访问共享文件的权限。
步骤如下:
1. 双击项目的bar-descriptor.xml文件。
2.打开Application页面
3.勾选Permissions一栏中的“Camera”和“Shared Files”选项
4.保存bar-descriptor.xml文件。
然后打开assets目录下的main.qml文件,我们需要在main.qml中加入照相机的支持。
首先是引入相关的包,代码如下,
import bb.cascades.multimedia 1.0 import bb.multimedia 1.0
然后是添加Camera组件,代码如下,部分不重要的代码已经省略,完整代码在最后有。
Camera { id: camera objectName: "myCamera" onTouch: { if (event.isDown()) { capturePhoto(); } } onCameraOpened: { getSettings(cameraSettings) cameraSettings.focusMode = CameraFocusMode.ContinuousAuto //cameraSettings.shootingMode = CameraShootingMode.Stabilization applySettings(cameraSettings) camera.startViewfinder(); } // ..... onPhotoSaved: { console.log("photoSaved successfully, filaName:" + fileName); } attachedObjects: [ CameraSettings { id: cameraSettings } ] }//Camera
以上代码为mail.qml加上了照相机组件,使用的是Camera{}元素,其中加载了一个对象叫CameraSettings,是照相机初始化时需要使用的。
在Camera{}的onTouch事件中我们加入了capturePhoto()方法的调用,这样当用户点击照相机显示的屏幕时就会照一张像。
在onCameraOpened事件中我们加入了Camera.startViewfinder方法的调用,在相机打开的时候开始捕获传入照相机的图像。
然后在onPhotoSaved事件中加入了控制台输出消息,显示照片所保存的文件名,注意其中的fileName变量是Camera组件生产的变量,代表照片保存的文件位置,额外需要提醒大家注意变量的大小写。
最后是相机的启动,我们希望页面打开后自动启动相机,所以利用了页面(Page)的onCreatedCompleted事件,在该事件中调用了camera.open()方法一启动照相机,代码如下。
onCreationCompleted: { console.log("Photo camera loaded!"); camera.open(); }
一切做完了就可以开始打包测试了,如果应用成功启动的话,应用启动后显示照相机的预览画面,点击预览画面就可以拍照。
注意缺省情况下应用的日志是不出现在BB10 Cascades 的控制台的,需要确认是否拍照成功的话直接去机器的图片应用查看是否有新照片就好了。如果需要在控制台看到信息,请参考我之前有关BB10 Cascades Beta3控制台信息显示的博文。
下面是main.qml的完整代码,供参考。
// Default empty project template import bb.cascades 1.0 import bb.cascades.multimedia 1.0 import bb.multimedia 1.0 // creates one page with a label Page { Container { layout: DockLayout { } Label { text: qsTr("Hello World") textStyle.base: SystemDefaults.TextStyles.BigText verticalAlignment: VerticalAlignment.Center horizontalAlignment: HorizontalAlignment.Center } Camera { id: camera objectName: "myCamera" onTouch: { if (event.isDown()) { capturePhoto(); } } onCameraOpened: { getSettings(cameraSettings) cameraSettings.focusMode = CameraFocusMode.ContinuousAuto //cameraSettings.shootingMode = CameraShootingMode.Stabilization applySettings(cameraSettings) camera.startViewfinder(); } // There are loads of messages we could listen to here //onPhotoSaved and onShutterFired are taken care of in the C code onCameraOpenFailed: { console.log("onCameraOpenFailed signal received with error " + error); } onViewfinderStartFailed: { console.log("viewfinderStartFailed signal received with error " + error); } onViewfinderStopFailed: { console.log("viewfinderStopFailed signal received with error " + error); } onPhotoCaptureFailed: { console.log("photoCaptureFailed signal received with error " + error); } onPhotoSaveFailed: { console.log("photoSaveFailed signal received with error " + error); } onPhotoSaved: { console.log("photoSaved successfully, filaName:" + fileName); } attachedObjects: [ CameraSettings { id: cameraSettings } ] } } onCreationCompleted: { console.log("Photo camera loaded!"); camera.open(); } }