对于熟悉C#语言的开发者而言,用Xamarin开发Android应用也是一个不错的选择。小米盒子是Android系统,当然也就可以使用Xamarin来开发。首选来看效果图。
注:(1).左图是从数据库中拉取用户列表(图中的用户的虚拟的)
(2)中间图是根据选中的用户发起微信通知
(3)右图是微信企业号中收到的通知
一、在VS中建立Android应用
1.布局主界面
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent"> <LinearLayout android:orientation="horizontal" android:layout_width="match_parent" android:layout_height="wrap_content"> <Button android:id="@+id/btnLoadUser" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/LoadUser" /> <Button android:id="@+id/btnWeiXinNotify" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/WeiXinNotify" /> <Button android:id="@+id/btnClear" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/Clear" /> </LinearLayout> <ListView android:minWidth="25px" android:minHeight="25px" android:layout_width="match_parent" android:layout_height="wrap_content" android:id="@+id/listView1" /> </LinearLayout>
注:由于小米盒子使用的遥控器控制,不像手机是触摸屏的,所以界面中尽量以按钮、简易图表的形式展现,以方便控制。
2.主界面MainActivity的CS代码
using System; using Android.App; using Android.Content; using Android.Runtime; using Android.Views; using Android.Widget; using Android.OS; using System.Data; using System.Data.SqlClient; using System.Collections.Generic; using System.Text; namespace XiaoMiBoxDemo { [Activity(Label = "小米盒子应用示例", MainLauncher = true, Icon = "@drawable/icon")] public class MainActivity : Activity { private Button _btnLoadUser = null; private Button _btnWeiXinNotify = null; private Button _btnClear = null; private ListView _listView = null; private List<UserInfo> _userList = null; protected override void OnCreate(Bundle bundle) { base.OnCreate(bundle); SetContentView(Resource.Layout.Main); FetchControls(); BindEvents(); } private void BindEvents() { _btnLoadUser.Click += _btnLoadUser_Click; _listView.ItemClick += _listView_ItemClick; _btnWeiXinNotify.Click += _btnWeiXinNotify_Click; _btnClear.Click += _btnClear_Click; } void _btnClear_Click(object sender, EventArgs e) { _listView.Adapter = null; _userList.Clear(); } void _btnWeiXinNotify_Click(object sender, EventArgs e) { if (_userList == null || _userList.Count <= 0) { Toast.MakeText(this, "请选择要通知的用户", ToastLength.Long).Show(); return; } String qyNoIds = ""; bool hasSelectedUser = false; foreach (UserInfo _userInfo in _userList) { if (!_userInfo.IsSelected) { continue; } hasSelectedUser = true; if (String.IsNullOrWhiteSpace(_userInfo.QYNo)) { continue; } if (String.IsNullOrWhiteSpace(qyNoIds)) { qyNoIds = _userInfo.QYNo; } else { qyNoIds += "|" + _userInfo.QYNo; } } if (String.IsNullOrWhiteSpace(qyNoIds)) { if (hasSelectedUser) { Toast.MakeText(this, "所选择的用户没有绑定企业号", ToastLength.Long).Show(); } else { Toast.MakeText(this, "请选择要通知的用户", ToastLength.Long).Show(); } return; } String returnErrorMsg = ""; EditText editText = new EditText(this); AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.SetTitle("通知信息"); builder.SetView(editText); EventHandler<DialogClickEventArgs> okClick = delegate { String message = editText.Text; if (WeiXinHelper.SendContent(out returnErrorMsg, qyNoIds, message)) { returnErrorMsg = "发送成功"; } Toast.MakeText(this, returnErrorMsg, ToastLength.Long).Show(); }; builder.SetNegativeButton("取消", delegate { }); builder.SetPositiveButton("确定", okClick); builder.Show(); } void _listView_ItemClick(object sender, AdapterView.ItemClickEventArgs e) { UserInfo userInfo = _userList[e.Position]; userInfo.IsSelected = (!userInfo.IsSelected); String guid = String.Format("{0}", e.View.Tag); if (guid.Equals(userInfo.Guid)) { e.View.FindViewById<CheckBox>(Resource.Id.checkBoxUser).Checked = userInfo.IsSelected; } } private void FetchControls() { _btnLoadUser = FindViewById<Button>(Resource.Id.btnLoadUser); _btnWeiXinNotify = FindViewById<Button>(Resource.Id.btnWeiXinNotify); _btnClear = FindViewById<Button>(Resource.Id.btnClear); _listView = FindViewById<ListView>(Resource.Id.listView1); } void _btnLoadUser_Click(object sender, EventArgs e) { try { String sql = " SELECT * FROM TUser "; DataTable dt = DBUtil.Query(sql).Tables[0]; String account = ""; String nickname = ""; String qyNo = ""; String guid = ""; _userList = new List<UserInfo>(); foreach (DataRow row in dt.Rows) { guid = String.Format("{0}", row["u_guid"]); account = String.Format("{0}", row["u_account"]); nickname = String.Format("{0}", row["u_nickname"]); qyNo = String.Format("{0}", row["u_qyNo"]); _userList.Add(new UserInfo() { Guid = guid, Account = account, Nickname = nickname, QYNo = qyNo, IsSelected = false }); } _listView.Adapter = new UserListAdapter(this, _userList); } catch (Exception ex) { Toast.MakeText(this, ex.Message, ToastLength.Long).Show(); } } } }
注:(1)使用自定义的DBUtil类来查询数据库。
(2)_listView使用自定义的UserListAdapter来绑定数据。
(3)给相应的按钮和_listView的项添加点击事件
(3)需要特别注意的是MainActivity定义的上一行有 [Activity(Label = "小米盒子应用示例", MainLauncher = true, Icon = "@drawable/icon")]的代码,这里的Label将会影响到最终应用显示的名称(包括应用列表和应用启动后的),而icon在更换了之后,除了在项目属性中调整之外,这里也要一并调整,否则会编译不过。
3.数据库工具类DBUtil
using System; using System.Collections.Generic; using System.Data; using System.Data.SqlClient; using System.Text; namespace XiaoMiBoxDemo { public class DBUtil { private static String ConnectionString = "server=数据库服务器;database=DBDemo;uid=sa;pwd=abc;pooling=false;Connect Timeout=120;"; /// <summary> /// 执行查询语句,返回DataSet /// </summary> /// <param name="SQLString">查询语句</param> /// <returns>DataSet</returns> public static DataSet Query(string SQLString) { using (SqlConnection connection = new SqlConnection(ConnectionString)) { DataSet ds = new DataSet(); try { connection.Open(); SqlDataAdapter command = new SqlDataAdapter(SQLString, connection); command.Fill(ds, "ds"); } catch (System.Data.SqlClient.SqlException ex) { throw new Exception(ex.Message); } return ds; } } } }注:(1).数据库服务器,需要换成相应的值。比如192.168.1.101或www.xxx.com:8090等
(2).C#下的Android连接数据库时,直接使用.Net的数据库连接方式,不必像eclipse下那么麻烦。不过需要注意,记得要引用相应的DLL(System.Data).该DLL一定要引用Android下的,而不.Net下的,这是我的Android的System.Data.dll路径C:Program Files (x86)Reference AssembliesMicrosoftFrameworkMonoAndroidv1.0System.Data.dll。
(3).记得设置Android应用的编码,否则在执行SQL时可能会出现"Code page 936 not support"。这是因为我们开发的应用的字符集和数据库服务器的字符集不一致造成的。
从图上可以看到SQL服务器使用的Chinese_PRC_CI_AS的排序规则,这是GBK的字符集,所以我们的应用也需需有这样的字符集支持,为此需要给应用加一个配置,见下图。项目->右键->属性,然后按下图配置。
注:CJK表示的中日韩的字符集。
4.列表适配器UserListAdapter
using System; using System.Collections.Generic; using System.Linq; using System.Text; using Android.App; using Android.Views; using Android.Widget; namespace XiaoMiBoxDemo { public class UserListAdapter : BaseAdapter<UserInfo> { /// <summary> /// 所有UserInof 的数据 /// </summary> List<UserInfo> items; Activity context; public UserListAdapter(Activity context, List<UserInfo> items) : base() { this.context = context; this.items = items; } public override long GetItemId(int position) { return position; } public override UserInfo this[int position] { get { return items[position]; } } public override int Count { get { return items.Count; } } /// <summary> /// 系统会呼叫 并且render. /// </summary> /// <param name="position"></param> /// <param name="convertView"></param> /// <param name="parent"></param> /// <returns></returns> public override View GetView(int position, View convertView, ViewGroup parent) { var item = items[position]; var view = convertView; if (view == null) { //使用自订的UserListItemLayout view = context.LayoutInflater.Inflate(Resource.Layout.UserListItemLayout, null); } view.FindViewById<TextView>(Resource.Id.textViewAccount).Text = item.Account; view.FindViewById<TextView>(Resource.Id.textViewNickname).Text = item.Nickname; view.FindViewById<TextView>(Resource.Id.textViewQYNo).Text = item.QYNo; view.FindViewById<CheckBox>(Resource.Id.checkBoxUser).Checked = item.IsSelected; view.Tag = item.Guid; return view; } } }
其中UserInfo的代码如下
using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Text; namespace XiaoMiBoxDemo { public class UserInfo { /// <summary> /// Guid /// </summary> public String Guid { get; set; } /// <summary> /// 帐号 /// </summary> public String Account { get; set; } /// <summary> /// 昵称 /// </summary> public String Nickname { get; set; } /// <summary> /// 企业号 /// </summary> public String QYNo { get; set; } /// <summary> /// 是否选中 /// </summary> [DefaultValue(false)] public bool IsSelected { get; set; } } }
下面是每一个Item的布局代码
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="horizontal" android:layout_width="fill_parent" android:layout_height="wrap_content" android:paddingLeft="10px"> <CheckBox android:text="" android:layout_width="wrap_content" android:layout_height="wrap_content" android:focusable="false" android:id="@+id/checkBoxUser" /> <LinearLayout android:orientation="vertical" android:layout_width="wrap_content" android:layout_height="wrap_content"> <LinearLayout android:orientation="horizontal" android:layout_width="fill_parent" android:layout_height="wrap_content"> <TextView android:text="@string/Account" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <TextView android:text="Text" android:layout_width="match_parent" android:layout_height="wrap_content" android:id="@+id/textViewAccount" /> </LinearLayout> <LinearLayout android:orientation="horizontal" android:layout_width="fill_parent" android:layout_height="wrap_content"> <TextView android:text="@string/Nickname" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <TextView android:text="Text" android:layout_width="match_parent" android:layout_height="wrap_content" android:id="@+id/textViewNickname" /> </LinearLayout> <LinearLayout android:orientation="horizontal" android:layout_width="fill_parent" android:layout_height="wrap_content"> <TextView android:text="@string/QYNo" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <TextView android:text="Text" android:layout_width="match_parent" android:layout_height="wrap_content" android:id="@+id/textViewQYNo" /> </LinearLayout> </LinearLayout> </LinearLayout>5.字符串变量Strings.xml
<?xml version="1.0" encoding="utf-8"?> <resources> <string name="LoadUser">用户列表</string> <string name="WeiXinNotify">微信通知</string> <string name="Clear">清空列表</string> <string name="ApplicationName">小米盒子应用示例</string> <string name="Account">帐号:</string> <string name="Nickname">昵称:</string> <string name="QYNo">企业号:</string> </resources>
二.WCF实现微信通知
1.原理:在实现了微信企业号的WCF服务后,在Android端以Web服务调用的形式来发起通知。
2.引用WCF
在WinForm或者Asp.Net下调用WCF,只需要直接添加服务引用就可以了。在Xamarin下也是如果,所不同的是,这里添加的是Web引用,见下图。
这时在我们的项目中,会多一个Web References的文件夹,下面会有一个WeiXinService,见下图。
3.调用WCF
为了方便以后调用,这里将调用封装到WeiXinHelper类中,代码如下
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace XiaoMiBoxDemo { #region WeiXinMessageType /// <summary> /// 微信的消息类型 /// </summary> public sealed class WeiXinMessageType { public const string TEXT = "text"; } #endregion #region WeiXinMessageEncription /// <summary> /// 微信的消息加密开关 /// </summary> public sealed class WeiXinMessageEncription { /// <summary> /// 不需要加密 /// </summary> public const string NOT = "0"; /// <summary> /// 需要加密 /// </summary> public const string NEED = "1"; } #endregion public class WeiXinHelper { /// <summary> /// 发送文本消息给微信用户 /// </summary> /// <param name="returnErrorMsg">当返回值为false时,返回的错误信息.</param> /// <param name="qyNoIds">用户的企业号的ID列表(多个用户之间用‘|’分隔)</param> /// <param name="message">发送的消息</param> /// <returns></returns> public static bool SendContent(out String returnErrorMsg, String qyNoIds, String message) { if (String.IsNullOrWhiteSpace(qyNoIds)) { returnErrorMsg = "缺少微信用户"; return false; } WeiXinService.MainService mainService = new WeiXinService.MainService(); bool isSendMsgResult = false; bool isSendMsgResultSpecified = false; mainService.SendMsg(qyNoIds, null, null, WeiXinMessageType.TEXT, message, WeiXinMessageEncription.NOT, out isSendMsgResult, out isSendMsgResultSpecified, out returnErrorMsg); if isSendMsgResult&& isSendMsgResultSpecified, { return true; } else { return false; } } } }注:
(1).这里调用WCF时不像Asp.Net或者WinForm下,使用Client来调用。而是要使用MainService实例来调用。为什么使用MainService呢?因为WCF的微信企业号服务主类就是MainService.
(2).MainService中的SendMsg方法,原来有一个bool的返回值,但是这里不见了。其实不是不见了,而是以out的形式输出了。即isSendMsgResult和isSendMsgResultSpecified.其中isSendMsgResultSpecified是引用时自动带入的参数,当isSendMsgResult和isSendMsgResultSpecified都为true时,表示成功。
三.生成应用
1.在生成应用之前,一定记得设置应用的api级别,并配置所需要的相关权限,具体见下面的图。
注:如果没有配置Android的API级别,将会导致应用安装时程序包解析失败而无法安装。
注:这里需要配置ACCESS_WIFI_STATE和INTERNAL的权限,如果没有配置将会导致数据库连接和微信通知时失败,提示connect refuse等字样。
2.一定记得设置成Release下生成应用,在Debug下生成的应用可能正常运行的,在Release下可能会失败,最常见的就是权限没有设置导致的。
3.应用可能需要配置合适的屏幕尺寸,否则在小米盒子中会无法显示,比如一片黑。
3.打包应用
转载请注明出处http://blog.csdn.net/xxdddail/article/details/46707481。