最近在做一些Android的程序,开发一个我们一个系统的手持端。虽说在2007年Android平台刚刚推出的时候我就做过一些上面的开发,但是现在已经是2.3最新版本了,Android还是有大量的变化的,因此跟从头学起的差别不是很大。我并不想在这里教会别人怎么去开发,教程网上太多了,只是对于我用到的东西做一点总结,概略而不缺失难点的细节。
- 基本开发方法
Android开发一般使用的环境是Eclipse,安装Android的SDK和ADT插件,值得注意的是我在安装的过程中出想过只安装了2.2版本的SDK,于是开发只能使用这一版本的问题,希望朋友注意,最好能够安装一个比较全的版本。
新建项目之后,会有项目的目录结构,AndroidManifest.xml文件是整个软件的配置文件,在这其中,一般需要配置的是程序需要的权限,语法格式如下:
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.RESTART_PACKAGES"/>
你生成的apk文件在安装时会给用户提示是否授予这些权限,如果不在这个文件里说明,使用到诸如网络通讯、访问通讯录等功能时就会报错。
activity也配置在这其中,比如:
<activity android:name=".MainActivity"
android:label="@string/main_title">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
可是设置Activity的标题,对应的类名等属性,新添加一个Activity就需要这里加入这个Activity的说明。
然后,我们可以在res文件夹下的values文件夹下的strings.xml文件中定义我们自己想要的常量,在layout文件夹下定义界面的布局,就可以进行具体代码的开发工作了。 - Android上的数据存储
Android上有多种数据存储方式,包括:
a) 使用SharedPreferences。这种方式以NVP(键值对)的形式存储,而最终会存储到你定义的一个XML文件中,具体的方法可以搜索下相关资料,非常简单
b) 文件存储数据。Android的文件存储跟普通JAVA的文件存储一样,使用流的形式。
c) SQLite数据库存储数据,类似于数据库表形式的存储方法,非常方便,因为精简,所以比普通的SQL还要舒服些。主要通过继承自SQLiteOpenHelper的一个类来实现创建删除数据库的操作,对应的增删改查在SQLiteDatabase中都有对应的接口,通过SQLiteOpenHelper子类的getReadable/WritableDatabase()方法获取。有趣的是除非空间已满和指定只读外,这两个方法获取的操作对象是一样的,而且我感觉这个方法获取的是缓冲池形式的操作对象,比如:
SQLiteDatabase a = getReadableDatabase();
SQLiteDatabase b = getReadableDatabase();
b.close();
a.query(...) //此处操作a可能会报已关闭的错误。
具体是否是这样的我没有仔细探究,我是在系统中维护另一个单例的SQLiteDatabase对象出现了类似的错误,所以推测内部实现是如此的。
d) 使用ContentProvider存储数据,一般用于程序之间共享数据。
e) 网络存储数据。
此处需要额外说明的是我们在程序中通常会使用配置页面,Android提供了一个非常简单的实现方式,继承自PreferenceActivity的类可以用于设置参数界面,其配置文件为"PreferenceScreen"标签。这部分的参数存储和现实都不需要手动控制,会自动使用SharedPreferences进行存储和修改,界面也不用编写,一个简单的示例如下所示:
1 <?xml version="1.0" encoding="utf-8"?>
2 <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
3 <PreferenceCategory android:title="自动操作设置">
4 <CheckBoxPreference
5 android:key="AutoDelBeforeDownload"
6 android:title="下载时自动清空原始记录"
7 android:defaultValue="false"
8 />
9 <CheckBoxPreference
10 android:key="AutoDelAfterUpload"
11 android:title="上传时自动删除成功记录"
12 android:defaultValue="false"
13 />
14 </PreferenceCategory>
15 <PreferenceCategory android:title="远程服务地址设置">
16 <EditTextPreference
17 android:key="ServerIP"
18 android:title="设置远程服务IP地址"
19 android:summary="点击输入"
20 android:dialogTitle="远程服务IP地址设置"
21 android:defaultValue="192.168.30.223"
22 />
23 <EditTextPreference
24 android:key="ServerPort"
25 android:title="设置远程服务端口"
26 android:summary="点击输入"
27 android:dialogTitle="远程服务地址设置"
28 android:defaultValue="8080"
29 />
30 </PreferenceCategory>
31 </PreferenceScreen>在界面类中的onCreate()函数中添加如下代码即可:
addPreferencesFromResource(R.xml.preferences);
如果需要,可以重载onKeyDown()方法来在界面退出的时候设置系统环境变量,代码示例如下:1 @Override
2 public boolean onKeyDown(int keyCode, KeyEvent event){
3 if(keyCode == KeyEvent.KEYCODE_BACK){
4 ConfigProcessor.getContextConfig(this);
5 return super.onKeyDown(keyCode, event);
6 }else{
7 return super.onKeyDown(keyCode, event);
8 }
9 } - KSOAP2的使用
由于我所做的程序是依托现有系统实现了,因此一些数据要从现有的系统中去读取并返回一些数据,经过一些比较,选择了Web Service作为通讯工具。Android上对于Web Service的支持也比较好,KSOAP2是一个常用的包。当然,开发时要将ksoap2-android-assembly-2.4-...这个jar包引入到系统中。在使用的时候比较简单,代码示例如下:
try {
SoapObject rpc = new SoapObject(MobileConstant.NAMESPACE,
MobileConstant.LOGIN_METHORT_NAME); //设置namespace和方法名称
rpc.addProperty("arg0", lName); //设置参数
rpc.addProperty("arg1", pass);
SoapSerializationEnvelope envelope = new SoapSerializationEnvelope(
SoapEnvelope.VER11);
AndroidHttpTransport ht = new AndroidHttpTransport(MobileContext
.getInstance().getService_URL()); //wsdl的url
envelope.bodyOut = rpc;
envelope.setOutputSoapObject(rpc);
ht.call(null, envelope);
SoapObject result = (SoapObject) envelope.bodyIn;
int cnt = result.getPropertyCount();
if (cnt > 0) { //服务的返回值是一个数组,第一个元素是oid,第二个是用户的显示名
String userOID = result.getProperty(0).toString();
String userShowName = result.getProperty(1).toString();
// 保存必须的上下文信息。
MobileContext.getInstance().setLoginName(lName);
MobileContext.getInstance().setUserOID(userOID);
MobileContext.getInstance().setUserShowName(userShowName);
// 保存用户名密码到文件
SharedPreferences preferences = PreferenceManager
.getDefaultSharedPreferences(this);
SharedPreferences.Editor editor = preferences.edit();
if (cb_isSave.isChecked()) {
editor.putString(
getResources().getString(R.string.saved_name),
lName);
editor.putString(
getResources().getString(R.string.saved_password),
pass);
} else {
editor.putString(
getResources().getString(R.string.saved_name), "");
editor.putString(
getResources().getString(R.string.saved_password),
"");
}
editor.commit();
return true;
} else {
new AlertDialog.Builder(this)
.setMessage("用户名或密码错误!")
.setTitle("错误")
.setNeutralButton("关闭",
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dlg,
int sumthin) {
}
}).show();
return false;
}
} catch (Exception e) {
new AlertDialog.Builder(this)
.setMessage("远程服务请求失败,请检查网络连接或联系系统管理员!")
.setTitle("错误")
.setNeutralButton("关闭",
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dlg,
int sumthin) {
}
}).show();
}
此处遇到的还未解决的问题包括向服务器提交String[]形式的参数报错的问题,和如果服务不存在超时的问题。前者我不知道是否有办法解决,现在是用一个间隔符划分的字符串代替了数组的传输,后者应该加上一个多线程的进度条来实现比较好。如果有谁解决过这方面的问题,还请指教~ - 二维条形码的读取
在设备上分配了一个唯一的编号,然后生成一个二维条形码,贴在机器上。这样,要求我们的手持程序可以解析二维条形码,读出其中的信息,Android上解读条形码的应用非常的简单,只需要同时安装一个BarcodeScanner.apk软件,在我们的程序里进行调用就可以了,调用代码如下:
private void doScanner() { final boolean scanAvailable = MobileTools.isIntentAvailable(this, "com.google.zxing.client.android.SCAN"); if (scanAvailable == false) { new AlertDialog.Builder(this) .setMessage("请先安装BarcodeScanner组件!") .setTitle("提示") .setNeutralButton("关闭", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dlg, int sumthin) { } }).show(); return; } Intent scannerIntent = new Intent( "com.google.zxing.client.android.SCAN"); startActivityForResult(scannerIntent, 100); } public void onActivityResult(int requestCode, int resultCode, Intent intent) { if (requestCode == 100) { String resultStr = "读取失败"; if (resultCode == RESULT_OK) { String contents = intent.getStringExtra("SCAN_RESULT"); String format = intent.getStringExtra("SCAN_RESULT_FORMAT"); resultStr = contents + " " + format; } else if (resultCode == RESULT_CANCELED) { resultStr = "读取失败"; } new AlertDialog.Builder(this) .setMessage(resultStr) .setTitle("条形码读取结果") .setNeutralButton("关闭", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dlg, int sumthin) { } }).show(); } }
其中isIntentAvailable()函数用于验证某个需求的组件是否已经存在,其实现代码如下:
/** * Indicates whether the specified action can be used as an intent. This * method queries the package manager for installed packages that can * respond to an intent with the specified action. If no suitable package is * found, this method returns false. * * @param context The application's environment. * @param action The Intent action to check for availability. * * @return True if an Intent with the specified action can be sent and * responded to, false otherwise. */ public static boolean isIntentAvailable(Context context, String action) { final PackageManager packageManager = context.getPackageManager(); final Intent intent = new Intent(action); List<ResolveInfo> list = packageManager.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY); return list.size() > 0; }