作业要求:
做一个天气应用

  1. 接口参考:
  2. 考察内容:获取数据,解析JSON
  3. 数据缓存在数据库中,使用ContentProvider来处理
  4. 如果不强制刷新,则使用缓存数据每隔一定时间再刷新一次

前期准备

1.选择合适的API!选择合适的API!选择合适的API!(重要的话说三遍,中途变更API严重影响效率和心情).
2.gson.如何添加依赖库

目录结构

android 天气json解析api 安卓天气app开发教程_android 天气json解析api

UI

注意:在布局的过程中weightSum和layout_weight要慎用,尤其是在内容长度可变的情况下,使用结果往往不符合预期.

android 天气json解析api 安卓天气app开发教程_数据库_02

主要逻辑

主要逻辑集中在查询按钮的点击事件上

(注意:cursor.moveToFirst(),Long.valueOf()和Long.getLong()的用法.)

mQueryButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //判断用户是否输入了城市名称
                if (!TextUtils.isEmpty(mCityName.getText())) {
                    //首先查询本地数据库
                    Uri uri = Uri.parse("content://com.example.janiszhang.weatherdemo.provider/weatherdata");
                    Cursor cursor = getContentResolver().query(uri, null, "cityname = ?", new String[]{mCityName.getText() + ""}, null);
                    if (cursor.moveToFirst()) {// 查询成功    //这里不可以使用cursor!= null来判断!!!!
                        Log.i("zhangbz", cursor.getString(cursor.getColumnIndex("savetime")));

                       if ((System.currentTimeMillis() - Long.valueOf(cursor.getString(cursor.getColumnIndex("savetime")))) < (1000*60)) {//这里不可以使用Long.getLong(),因为它返回的是系统属性的值,其参数是被请求的系统属性的名称
                           //如果数据库中的数据没有过期,就从数据库中查询
                           updateUIfromDatabase(cursor);
                       } else {
                           //如果数据库中的数据过期了,则通过网络查询并update到数据库
                           shouldUpdate = true;
                           try {
                               httpArg = "city=" + URLEncoder.encode(mCityName.getText().toString(), "UTF-8");//中文需要编码
                           } catch (UnsupportedEncodingException e) {
                               e.printStackTrace();
                           }
                           new MyAsyncTask().execute(httpArg);//使用asynctask

                       }
                    } else {
                        //网络查询并insert到数据库
                        shouldUpdate = false;//需要insert
                        try {
                            httpArg = "city=" + URLEncoder.encode(mCityName.getText().toString(), "UTF-8");//中文需要编码
                        } catch (UnsupportedEncodingException e) {
                            e.printStackTrace();
                        }
                        new MyAsyncTask().execute(httpArg);
                    }

                    
                    SharedPreferences sp = getSharedPreferences("last", Context.MODE_PRIVATE);
                    SharedPreferences.Editor editor = sp.edit();
                    editor.putString("cityname", mCityName.getText().toString());
                    editor.apply();
                    
                    //开启自动更新
                    Intent intent = new Intent(MainActivity.this, AutoUpdateService.class);
                    startService(intent);
                }
            }
        });

重点记录

使用Gson解析数据

Gson gson = new Gson();
            WeatherDataStatus weatherDataStatus = gson.fromJson(s, WeatherDataStatus.class);

这里注意两个问题:
1.使用gson解析json数据时,不需要为整个json数据创建实体类,只需要为需要解析的数据定义变量和提供getter/setter方法.
2.注意区分什么是json数组:

//json数组:中括号
        [{
		"这是json数组": "这是json数组"
	}, {
		"这是json数组": "这是json数组"
	}, {
		"这是json数组": "这是json数组"
	}]

        
       //注意和这种情况区分开
       {
	    "这不是json数组": [{
		"这才是json数组": "这才是json数组"
	    }, {
		"这才是json数组": "这才是json数组"
	    }, {
		"这才是json数组": "这才是json数组"
	    }]
       }

自动更新的实现

service需要由Activity启动,之后由service和receiver配合相互唤醒.

AutoUpdateService.class

public class AutoUpdateService extends Service{

    private String mCityname;

    @Nullable
    @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实现定时任务
        AlarmManager manager = (AlarmManager) getSystemService(ALARM_SERVICE);
        int anHour = 8 * 60 *60 * 1000;//8小时
        long triggerAtTime = SystemClock.elapsedRealtime() + anHour;
        //service -> receiver
        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);
    }
    //...

}

AutoUpdateReceiver.class

public class AutoUpdateReceiver extends BroadcastReceiver{
    @Override
    public void onReceive(Context context, Intent intent) {
        //receiver->service
        Intent i = new Intent(context, AutoUpdateService.class);
        context.startService(i);
    }
}

contentprovider的练习

虽然用在这里很牵强,但是目的是练习嘛.
很久没有使用contentprovider,以下为基本使用方法.

public class MyProvider extends ContentProvider{

    public static final int WEATHERDATA_DIR = 0;
    public static final String AUTHORITY = "com.example.janiszhang.weatherdemo.provider";
    private static UriMatcher sUriMatcher;
    private MyDBHelper mMyDBHelper;

    static {
        sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
        sUriMatcher.addURI(AUTHORITY, "weatherdata", WEATHERDATA_DIR);
    }
    @Override
    public boolean onCreate() {
        mMyDBHelper = new MyDBHelper(getContext(),  "weatherDataDB.db", null, 1);
        return true;
    }

    @Nullable
    @Override
    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
        SQLiteDatabase db = mMyDBHelper.getReadableDatabase();
        Cursor cursor = null;
        switch (sUriMatcher.match(uri)) {
            case WEATHERDATA_DIR:
                cursor = db.query("weatherdata", projection, selection, selectionArgs, null, null, sortOrder);
                break;
        }
        return cursor;
    }

    @Nullable
    @Override
    public String getType(Uri uri) {
        switch (sUriMatcher.match(uri)) {
            case WEATHERDATA_DIR:
                return "vnd.android.cursor.dir/vnd.com.example.janiszhang.weatherdemo.provider.weatherdata";
        }
        return null;
    }

    @Nullable
    @Override
    public Uri insert(Uri uri, ContentValues values) {

        SQLiteDatabase db = mMyDBHelper.getReadableDatabase();
        Uri uriReturn = null;
        switch (sUriMatcher.match(uri)) {
            case WEATHERDATA_DIR:
                long newId = db.insert("weatherdata", null, values);
                uriReturn = Uri.parse("content://" + AUTHORITY + "/weatherdata/" + newId);
                break;
        }
        return uriReturn;
    }

    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        return 0;
    }

    @Override
    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
        SQLiteDatabase db = mMyDBHelper.getWritableDatabase();
        int updateRows = 0;
        switch (sUriMatcher.match(uri)) {
            case WEATHERDATA_DIR:
                updateRows = db.update("weatherdata", values, selection, selectionArgs);
                break;
        }
        return updateRows;
    }
}

总结:写这个demo,把四大组件都用到了,把基本用法复习了一遍,温故知新.