第一行代码 weather项目-自我总结

一.创建数据库和表

1.建立三张表,分别存放Province ,City ,County 的数据
建表语句:
Province:

 create table Province(
 id integer primary key autoincrement,
 province_name text,
 province_code text)

City:

create table City(
id integer primary key autoincrement,
city_name text,
city_code text,
province_id integer)

Country;

create table Country(
id integer primary key autoincrement,
country_name text,
country_code text,
city_id integer)

2.将建表语句写入到代码中
新建一个CoolWeatherOpenHelper类继承于SQLiteOpenHelper
将三条建表语句定义成常量,然后在onCrea方法中去执行创建

3.创建3张表的实体类
方便后续开发的工作
以Province为例:

 public class Province{
     private int id;
     private String province_name;
     private String province_code;
    //添加getset方法
    }

4.创建一个CoolWeatherDB类
把一些常用的数据库操作封装起来,以方便后面的使用。
CoolWeatherDB是一个单例类,我们将它的构造方法私有化,并提供了一个getInstance()方法来获取CoolWeatherDB的实例,这样就可以保证全局范围内只会有一个CoolWeatherDB的实例。接下来我们在CoolWeatherDB中提供了六组方法,saveProvince()、loadProvinces()、saveCity()、loadCities()、saveCounty()、loadCounties(),分别用于存储省份数据、读取所有省份数据、存储城市数据、读取某省内所有城市数据、存储县数据、读取某市内所有县的数据。有了这几个方法,我们后面很多的数据库操作都将会变得非常简单。

二.遍历全国省市县数据

1.增加一个HttpUtil类
因为所有的数据都是从服务器上获取的,所以和服务器的交互必不可少
2.创建一个Utility类
解析和处理从服务器返回的省市县数据
提供了handleProvincesResponse()、handleCitiesResponse()、handleCountiesResponse()这三个方法,分别用于解析和处理服务器返回的省级、市级和县级数据。解析的规则就是先按逗号分隔,再按单竖线分隔,接着将解析出来的数据设置到实体类中,最后调用CoolWeatherDB中的三个save()方法将数据存储到相应的表中。
3.创建显示省市县的布局文件
4.创建遍历省市县的活动
这个类里的代码虽然非常多,可是逻辑却不复杂,我们来慢慢理一下。在onCreate()方法中先是获取到了一些控件的实例,然后去初始化了ArrayAdapter,将它设置为ListView的适配器。之后又去获取到了CoolWeatherDB的实例,并给ListView设置了点击事件,到这里我们的初始化工作就算是完成了。
在onCreate()方法的最后,调用了queryProvinces()方法,也就是从这里开始加载省级数据的。queryProvinces()方法的内部会首先调用CoolWeatherDB的loadProvinces()方法来从数据库中读取省级数据,如果读取到了就直接将数据显示到界面上,如果没有读取到就调用queryFromServer()方法来从服务器上查询数据。
queryFromServer()方法会先根据传入的参数来拼装查询地址,这个地址就是我们在14.1节分析过的。确定了查询地址之后,接下来就调用HttpUtil的sendHttpRequest()方法来向服务器发送请求,响应的数据会回调到onFinish()方法中,然后我们在这里去调用Utility的handleProvincesResponse()方法来解析和处理服务器返回的数据,并存储到数据库中。接下来的一步很关键,在解析和处理完数据之后,我们再次调用了queryProvinces()方法来重新加载省级数据,由于queryProvinces()方法牵扯到了UI操作,因此必须要在主线程中调用,这里借助了runOnUiThread()方法来实现从子线程切换到主线程,它的实现原理其实也是基于异步消息处理机制的。现在数据库中已经存在了数据,因此调用queryProvinces()就会直接将数据显示到界面上了。
当你点击了某个省的时候会进入到ListView的onItemClick()方法中,这个时候会根据当前的级别来判断是去调用queryCities()方法还是queryCounties()方法,queryCities()方法是去查询市级数据,而queryCounties()方法是去查询县级数据,这两个方法内部的流程和queryProvinces()方法基本相同,这里就不重复讲解了。
另外还有一点需要注意,我们重写了onBackPressed()方法来覆盖默认Back键的行为,这里会根据当前的级别来判断是返回市级列表、省级列表、还是直接退出。
5.配置AndroidManifest.xml文件

三.显示天气信息

1.创建显示天气的布局文件
2.在Utility类中添加几个方法用来解析和处理服务器返回的JSON数据

/**
     * 解析服务器返回的JSON数据,并将解析出的数据存储到本地。
     */
    public static void handleWeatherResponse(Context context, String response) {
        try {
            JSONObject jsonObject = new JSONObject(response);
            JSONObject weatherInfo = jsonObject.getJSONObject("weatherinfo");
            String cityName = weatherInfo.getString("city");
            String weatherCode = weatherInfo.getString("cityid");
            String temp1 = weatherInfo.getString("temp1");
            String temp2 = weatherInfo.getString("temp2");
            String weatherDesp = weatherInfo.getString("weather");
            String publishTime = weatherInfo.getString("ptime");
            saveWeatherInfo(context, cityName, weatherCode, temp1, temp2,
weatherDesp, publishTime);
        } catch (JSONException e) {
            e.printStackTrace();
        }
    }
    /**
     * 将服务器返回的所有天气信息存储到SharedPreferences文件中。
     */
    public static void saveWeatherInfo(Context context, String cityName, String weatherCode, String temp1, String temp2, String weatherDesp, String publishTime) {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy年M月d日", Locale.CHINA);
        SharedPreferences.Editor editor = PreferenceManager
.getDefaultSharedPreferences(context).edit();
        editor.putBoolean("city_selected", true);
        editor.putString("city_name", cityName);
        editor.putString("weather_code", weatherCode);
        editor.putString("temp1", temp1);
        editor.putString("temp2", temp2);
        editor.putString("weather_desp", weatherDesp);
        editor.putString("publish_time", publishTime);
        editor.putString("current_date", sdf.format(new Date()));
        editor.commit();
    }

3.创建显示天气的活动
同样,这个活动中的代码也非常长,我们还是一步步梳理下。在onCreate()方法中仍然先是去获取一些控件的实例,然后会尝试从Intent中取出县级代号,如果可以取到就会调用queryWeatherCode()方法,如果不能取到则会调用showWeather()方法,我们先来看下可以取到的情况。
queryWeatherCode()方法中并没有几行代码,仅仅是拼装了一个地址,然后调用queryFromServer()方法来查询县级代号所对应的天气代号。服务器返回的数据仍然会回调到onFinish()方法中,这里对返回的数据进行解析,然后将解析出来的天气代号传入到queryWeatherInfo()方法中。
queryWeatherInfo()方法也非常简单,同样是拼装了一个地址,然后调用queryFromServer()方法来查询天气代号所对应的天气信息。由于天气信息是以JSON格式返回的,因此我们在handleWeatherResponse()方法中使用JSONObject将数据全部解析出来,然后调用saveWeatherInfo()方法将所有的天气信息都存储到SharedPreferences文件中。注意除了天气信息之外,我们还存储了一个city_selected标志位,以此来辨别当前是否已经选中了一个城市。最后会去调用showWeather()方法来将所有的天气信息显示到界面上,showWeather()方法中的逻辑很简单,就是从SharedPreferences文件中将数据读取出来,然后一一设置到界面上即可。
刚才分析的是在onCreate()方法中可以取到县级代号的情况,那么不能取到的时候呢?原来就是直接调用showWeather()方法来显示本地存储的天气信息就可以了。
接下来我们要做的,就是如何从ChooseAreaActivity跳转到WeatherActivity了,修改ChooseAreaActivity中的代码,如下所示:

public class ChooseAreaActivity extends Activity {
    ……
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        SharedPreferences prefs = PreferenceManager. getDefaultSharedPreferences(this);
        if (prefs.getBoolean("city_selected", false)) {
            Intent intent = new Intent(this, WeatherActivity.class);
            startActivity(intent);
            finish();
            return;
        }
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        setContentView(R.layout.choose_area);
        listView = (ListView) findViewById(R.id.list_view);
        titleText = (TextView) findViewById(R.id.title_text);
        adapter = new ArrayAdapter(this, android.R.layout.simple_ list_item_1, dataList);
        listView.setAdapter(adapter);
        coolWeatherDB = CoolWeatherDB.getInstance(this);
        listView.setOnItemClickListener(new OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView arg0, View view, int index,
                    long arg3) {
                if (currentLevel == LEVEL_PROVINCE) {
                    selectedProvince = provinceList.get(index);
                    queryCities();
                } else if (currentLevel == LEVEL_CITY) {
                    selectedCity = cityList.get(index);
                    queryCounties();
                } else if (currentLevel == LEVEL_COUNTY) {
                    String countyCode = countyList.get(index).getCountyCode();
                    Intent intent = new Intent(ChooseAreaActivity.this, WeatherActivity.class);
                    intent.putExtra("county_code", countyCode);
                    startActivity(intent);
                    finish();
                }
            }
        });
        queryProvinces();  // 加载省级数据
    }

    ……
}

可以看到,这里我们主要修改了两处。第一,在onCreate()方法的一开始先从SharedPreferences文件中读取city_selected标志位,如果为true就说明当前已经选择过城市了,直接跳转到WeatherActivity即可。第二,在onItemClick()方法中加入一个if判断,如果当前级别是LEVEL_COUNTY,就启动WeatherActivity,并把当前选中县的县级代号传递过去。

四.切换城市和手动更新天气

1.在布局文件中加入更新天气和切换城市的按钮
2修改WeatherActivity中的代码

public class WeatherActivity extends Activity implements OnClickListener{
        ……
    /**
     * 切换城市按钮
     */
    private Button switchCity;
    /**
     * 更新天气按钮
     */
    private Button refreshWeather;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        setContentView(R.layout.weather_layout);
        ……
        switchCity = (Button) findViewById(R.id.switch_city);
        refreshWeather = (Button) findViewById(R.id.refresh_weather);
        switchCity.setOnClickListener(this);
        refreshWeather.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
        case R.id.switch_city:
            Intent intent = new Intent(this, ChooseAreaActivity.class);
            intent.putExtra("from_weather_activity", true);
            startActivity(intent);
            finish();
            break;
        case R.id.refresh_weather:
            publishText.setText("同步中...");
            SharedPreferences prefs = PreferenceManager. getDefaultSharedPreferences(this);
            String weatherCode = prefs.getString("weather_code", "");
            if (!TextUtils.isEmpty(weatherCode)) {
                queryWeatherInfo(weatherCode);
            }
            break;
        default:
            break;
        }
    }
    ……
}

我们在onCreate()方法中获取到了两个按钮的实例,然后分别调用了setOnClickListener()方法来注册点击事件。当点击的是更新天气按钮时,会首先从SharedPreferences文件中读取天气代号,然后调用queryWeatherInfo()方法去更新天气就可以了。当点击的是切换城市按钮时,会跳转到ChooseAreaActivity,但是注意目前我们已经选中过了一个城市,如果直接跳转到ChooseAreaActivity,会立刻又跳转回来,因此这里在Intent中加入了一个from_weather_activity标志位。
接着在ChooseAreaActivity对这个标志位进行处理,如下所示:

public class ChooseAreaActivity extends Activity {
    ……
    /**
     * 是否从WeatherActivity中跳转过来。
     */
    private boolean isFromWeatherActivity;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        isFromWeatherActivity = getIntent().getBooleanExtra("from_weather_ activity", false);
        SharedPreferences prefs = PreferenceManager. getDefaultSharedPreferences(this);
        // 已经选择了城市且不是从WeatherActivity跳转过来,才会直接跳转到WeatherActivity
        if (prefs.getBoolean("city_selected", false) && !isFromWeatherActivity) {
            Intent intent = new Intent(this, WeatherActivity.class);
            startActivity(intent);
            finish();
            return;
        }
        ……
    }
    ……
    @Override
    public void onBackPressed() {
        if (currentLevel == LEVEL_COUNTY) {
            queryCities();
        } else if (currentLevel == LEVEL_CITY) {
            queryProvinces();
        } else {
            if (isFromWeatherActivity) {
                Intent intent = new Intent(this, WeatherActivity.class);
                startActivity(intent);
            }
            finish();
        }
    }
}

可以看到,这里我们加入了一个isFromWeatherActivity变量,以此来标记是不是从WeatherActivity跳转过来的,只有已经选择了城市且不是从WeatherActivity跳转过来的时候才会直接跳转到WeatherActivity。另外,我们在onBackPressed()方法中也进行了处理,当按下Back键时,如果是从WeatherActivity跳转过来的,则应该重新回到WeatherActivity。

五.后台自动更新天气

1.首先在service包下新建一个AutoUpdateService继承自Service,
代码如下所示:

public class AutoUpdateService extends Service {

    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                updateWeather();
            }
        }).start();
        AlarmManager manager = (AlarmManager) getSystemService(ALARM_SERVICE);
        int anHour = 8 * 60 * 60 * 1000; // 这是8小时的毫秒数
        long triggerAtTime = SystemClock.elapsedRealtime() + anHour;
        Intent i = new Intent(this, AutoUpdateReceiver.class);
        PendingIntent pi = PendingIntent.getBroadcast(this, 0, i, 0);
        manager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, triggerAtTime, pi);
        return super.onStartCommand(intent, flags, startId);
    }

    /**
     * 更新天气信息。
     */
    private void updateWeather() {
        SharedPreferences prefs = PreferenceManager. getDefaultSharedPreferences(this);
        String weatherCode = prefs.getString("weather_code", "");
        String address = "http://www.weather.com.cn/data/cityinfo/" + weatherCode + ".html";
        HttpUtil.sendHttpRequest(address, new HttpCallbackListener() {
            @Override
            public void onFinish(String response) {
                Utility.handleWeatherResponse(AutoUpdateService.this, response);
            }

            @Override
            public void onError(Exception e) {
                e.printStackTrace();
            }
        });
    }

}

可以看到,在onStartCommand()方法中先是开启了一个子线程,然后在子线程中调用updateWeather()方法来更新天气,我们仍然会将服务器返回的天气数据交给Utility的handleWeatherResponse()方法去处理,这样就可以把最新的天气信息存储到SharedPreferences文件中。
2.新建AutoUpdateReceiver继承自BroadcastReceiver
代码如下所示:

public class AutoUpdateReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        Intent i = new Intent(context, AutoUpdateService.class);
        context.startService(i);
    }

}

3.激活服务
这里只是在onReceive()方法中再次去启动AutoUpdateService,就可以实现后台定时更新的功能了。不过,我们还需要在代码某处去激活AutoUpdateService这个服务才行。修改WeatherActivity中的代码,如下所示:

public class WeatherActivity extends Activity implements OnClickListener{
    ……
    private void showWeather() {
        SharedPreferences prefs = PreferenceManager. getDefaultSharedPreferences(this);
        cityNameText.setText( prefs.getString("city_name", ""));
        temp1Text.setText(prefs.getString("temp1", ""));
        temp2Text.setText(prefs.getString("temp2", ""));
        weatherDespText.setText(prefs.getString("weather_desp", ""));
        publishText.setText("今天" + prefs.getString("publish_time", "") + "发布");
        currentDateText.setText(prefs.getString("current_date", ""));
        weatherInfoLayout.setVisibility(View.VISIBLE);
        cityNameText.setVisibility(View.VISIBLE);
        Intent intent = new Intent(this, AutoUpdateService.class);
        startService(intent);
    }
}

可以看到,这里在showWeather()方法的最后加入启动AutoUpdateService这个服务的代码,这样只要一旦选中了某个城市并成功更新天气之后,AutoUpdateService就会一直在后台运行,并保证每8小时更新一次天气。
4.在AndroidManifest.xml中注册新增的服务和广播接收器

你可能感兴趣的:(学习笔记,Android)