欢迎大家阅读我的初学者安卓笔记,学习安卓的一些基础知识有了很长一段时间,也是看了《第一行代码》这本书,从我看过的几本书中,截止目前我认为《第一行代码》是一本最适合初学者学习安卓的书籍,这本书中不仅内容极为丰富,而且作者将知识表达的通俗易懂,再加上一些比较有意思的章节一小段话。我强烈建议想学习安卓的小伙伴买此书学习,由于最近写了这本书最后的天气项目,所以在这里写下关于天气项目的一些心得。
对于一个应用程序来说会有大量的数据的处理,那么数据是从哪里得到的那?分两种,一种是从数据库中,数据库中获取大家应该并不陌生,例如安卓本身自带的SQLite相信很多人在刚开始学习的时候都有用过。另一种则是从服务器中获取,本项目中用到了中国天气网提供的API接口实现的从服务器中获取的信息。
做一个项目我们当然首先要分析出我们这个应用都应该具备哪些功能,用什么样的技术去实现。
第一个功能:天气项目自然而然会涉及到我国的城市信息,也就是能够罗列出我国所有的省、市、县。技术方面我们通过下面这个网址www.weather.com.cn/data/list3/city.xml从服务器中获取城市信息,大家可以看一下进入该网址后服务器给我返回的信息.
这是我在调试程序下看到的从该网址返回的省份的信息。不难发现省份之间是由逗号隔开,同时每个省份前面都有其编号依次排列。如果想要知道当前省份所含的市级地区也不难,www.weather.com.cn/data/list3/city省级代号.xml 举例黑龙江省级代号是05,把网址中省级代号替代为05即可,下图是服务器返回的黑龙江所含市级地区。
由图中不难看出我们轻松的获取到了黑龙江省下所含市级城市,获取县级城市和上面获取市级城市的方法一样,即把省级代号那里换成市级代号就可以完成,下图是伊春市所含县级地区。
下面我们就看看怎么在代码中实现吧。
在这之前有一个非常重要的事情要提前说一下,我觉得项目中文件的结构非常重要,我们应该像把书放入书柜中一样,把一类的书放到一个书柜的位置中,项目中就是把不同作用的文件放到不同的包中,下图的结构是一个比较不错的文件结构。
activity不用多说自然是我们存放活动代码的地方
db是数据库(Date base)的缩写自然存放数据库相关代码
model我用于存放表的代码
util(工具)自然是将工具相关的代码(网络工具等)
正式步入正题
我们在使用应用时,不难发现关闭网络后在上网时显示的内容依然可以显示,这是因为应用中将从服务器获取的数据存储到了本地,所以我们先创建一个数据库,在这里我们直接用安卓自带的数据库SQLite。
我们在db包中先创建CoolWeatherOpenHelper类(用于建表),继承SQLiteOpenHelper类,继承和扩展SQLiteOpenHelper类主要做的工作就是重写以下两个方法。
onCreate(SQLiteDatabase db) : 当数据库被首次创建时执行该方法,一般将创建表等初始化操作在该方法中执行。
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(CREATE_PROVINCE);//创建Province表
db.execSQL(CREATE_CITY);//创建City表
db.execSQL(CREATE_COUNTY);//创建County表
}
onUpgrade(SQLiteDatabse dv, int oldVersion,int new Version):当打开数据库时传入的版本号与当前的版本号不同时会调用该方法。
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
建三张表 分别是Province、City、County 作用是分别存放省、市、县的数据信息。建表的语句是SQL语句,很简单,大家如果不太了解可以上网百度一下,记住一些基本的语句就没有问题,下面是建表语句:
/*
Province表建表语句
*/
public static final String CREATE_PROVINCE="create table Province ("
+"id integer primary key autoincrement,"
+"province_name text,"
+"province_code text)";
/*
* City表建表语句
* */
public static final String CREATE_CITY="create table City ("
+"id integer primary key autoincrement,"
+"city_name text,"
+"city_code text,"
+"province_id integer)";
/*
* County表建表语句
* */
public static final String CREATE_COUNTY="create table County ("
+"id integer primary key autoincrement,"
+"county_name text,"
+"county_code text,"
+"city_id integer)";
CoolWeatherOpenHelper类的代码我们就完成了,我们有了建表语句,当然也需要定义一下这三个表,于是在model包中定义这三个类
类中的写法很简单在这里我就以City类举例,大家可以去下载源码看一下那两个类的写法。
public class City {
private int id;//数据库中存储的id
private String cityName;//市级名
private String cityCode;//市级代号
private int provinceId;//市级所属于的省级的id
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getCityName() {
return cityName;
}
public void setCityName(String cityName) {
this.cityName = cityName;
}
public String getCityCode() {
return cityCode;
}
public void setCityCode(String cityCode) {
this.cityCode = cityCode;
}
public int getProvinceId() {
return provinceId;
}
public void setProvinceId(int provinceId) {
this.provinceId = provinceId;
}
}
我们在数据库中建表的工作就完成了!表中都是get和set的方法,用于对表中的数据进行获取和修改,感谢朋友们的支持,继续往下写...
我们在db包中新建一个CoolWeatherDB类,此类用于操作数据库,大家思考一下这个类中需要什么方法,首先肯定需要将省、市、县的数据存储,随之而然还需要能够获取到他们,简而言之这个类中我们需要6个方法。看下面的代码,我会详细注释
package com.example.coolweather.db;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import com.example.coolweather.model.City;
import com.example.coolweather.model.County;
import com.example.coolweather.model.Province;
import java.util.ArrayList;
import java.util.List;
public class CoolWeatherDB {
/*
* 数据库名
* */
public static final String DB_NAME = "cool_weather";
/*
* 数据库版本
* */
public static final int VERSION=1;
private static CoolWeatherDB coolWeatherDB;//创建对象
private SQLiteDatabase db;//这个类用于管理和操作SQLite数据库,对数据库的操作离不开这个类
/*
* 将构造方法私有化
* 保证全局范围内只会有一个实例
* */
private CoolWeatherDB(Context context){
CoolWeatherOpenHelper dbHelper = new CoolWeatherOpenHelper(context,DB_NAME,null,VERSION);
//创建建表类,传入的参数分别是上下文,数据库名,第三个参数不用管,数据库版本
db=dbHelper.getWritableDatabase();//通过getWritableDatabase()方法获取到db实例
}
/*
* 获取CoolWeather的实例
* */
public synchronized static CoolWeatherDB getInstance(Context context){
//对synchronized类不太了解的朋友可以百度一下
if(coolWeatherDB==null){
//如果数据库的操作类对象不存在则创建对象
coolWeatherDB=new CoolWeatherDB(context);
}
return coolWeatherDB;
}
/*
* 将Province实例存储到数据库
* */
public void saveProvince(Province province){
if(province!=null){
//ContentValues类是负责存储一些键值对,但是它存储的键值对当中的名是一个String类型(第一个参数),而值都是基本类型。
//说白了此类在这里用就是存储一下省的名字及代号
ContentValues values = new ContentValues();
values.put("province_name",province.getProvinceName());
values.put("province_code",province.getProvinceCode());
db.insert("Province",null,values);
}
}
/*
* 从数据库读取全国所有的省份信息
* */
public List<Province> loadProvince(){
List<Province> list = new ArrayList<>();
//这个应该很熟悉list集合且该集合存储的均为Province类型,如果对集合不是很清楚可以看一下java的基础知识
//Cursor 类是每行的集合,先查询Province表将其中的数据内容放到Cursor类中
Cursor cursor =db.query("Province",null,null,null,null,null,null);
if(cursor.moveToFirst()){//从第一行开始
do{
Province province =new Province();
province.setId(cursor.getInt(cursor.getColumnIndex("id")));
//getColumnIndex是返回指定列的名称,然后用get方法获取到想要的值并放到新创建的Province类中
province.setProvinceName(cursor.getString(cursor.getColumnIndex("province_name")));
province.setProvinceCode(cursor.getString(cursor.getColumnIndex("province_code")));
list.add(province);//添加到集合中
}while(cursor.moveToNext());
}
return list;
}
/*
* 将City实例存储到数据库
* */
public void saveCity(City city){
if(city!=null){
ContentValues values = new ContentValues();
values.put("city_name",city.getCityName());
values.put("city_code",city.getCityCode());
values.put("province_id",city.getProvinceId());
db.insert("City",null,values);
}
}
/*
* 从数据库读取某省下所有的城市信息
* */
public List<City> loadCities(int provinceId){
List<City> list = new ArrayList<>();
Cursor cursor = db.query("City",null,"province_id=?",new String[] {String.valueOf(provinceId)}
,null,null,null);
if(cursor.moveToFirst()){
do{
City city = new City();
city.setId(cursor.getInt(cursor.getColumnIndex("id")));
city.setCityName(cursor.getString(cursor.getColumnIndex("city_name")));
city.setCityCode(cursor.getString(cursor.getColumnIndex("city_code")));
city.setProvinceId(provinceId);
list.add(city);
}while(cursor.moveToNext());
}
return list;
}
/*
* 将County实例存储到数据库中
* */
public void saveCounty(County county){
if(county!=null){
ContentValues values = new ContentValues();
values.put("county_name",county.getCountyName());
values.put("county_code",county.getCountyCode());
values.put("city_id",county.getCityId());
db.insert("County",null,values);
}
}
/*
* 从数据库读取某城市下所有县的信息
* */
public List<County> loadCounties(int cityId){
List<County> list = new ArrayList<>();
Cursor cursor = db.query("County",null,"city_id=?",new String[]{String.valueOf(cityId)},
null,null,null);
if(cursor.moveToFirst()){
do{
County county = new County();
county.setId(cursor.getInt(cursor.getColumnIndex("id")));
county.setCountyName(cursor.getString(cursor.getColumnIndex("county_name")));
county.setCountyCode(cursor.getString(cursor.getColumnIndex("county_code")));
county.setCityId(cityId);
list.add(county);
}while(cursor.moveToNext());
}
return list;
}
}
是不是非常的简单,我详细的注释了Province表的存储和获取信息,其他两个表中个方法类似,因此你可以轻松的理解。到这里我们已经完成了对数据库操作的基本代码,下面就是如何从服务器中获取到我们所需要的数据,相信你也已经迫不及待了,接着往下看吧。
由于要从服务器中获取,所以我们在util(工具)包中新建一个HttpUtil类,这个类中的代码如下:
package com.example.coolweather.util;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
public class HttpUtil {
//用static是因为可以不用创建实例,即可调用该方法。开子线程是因为网络获取数据非常耗时,防止堵塞
//我们还用到了HttpCallbackListener接口为了实现回调接收服务器返回的结果
public static void sendHttpRequest(final String address,final HttpCallbackListener listener){
new Thread(new Runnable() {
@Override
public void run() {
HttpURLConnection connection =null;
try {
//下面是安卓的网络操作,很简单为大家介绍一下
//URL说白了就是一个网址,在这里我们也可以称之为网络接口
// 首先我们获取到了接口,然后用openConnection获取到了HttpURLConnection的实例,再往下就是设置他的属性
URL url = new URL(address);
connection = (HttpURLConnection) url.openConnection();
//GET表示从服务器中获取数据,下面设置的是连接超时,读取超时毫秒数
connection.setRequestMethod("GET");
connection.setConnectTimeout(8000);
connection.setReadTimeout(8000);
//我们还要定义一个输入流,用于读取connection中的信息
//下面是读取信息的代码
InputStream in = connection.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
StringBuilder response = new StringBuilder();
String line;
while((line=reader.readLine())!=null){//按行读取
response.append(line);//依行添加到respond对象中
}
if(listener!=null){
//回调onFinish()方法
listener.onFinish(response.toString());
}
} catch (Exception e) {
e.printStackTrace();
if(listener!=null){
listener.onError(e);
}
}finally {
if(connection!=null){
connection.disconnect();//断开连接
}
}
}
}).start();//运行线程
}
}
上面提到了我们用的是HttpCallbackListener接口实现的回调(Java提供的回调机制),因此我把接口的代码也展示出来。
public interface HttpCallbackListener {
void onFinish(String response);
void onError(Exception e);
}
现在我们可以获取到从服务器返回的数据,在上文中我也说过其返回数据的格式是“代号|城市,代号|城市”,我们想要获取代号或者城市,必然涉及到解析过程,那么如何解析那,很简单
在util包中新建一个Utility类,这个类专门负责解析处理服务器返回的数据,下面是我们解析的代码,我会详细的说明如何解析:
package com.example.coolweather.util;
import android.content.Context;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.text.TextUtils;
import com.example.coolweather.db.CoolWeatherDB;
import com.example.coolweather.model.City;
import com.example.coolweather.model.County;
import com.example.coolweather.model.Province;
import org.json.JSONException;
import org.json.JSONObject;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
public class Utility {
/*
* 解析和处理服务器返回的省级数据
* */
public synchronized static boolean handleProvinceResponse(CoolWeatherDB coolWeatherDB
,String response){
if(!TextUtils.isEmpty(response)){
String[] allProvince = response.split(",");//将response字符串中的内容以逗号为界限分开存储到allProvince数组中
if(allProvince!=null&&allProvince.length>0){
for(String p:allProvince){
String[] array = p.split("\\|");//split中传入的参数为正则表达式 需要两个\\才能代表|
Province province = new Province();
//解析后 代号位于数组第一个位置,名字位于第二个位置
province.setProvinceCode(array[0]);
province.setProvinceName(array[1]);
//将解析出来的数据存储到Province表
coolWeatherDB.saveProvince(province);
}
return true;//成功解析
}
}
return false;
}
/*
* 解析和处理服务器返回的市级数据
* */
public static boolean handleCitiesponse(CoolWeatherDB coolWeatherDB,String response,int provinceId){
if(!TextUtils.isEmpty(response)){
String[] allCities = response.split(",");
if(allCities!=null&&allCities.length>0){
for(String c:allCities){
String[] array = c.split("\\|");
City city = new City();
city.setCityCode(array[0]);
city.setCityName(array[1]);
city.setProvinceId(provinceId);
//将解析出来的数据存储到City表中
coolWeatherDB.saveCity(city);
}
return true;
}
}
return false;
}
/*
* 解析和处理服务器返回的县级数据
* */
public static boolean handleCountiesResponse(CoolWeatherDB coolWeatherDB,String response,int cityId){
if(!TextUtils.isEmpty(response)){
String[] allCounties = response.split(",");
if(allCounties!=null&&allCounties.length>0){
for(String c:allCounties){
String[] array = c.split("\\|");
County county = new County();
county.setCountyCode(array[0]);
county.setCountyName(array[1]);
county.setCityId(cityId);
//将解析出来的数据存储到County表中
coolWeatherDB.saveCounty(county);
}
return true;
}
}
return false;
}
}
简单的说我们的解析规则就是先按逗号分隔,再按单数线分割并放入数组中,再从数组中取出。解析也搞定了!
下面的工作自然是将我们解析出来的城市显示到界面上,我们做了这么多的幕后工作,为界面的显示做了非常充分的工作,下面让我们一起写一下显示城市的界面,界面自然要放到res/layout目录下,新建一个choose_area.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="50dp"
android:background="#484E61">
<TextView
android:id="@+id/title_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:textColor="#fff"
android:textSize="24sp"/>
</RelativeLayout>
<ListView
android:id="@+id/list_view"
android:layout_width="match_parent"
android:layout_height="match_parent"></ListView>
</LinearLayout>
把代码粘贴到你的Android studio中你就可以清楚的知道这些代码的作用了,其实就是简单的加了一个显示文本内容的主标题和一个用于显示城市名的listview,写法非常简单。
布局文件写完了,下面自然要写与它相关联的类文件ChooseAreaActivity
public class ChooseAreaActivity extends Activity {
public static final int LEVEL_PROVINCE = 0;//标记省级
public static final int LEVEL_CITY = 1;//标记市级
public static final int LEVEL_COUNTY = 2;//标记县级
private ProgressDialog progressDialog;//进度条
private TextView titleText;//标题文本
private ListView listView;//显示城市信息滑动列表
private ArrayAdapter<String> adapter;//适配器
private CoolWeatherDB coolWeatherDB;
private List<String> dataList = new ArrayList<>();//数据列表
/*
* 省列表
* */
private List<Province> provinceList;
/*
* 市列表
* */
private List<City> cityList;
/*
* 县列表
* */
private List<County> countyList;
/*
* 选中的省份
* */
private Province selectedProvince;
/*
* 选中的城市
* */
private City selectedCity;
/*
* 当前选中的级别
* */
private int currentLevel;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);//设置显示界面没有标题
setContentView(R.layout.choose_area);
//获取控件
listView = (ListView) findViewById(R.id.list_view);
titleText = (TextView) findViewById(R.id.title_text);
//初始化适配器 并显示在listview中
adapter = new ArrayAdapter<String>(this, android.R.layout.simple_expandable_list_item_1, dataList);
listView.setAdapter(adapter);
//获取到CoolWeatherDB的实例
coolWeatherDB = coolWeatherDB.getInstance(this);
//listview的点击事件
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
if (currentLevel == LEVEL_PROVINCE) {//如果当前选中的级别是省级
//省列表中获取到当前点击位置的省的信息
selectedProvince = provinceList.get(position);
queryCities();//加载市级数据
} else if (currentLevel == LEVEL_CITY) {
selectedCity = cityList.get(position);
queryCounties();//加载县级数据
}
}
});
queryProvinces();//加载省级数据
}
/*
* 查询全国所有的省,优先从数据库查询,如果没有查询到再去服务器上查询
* */
private void queryProvinces() {
//从数据库中读取省级数据
provinceList = coolWeatherDB.loadProvince();//加载省份
if (provinceList.size() > 0) {
dataList.clear();//清空dataList中的数据
for (Province province : provinceList) {
dataList.add(province.getProvinceName());//向dataList中添加省份名字信息
}
adapter.notifyDataSetChanged();
listView.setSelection(0);//滑动列表默认第一个位置
titleText.setText("中国");//更改标题为中国
currentLevel = LEVEL_PROVINCE;
} else {
queryFromServer(null, "province");
}
}
/*
* 查询全国所有的市,优先从数据库中查询,如果没有查询到再去服务器上查询
* */
private void queryCities() {
cityList = coolWeatherDB.loadCities(selectedProvince.getId());//加载该省份所含的城市信息 需要传入省份地址参数
//从数据库中获取
if (cityList.size() > 0) {
dataList.clear();//如果dataList中有数据就清空
for (City city : cityList) {//遍历cityList
dataList.add(city.getCityName());
}
adapter.notifyDataSetChanged();
listView.setSelection(0);//将滑动列表设置默认第一行
titleText.setText(selectedProvince.getProvinceName());//将标题设置为选中的省份的名字
currentLevel = LEVEL_CITY;//设置当前级别为市级
} else {
//从服务器上获取
queryFromServer(selectedProvince.getProvinceCode(), "city");
}
}
/*
* 查询所有的县,优先从数据库中查询,如果没有则再到服务器中查询
* */
private void queryCounties() {
countyList = coolWeatherDB.loadCounties(selectedCity.getId());
if (countyList.size() > 0) {
dataList.clear();
for (County county : countyList) {
dataList.add(county.getCountyName());
}
adapter.notifyDataSetChanged();
listView.setSelection(0);
titleText.setText(selectedCity.getCityName());
currentLevel = LEVEL_COUNTY;
} else {
queryFromServer(selectedCity.getCityCode(), "county");
}
}
/*
*
* 根据传入的代号和类型从服务器上查询省市县数据
* */
private void queryFromServer(final String code, final String type) {
String address;//网页接口地址
if (!TextUtils.isEmpty(code)) {
//拼接成网络接口地址
address = "http://www.weather.com.cn/data/list3/city" + code + ".xml";
} else {
address = "http://www.weather.com.cn/data/list3/city.xml";
}
showProgressDialog();//显示进度
HttpUtil.sendHttpRequest(address, new HttpCallbackListener() {
@Override
public void onFinish(String response) {
boolean result = false;
if ("province".equals(type)) {
result = Utility.handleProvinceResponse(coolWeatherDB, response);
//调用解析服务器返回数据的方法
} else if ("city".equals(type)) {
result = Utility.handleCitiesponse(coolWeatherDB, response, selectedProvince.getId());
} else if ("county".equals(type)) {
result = Utility.handleCountiesResponse(coolWeatherDB, response, selectedCity.getId());
}
//如果成功的解析了数据
if (result) {
//通过runOnUiThread()方法回到主线程处理逻辑
runOnUiThread(new Runnable() {
@Override
public void run() {
closeProgressDialog();
if ("province".equals(type)) {
queryProvinces();
} else if ("city".equals(type)) {
queryCities();
} else if ("county".equals(type)) {
queryCounties();
}
}
});
}
}
@Override
public void onError(Exception e) {
runOnUiThread(new Runnable() {
@Override
public void run() {
closeProgressDialog();
Toast.makeText(ChooseAreaActivity.this, "加载失败", Toast.LENGTH_SHORT).show();
}
});
}
});
}
/*
* 打开进度对话框
* */
private void showProgressDialog() {
if (progressDialog == null) {
progressDialog = new ProgressDialog(this);
progressDialog.setMessage("正在加载...");
progressDialog.setCanceledOnTouchOutside(false);//点击对话框外部不取消对话框显示
}
}
/*
* 关闭进度对话框
* */
private void closeProgressDialog() {
if (progressDialog != null) {
progressDialog.dismiss();//释放资源
}
}
/*
* 捕获Back按键 根据当前的级别来判断,此时应该返回市列表、省列表、还是直接退出
* */
@Override
public void onBackPressed() {
//当为县级时 点击返回键后退到市级
if(currentLevel==LEVEL_COUNTY){
queryCities();
}else if(currentLevel==LEVEL_CITY){
queryProvinces();
}
finish();
}
}
}
这个类实现的是从服务器中获取到数据并通过工具包中的Utility类将服务器返回的数据解析并放到了ListView中显示。对于ListView显示数据通过适配器在这里不在赘述,代码中还写了点击事件,因为根据用户所点击的不同等级的城市(省、市)其调用的方法自然不同,如果是省级则我们应该从服务器中获取该省所包含的市级的信息。在最后面处onBackPressed()方法实现的是将返回键重写,当我们的界面位于县级城市时,点击返回键返回市级城市。当我们的界面位于市级城市时,点击返回键返回省级城市。最后不要忘记在AndroidManifest.xml文件中添加网络权限
我们天气项目的第一个功能获取城市信息的功能就完美的完成了!
请等待后续(2)...