android客户端通过wcf与sqlserver数据库交换数据

写在前面的话

       首先说几句,我只是一个android小白,在本科毕设时做了一款简单的测试题app,界面难看不说,功能还非常容易实现,不过从此我对android产生了浓厚的兴趣。在研究生入学之前,老板要求我们来学校暑期培训,我的培训任务就是继续完善这款app(添加功能和美化UI)。于是从7月下旬忙到现在,总算像一款app了。不过说实话,既要实现功能,又要美化界面;既要做客户端,还要做服务端,真的是挺蛋疼的。
       好了,废话不说了,直奔主题吧。先从android存储方式说起,第一版的时候,我要存数据,我看的书上介绍了4种:SharedPreference、Sqlite、文件流、db4o(这个貌似很小众)。什么都不懂的我选择了语法最简单的SP,而且当时需要存储的数据也不多,我就将就用了。培训期间,根据要求我把存储方式改成了Sqlite(本来就应该是这样 = =!),心好累。然而在这个改动过程中,由于自己对Sql数据库一点都不了解,就默默地留下了一个坑,细节就不说了,都是泪。再后来,就前不久,师兄说用户需求是把数据存储到云端,所以你接着改吧,顺便加上登录注册模块。我像没头的苍蝇一样搞了一周,也算是实现了。这中间的道路非常曲折,各种问题,各种蛋疼。于是我才有了写下来的想法,一来总结记录自己的收获,二来分享我遇到的问题给大家,让后来人少走弯路。

正文

       不就是连接数据库吗?打开百度(还不习惯Google),搜索“android连接服务器数据库”,果然一堆文章。然而当时的我连什么是服务器都不懂(其实现在我也说不清楚),看了一上午,我大概知道了,连接数据库有直接和间接两种方式:直接的话通过JDBC驱动(微软官方的和民间的),间接的话就是通过服务器和数据库交互,服务器可以是java web、webservice、wcf或者java application。那还想什么,直连多方便(too young = =!),文章也是现成的,于是我就参考某大牛的博客开始尝试。然并卵,整整两天没调出来。也许博主是调试通过了的,但是直连真的好么?其实不好,一个小小的android客户端和一个大型的数据库直接交互,你觉得安全吗?(调不出来,强行找台阶= =!)虽然不符合逻辑不实用,不过你们可以去试试。
       果断换成间接连接,参考文章:http://blog.csdn.net/zhyl8157121/article/details/8169172。说实话,大神博主写得非常详细,在此衷心地表达一下感谢。不过,我觉得有些地方还是有点不实用(额 = =!,并不是强行装x)。首先服务端,由于安全性问题,我把webservice换成了wcf;其次客户端,原博中写的HttpConnSoap类连接webservice太繁琐,不如ksoap2-android简单实用。接下来,我就大致说说我的做法,有什么不对的地方或者有什么更好的建议和意见,欢迎批评指正,可骂可赞。有任何问题都可以联系我,邮箱:21515046@zju.edu.cn。

数据库

       我连接的数据库是服务器的数据库,它用的sql server 2008 R2。按照一般步骤来:先建库,再建表,然后设计表,注意设置字段的约束,比如是否可以为null、是否为主键、是否自增、是否需要联合主键等等。

服务端

首先vs创建项目:

用vs新建项目

项目结构如下:

项目结构

连接数据库:
视图 -> 其它窗口 -> 服务器资源管理器 或者 视图 -> 服务器资源管理器

这里写图片描述

右键 -> 添加连接。我这里是连接服务器的数据库,所以使用SQL Server身份验证,服务器名填IP地址。如果连接本地数据库的话就使用Windows身份验证。测试一下是否可以连接,可以就点确定。

这里写图片描述

这里写图片描述

右键->属性,复制连接字符串,等会要用到。

这里写图片描述

在DBOperation中写操作契约和数据契约,操作契约就是你要调用的方法名,数据契约就是自定义的复杂数据类型,比如我这里的个人信息和测试数据。

namespace ADWebService_WCF
{
    [ServiceContract(Namespace = "http://xxxx.org/")]//自定义命名空间
    public interface DBOperation
    {

        [OperationContract]
        bool IsUserNameExist(string UserName);

        [OperationContract]
        bool IsPasswordMatch(string UserName, string password);

        [OperationContract]
        List<Info> GetAllInfo(string UserName);

        [OperationContract]
        List<TestData> GetAllTestData(string UserName);
        ...

    }

    [DataContract]
    public class Info
    {
        string UserName;
        string password;
        string UserId;
        string name;
        string sex;
        string age;
        string job;
        string edu;
        string num;

        [DataMember]
        public string Username
        {
            get { return UserName; }
            set { UserName = value; }
        }

        [DataMember]
        public string Password
        {
            get { return password; }
            set { password = value; }
        }
        ...
    }

在ADWebService.svc.cs中写操作的具体实现。

namespace ADWebService_WCF
{
    [ServiceBehavior(Namespace = "http://xxxx.org/")]//自定义命名空间时,这里也要加上
    public class ADWebService : DBOperation
    {
        public static SqlConnection sqlCon;

        private String ConServerStr = @"Data Source=115.28.xx.xxx;Initial Catalog=AD;Persist Security Info=True;User ID=sa;Password=******";//刚才复制的连接字符串,记得把******改成真正的密码。

        //打开数据库
        private void OpenDB()
        {
            sqlCon = new SqlConnection();
            sqlCon.ConnectionString = ConServerStr;
            sqlCon.Open();
        }

        //关闭数据库
        private void CloseDB()
        {
            sqlCon.Close();
        }

        public bool IsUserNameExist(string UserName)
        {
            ...           
        }

        public bool IsPasswordMatch(string UserName, string password)
        {
            ...
        }

        //原博文返回的是string数组,如下面两行所示。那样的话返回值的形式不好看,客户端也不好解析
        //操作的实现:public List<string> selectAllCargoInfor()
        //操作的接口定义:public string[] selectAllCargoInfor()
        public List<Info> GetAllInfo(string UserName)
        {
            List<Info> list = new List<Info>();

            try
            {
                OpenDB();
                string sql = "select * from InfoTable where UserName=" + UserName;//条件查询
                SqlCommand cmd = new SqlCommand(sql, sqlCon);
                SqlDataReader reader = cmd.ExecuteReader();

                while (reader.Read())
                {
                    Info info = new Info();
                    info.Username = reader[0].ToString();//reader[i]是数据表的第i列
                    info.Password = reader[1].ToString();
                    info.Userid = reader[2].ToString();
                    info.Name = reader[3].ToString();
                    info.Sex = reader[4].ToString();
                    info.Age = reader[5].ToString();
                    info.Job = reader[6].ToString();
                    info.Edu = reader[7].ToString();
                    info.Num = reader[8].ToString();
                    list.Add(info);
                }

                reader.Close();
                cmd.Dispose();
                CloseDB();
            }
            catch (Exception e)
            {
                throw e;
            }

            return list;
        }

        public List<TestData> GetAllTestData(string UserName)
        {
            ...
        }

        public bool InsertInfo(string UserName, string password, string UserId, string name, string sex, string age, string job, string edu, string num)
        {
            try
            {
                OpenDB();
                string sql = "insert into InfoTable(UserName,password,UserId,name,sex,age,job,edu,num) values('" + UserName + "','" + password + "','" + UserId + "','" + name + "','" + sex + "','" + age + "','" + job + "','" + edu + "','" + num + "')";//插入的例子。注意:sql语句很容易出错,但是千万不能错。如果不是string型的数据,就不要单引号。
                SqlCommand cmd = new SqlCommand(sql, sqlCon);
                cmd.ExecuteNonQuery();
                cmd.Dispose();
                CloseDB();
                return true;
            }
            catch (Exception e)
            {
                return false;
                throw e;                
            }
        }

        public bool UpdateInfo(string UserName, string UserId, string name, string sex, string age, string job, string edu, string num)
        {
            try
            {
                OpenDB();
                string sql = "update InfoTable set name='" + name + "',sex='" + sex + "',age='" + age + "',job='" + job + "',edu='" + edu + "',num='" + num + "' where UserName=" + UserName + " and UserId=" + UserId;
                //更新的例子。注意两点:
                //1.set后面的string型数据有单引号,而where后面的string型数据没有!
                //2.where和and前面有空格!
                SqlCommand cmd = new SqlCommand(sql, sqlCon);
                cmd.ExecuteNonQuery();
                cmd.Dispose();
                CloseDB();
                return true;
            }
            catch (Exception e)
            {
                return false;
                throw e;
            }
        }

        public bool InsertTestData(string UserName, string UserId, string TestNum, string time, string score, string possibility)
        {
            ...
        }

        //“增删查改”就差删除了,这里补上删除的sql语句:string sql = "delete from 表名 where 字段名=" + 相应的值;  

    }

}

调试结果:
在返回值的xml中,我们可以看到:当返回复杂数据时,数据是按照首字母排列的,并不是按照类中定义的顺序,也不是按照数据表中的顺序,在客户端解析的时候要注意。

这里写图片描述

这里写图片描述

发布:
用IIS发布之前首先需要在Windows中打开这个功能并配置好。详细请百度,这里不赘述了。

这里写图片描述

目标位置:随便填一个路径。

这里写图片描述

发布成功以后,在该路径下有三个东西,如图所示。如果要发布到服务器上,需要将这三个东西拷贝过去。

这里写图片描述

打开IIS,网站 -> 右键->添加网站

这里写图片描述

网站名称:随意;
应用程序池:名称和网站名称一样就行,等会在应用程序池中修改;
物理路径:就是那三个东西在哪儿;
IP地址:填写本地的IP(本地发布)或者填写服务器的IP(在服务器上发布)<啥?!你不知道怎么查看电脑的ip?竟然和我一样是电脑白痴,运行 -> cmd -> ipconfig,ipv4的地址就是>;
端口:填8000以上,一定确保没有被占用,我在服务器上发布的时候填的13000以上;

这里写图片描述

  1. 应用程序池 -> 刚才创建的应用程序池 -> 右键 -> 基本设置,根据你创建wcf时基于的.net版本修改.NET CLR版本,一般先用4.0,不行改为2.0;
  2. 目录浏览 -> 启用;
  3. 浏览网站测试一下,不报错就可以了,如果报错就复制粘贴错误原因自行百度解决;

这里写图片描述

在服务器上发布完以后可能还有两个问题:
1. 用其他PC无法访问,解决:防火墙 -> 允许程序通过Windows防火墙 -> 例外 -> 添加端口 -> 添加发布网站的端口号;
2. 用其他PC访问时提示:只接受本地计算机的请求,解决:修改配置文件,在Web.config(就是那三个东西里面的一个)中“system.web”之间加上下面这段代码。(不过貌似webservice需要加,wcf不需要)

<webServices>
    <protocols>
        <add name="HttpSoap"/>
        <add name="HttpPost"/>
        <add name="HttpGet"/>
        <add name="Documentation"/>
    </protocols>
</webServices>

这里写图片描述

到这里应该没问题了,其他问题我也没遇到过,自己发挥聪明才智解决吧。

客户端

       最后说一下android客户端如何调用wcf或者webservice。首先感谢CSDN大神柳峰,我就是参考他的文章才弄出来的,原博文链接:http://blog.csdn.net/lyq8479/article/details/6428288/
1. 下载ksoap2-android的jar包,链接: http://pan.baidu.com/s/1o63TmIm 密码: 5n2m。
2. 创建eclipse工程(或者android studio),将jar包复制到工程中libs文件夹下就行了,应该不用配置。
3. DBUtil

package com.lmz.testsql;

import java.util.ArrayList;  

public class DBUtil { 

    private ArrayList<String> arrayList = new ArrayList<String>();  
    private ArrayList<String> brrayList = new ArrayList<String>();

    private GetWebService gws = new GetWebService();

    public boolean IsUserNameExist(String UserName) {  

        arrayList.clear();  
        brrayList.clear();

        arrayList.add("UserName");         
        brrayList.add(UserName);  

        return gws.GetWebService1("IsUserNameExist", arrayList, brrayList);
    }

    public boolean IsPasswordMatch(String UserName, String password) {  
        ...
    }

      //本来想将webservice返回的SoapObject传递给主函数,发现根本行不通
      //原因暂且不明,求知道的大神告诉我。我猜想可能是数据量太大
      //解决办法:只能在需要解析SoapObject的地方直接调用WebService
//    public SoapObject GetAllInfo(String UserName) {  
//        
//        arrayList.clear();  
//        brrayList.clear();
//
//        arrayList.add("UserName");         
//        brrayList.add(UserName);
//
//      return gws.GetWebService2("GetAllInfo", arrayList, brrayList);
//    }
//    
//    public SoapObject GetAllTestData(String UserName) {  
//        ...
//    }

    public boolean InsertInfo(String UserName, String password, String UserId, String name, String sex, String age, String job, String edu, String num) {  

        arrayList.clear();  
        brrayList.clear();

        arrayList.add("UserName");
        arrayList.add("password");
        arrayList.add("UserId");
        arrayList.add("name");
        arrayList.add("sex");
        arrayList.add("age");
        arrayList.add("job");
        arrayList.add("edu");
        arrayList.add("num");

        brrayList.add(UserName);
        brrayList.add(password);
        brrayList.add(UserId);
        brrayList.add(name);
        brrayList.add(sex);
        brrayList.add(age);
        brrayList.add(job);
        brrayList.add(edu);
        brrayList.add(num);

        return gws.GetWebService1("InsertInfo", arrayList, brrayList);
    }

    public boolean UpdateInfo(String UserName, String UserId, String name, String sex, String age, String job, String edu, String num) {  
        ...
    }

    public boolean InsertTestData(String UserName, String UserId, String TestNum, String time, String score, String possibility) {  
        ...
    }

}

4 . GetWebService
首先查看WSDL,下面两幅图是wcf的,webservice的基本一样。

这里写图片描述

这里写图片描述

package com.lmz.testsql;

import java.io.IOException;
import java.util.ArrayList;

import org.ksoap2.SoapEnvelope;
import org.ksoap2.serialization.SoapObject;
import org.ksoap2.serialization.SoapSerializationEnvelope;
import org.ksoap2.transport.HttpTransportSE;
import org.xmlpull.v1.XmlPullParserException;

public class GetWebService {

    private String result;

    //Except method "getAllInfo" and "getAllTestData" 
    public boolean GetWebService1(String methodName, ArrayList<String> Parameters, ArrayList<String> ParValues) {

        //wcf和webservice这三个参数不一样,不过区别不大
        //nameSpace一样
        //endPoint:wcf的后缀是.svc;webservice的后缀是.asmx
        //soapAction:webservice直接是nameSpace+methodName

        //命名空间
        String nameSpace = "http://Connect2Server.org/";
        //EndPoint
        String endPoint = "http://115.28.xxx.xxx:13258/ADWebService.svc";
        //SOAP Action
        final String soapAction = "http://Connect2Server.org/DBOperation/" + methodName;

        final HttpTransportSE transport = new HttpTransportSE(endPoint);
        transport.debug = true;

        //指定WebService的命名空间和调用的方法名  
        SoapObject rpc = new SoapObject(nameSpace, methodName);

        for (int i = 0; i<Parameters.size(); i++){
            rpc.addProperty(Parameters.get(i),ParValues.get(i));
        }

        //生成调用WebService方法的SOAP请求信息,并指定SOAP的版本(10、11或者12)  
        final SoapSerializationEnvelope envelope = new SoapSerializationEnvelope(SoapEnvelope.VER11);

        envelope.bodyOut = transport;
        //设置是否调用的是.Net开发的WebService
        envelope.dotNet = true;

        envelope.setOutputSoapObject(rpc);

        //android 4.0以后的版本在主程中不允许联网
        Thread thread = new Thread(){

            public void run(){

                try {
                    //调用WebService
                    transport.call(soapAction, envelope);
                    SoapObject object = (SoapObject) envelope.bodyIn;//获取返回值,并封装成SoapObject
                    result = object.getProperty(0).toString();                  
                }
                catch (IOException e) {
                    e.printStackTrace();
                }
                catch (XmlPullParserException e) {
                    e.printStackTrace();
                }
                catch (Exception ex) {
                    ex.printStackTrace();
                }

            }

        };

        thread.start();
        try {
            thread.join();//等待该子程结束
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        if(result.equals("true")){
            return true;
        }
        else{
            return false;
        }

    }

5 . MainActivity
其他的代码我就不贴了,我只贴一下如何解析返回的SoapObject

    //For method "getAllInfo" and "getAllTestData" 
    public void GetAllData(String methodName, String UserName) {

        //命名空间
        String nameSpace = "http://Connect2Server.org/";
        //EndPoint
        String endPoint = "http://115.28.xxx.xxx:13258/ADWebService.svc";
        //SOAP Action
        final String soapAction = "http://Connect2Server.org/DBOperation/" + methodName;

        final HttpTransportSE transport = new HttpTransportSE(endPoint);
        transport.debug = true;

        //指定WebService的命名空间和调用的方法名  
        SoapObject rpc = new SoapObject(nameSpace, methodName);

        rpc.addProperty("UserName", UserName);

        //生成调用WebService方法的SOAP请求信息,并指定SOAP的版本  
        final SoapSerializationEnvelope envelope = new SoapSerializationEnvelope(SoapEnvelope.VER11);

        envelope.bodyOut = transport;
        //设置是否调用的是.Net开发的WebService
        envelope.dotNet = true;

        envelope.setOutputSoapObject(rpc);

        Thread thread = new Thread(){

            public void run(){

                try {
                    //调用WebService
                    transport.call(soapAction, envelope);
                    SoapObject so = (SoapObject)envelope.bodyIn;
                    SoapObject soapobject = (SoapObject)(so.getProperty(0));

                    //注意顺序
                    String UserName,password,UserId,name,sex,age,job,edu,num;
                    for(int i = 0; i<soapobject.getPropertyCount(); i++) {
                        SoapObject info = (SoapObject)soapobject.getProperty(i);
                        age = info.getProperty(0).toString();
                        edu = info.getProperty(1).toString();
                        job = info.getProperty(2).toString();
                        name= info.getProperty(3).toString();
                        num = info.getProperty(4).toString();
                        password = info.getProperty(5).toString();
                        sex= info.getProperty(6).toString();
                        UserId = info.getProperty(7).toString();
                        UserName = info.getProperty(8).toString();

                        //存入本地数据库
                        db.execSQL("insert into InfoTable(UserName,password,UserId,name,sex,age,edu,job,num) values(?,?,?,?,?,?,?,?,?)",
                                new Object[]{UserName,password,Integer.parseInt(UserId),name,sex,age,edu,job,num});                     
                    }

                }
                catch (IOException e) {
                    e.printStackTrace();
                }
                catch (XmlPullParserException e) {
                    e.printStackTrace();
                }
                catch (Exception ex) {
                    ex.printStackTrace();
                }

                db.close();
            }

        };

        thread.start();
        try {
            thread.join();
        } catch (InterruptedException e) {  
            e.printStackTrace();
        }

    }

6 . 最后,千万记得在AndroidManifest.xml中添加网络权限!添加网络权限!添加网络权限!(重要的事情说三遍)

这里写图片描述

结束语

       第一次写博客,第一次写技术博客,好紧张,可能有疏漏或者遗忘的地方,望各位同行指出错误,批评指正,共同讨论,一起进步!最后,感谢你的阅读和分享!

MySQL数据库从入门实战课

12-31
限时福利1:购课进答疑群专享柳峰(刘运强)老师答疑服务。 限时福利2:购课后添加学习助手(微信号:csdn590),按消息提示即可领取编程大礼包! 注意:原价129的课程,最后2天限时秒杀仅需49元!! 为什么说每一个程序员都应该学习MySQL? 根据《2019-2020年中国开发者调查报告》显示,超83%的开发者都在使用MySQL数据库。 使用量大同时,掌握MySQL早已是运维、DBA的必备技能,甚至部分IT开发岗位也要求对数据库使用和原理有深入的了解和掌握。 学习编程,你可能会犹豫选择 C++ 还是 Java;入门数据科学,你可能会纠结于选择 Python 还是 R;但无论如何, MySQL 都是 IT 从业人员不可或缺的技能! 【课程设计】 在本课程中,刘运强老师会结合自己十多年来对MySQL的心得体会,通过课程给你分享一条高效的MySQL入门捷径,让学员少走弯路,彻底搞懂MySQL。 本课程包含3大模块:  一、基础篇: 主要以最新的MySQL8.0安装为例帮助学员解决安装与配置MySQL的问题,并对MySQL8.0的新特性做一定介绍,为后续的课程展开做好环境部署。 二、SQL语言篇: 本篇主要讲解SQL语言的四大部分数据查询语言DQL,数据操纵语言DML,数据定义语言DDL,数据控制语言DCL,学会熟练对库表进行增删改查等必备技能。 三、MySQL进阶篇: 本篇可以帮助学员更加高效的管理线上的MySQL数据库;具备MySQL的日常运维能力,语句调优、备份恢复等思路。  
©️2020 CSDN 皮肤主题: 大白 设计师: CSDN官方博客 返回首页
实付0元
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值