我对Android很新,在这个阶段我的代码正在编译,数据正在加载到数据库中(据我可以看到调试器),但不会显示在ListView中。我已经搜索了很长一段时间,但我的代码中的所有内容似乎都没问题。所以我的问题是:我可能错过什么?先感谢您! 下面是我的片段代码。它应该获取AsyncTask提取的数据。 AsyncTask从服务器获取(天气)数据并将其插入数据库。根据我的理解,当插入数据时,应该通知用户界面有关更改,但在那里(或其他地方)出现问题。
public class ForecastFragment extends Fragment implements LoaderManager.LoaderCallbacks <Cursor> {
private String mLocation;
private static final int FORECAST_LOCADER = 0;
public static SimpleCursorAdapter simpleCursorAdapter;
// column names for the projection
private static final String[] FORECAST_COLUMNS = {
WeatherContract.WeatherEntry.TABLE_NAME + "." + WeatherContract.WeatherEntry._ID,
WeatherContract.WeatherEntry.COLUMN_DATE_TEXT,
WeatherContract.WeatherEntry.COLUMN_SHORT_DESC,
WeatherContract.WeatherEntry.COLUMN_MAX_TEMP,
WeatherContract.WeatherEntry.COLUMN_MIN_TEMP,
WeatherContract.LocationEntry.COLUMN_LOCATION_SETTING
};
private OnFragmentInteractionListener mListener;
public ForecastFragment() {}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View view = inflater.inflate(R.layout.fragment_forecast, container, false);
return view;
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
if (context instanceof OnFragmentInteractionListener) {
mListener = (OnFragmentInteractionListener) context;
} else {
throw new RuntimeException(context.toString()
+ " must implement OnFragmentInteractionListener");
}
}
@Override
public void onDetach() {
super.onDetach();
mListener = null;
}
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
simpleCursorAdapter = new SimpleCursorAdapter(getActivity(),
R.layout.list_item_forecast,
null,
new String[]{
WeatherContract.WeatherEntry.COLUMN_DATE_TEXT,
WeatherContract.WeatherEntry.COLUMN_SHORT_DESC,
WeatherContract.WeatherEntry.COLUMN_MAX_TEMP,
WeatherContract.WeatherEntry.COLUMN_MIN_TEMP
},
new int[] {
R.id.list_item_date_textview,
R.id.list_item_forecast_textview,
R.id.list_item_high_textview,
R.id.list_item_low_textview
},
0
);
ListView lv = (ListView) getView().findViewById(R.id.listview_forecast);
lv.setAdapter(simpleCursorAdapter);
getLoaderManager().initLoader(FORECAST_LOCADER, null, this);
}
@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
String startDate = WeatherContract.getDbDateString(new Date());
String sortOrder = WeatherContract.WeatherEntry.COLUMN_DATE_TEXT + " ASC";
mLocation = Utility.getPreferredLocation(getActivity());
Uri weatherForLocationUri = WeatherContract.WeatherEntry.buildWeatherLocationWithStartDate(mLocation, startDate);
return new CursorLoader(
getActivity(),
weatherForLocationUri,
FORECAST_COLUMNS,
null,
null,
sortOrder
);
}
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
simpleCursorAdapter.swapCursor(data);
Log.i("onLoadFinished", "" + data.getCount());
}
@Override
public void onLoaderReset(Loader<Cursor> loader) {
simpleCursorAdapter.swapCursor(null);
}
public interface OnFragmentInteractionListener {
// TODO: Update argument type and name
void onFragmentInteraction(Uri uri);
}
}
在上面的代码中,日志消息“onLoadFinished”显示0,我不确定这是否是预期值,因为我安装的断点显示此过程在获取数据之前运行。对于ListView,当我使用带有虚拟数据的ArrayAdapter时,显示了数据,但是使用simpleCursorAdapter它到目前为止还不起作用。
在MainActivity中调用AsyncTask:
public class MainActivity extends AppCompatActivity implements ForecastFragment.OnFragmentInteractionListener {
//...
@Override
public void onFragmentInteraction(Uri uri) { }
@Override
public void onStart() {
super.onStart();
updateWeather();
}
public void updateWeather() {
FetchWeatherTask fetchWeatherTask = new FetchWeatherTask(getApplicationContext());
String userLocationPreference = Utility.getPreferredLocation(getApplicationContext());
String userUnitsPreference = Utility.getPreferredUnits(getApplicationContext());
fetchWeatherTask.execute(userLocationPreference, userUnitsPreference);
}
以下是AsyncTask的代码:
class FetchWeatherTask extends AsyncTask<String, Void, Void> {
private Context mContext;
public FetchWeatherTask(Context context) {
mContext = context;
}
@Override
protected Void doInBackground(String... params) {
...// fetching data
cVVector.add(weatherValues);
if (cVVector.size() > 0) {
int nRecordsInserted = addWeatherData(cVVector);
}
...//
}
//function inserting data into db
private int addWeatherData(Vector <ContentValues> cvValues) {
ContentValues[] values = new ContentValues [cvValues.size()];
for (int i = 0; i < cvValues.size(); i++) {
values[i] = cvValues.get(i);
Log.d("addWeatherData", values[i].toString());
}
Uri uri = WeatherContract.WeatherEntry.CONTENT_URI;
int nRecords = -1;
nRecords = mContext.getContentResolver().bulkInsert(uri, values);
Log.i("insert records", "" + nRecords + " records inserted");
return nRecords;
}
在上面的代码中,日志消息打印数据并说出插入了14条记录(如预期的那样)。 db:中的bulkInsert代码:
@Override
public int bulkInsert(Uri uri, ContentValues[] values) {
final SQLiteDatabase db = dbHelper.getWritableDatabase();
final int match = sUriMatcher.match(uri);
int returnCount = 0;
switch (match) {
case WEATHER: {
db.beginTransaction();
try {
for (ContentValues value: values) {
long _id = db.insert(WeatherContract.WeatherEntry.TABLE_NAME, null, value);
if (_id != -1) {
returnCount++;
}
}
db.setTransactionSuccessful();
}
catch (Exception err) {
Log.e("bulkInsert", err.getMessage());
}
finally {
db.endTransaction();
getContext().getContentResolver().notifyChange(uri, null);
}
break;
}
default: {
returnCount = super.bulkInsert(uri, values);
break;
}
}
Log.i("bulkInsert", "" + returnCount + " records inserted");
return returnCount;
}
据我所知(我已经使用调试器检查过),事务成功完成,应该通知UI更改
getContext().getContentResolver().notifyChange(uri, null);
但在应用程序中我看到空白屏幕并且没有显示任何记录。所以我不知道该怎么做,请帮忙!提前谢谢!
UPD:,如评论中所述,我还要添加WeatherContract
类的完整代码:
public class WeatherContract {
public static final String CONTENT_AUTHORITY = "com.example.irina.sunshine.app";
public static final Uri BASE_CONTENT_URI = Uri.parse("content://" + CONTENT_AUTHORITY);
public static final String PATH_WEATHER = "weather";
public static final String PATH_LOCATION = "location";
public static final String DATE_FORMAT = "yyyyMMdd";
public static class LocationEntry implements BaseColumns {
// description of location table
public static final Uri CONTENT_URI =
BASE_CONTENT_URI.buildUpon().appendPath(PATH_LOCATION).build();
public static final String CONTENT_TYPE =
"vnd.android.cursor.dir/" + CONTENT_AUTHORITY + "/" + PATH_LOCATION;
public static final String CONTENT_ITEM_TYPE =
"vnd.android.cursor.item/" + CONTENT_AUTHORITY + "/" + PATH_LOCATION;
public static final String TABLE_NAME = "location";
public static final String COLUMN_LOCATION_SETTING = "setting";
// other fields
}
public static class WeatherEntry implements BaseColumns {
public static final Uri CONTENT_URI =
BASE_CONTENT_URI.buildUpon().appendPath(PATH_WEATHER).build();
public static final String CONTENT_TYPE =
"vnd.android.cursor.dir/" + CONTENT_AUTHORITY + "/" + PATH_WEATHER;
public static final String CONTENT_ITEM_TYPE =
"vnd.android.cursor.item/" + CONTENT_AUTHORITY + "/" + PATH_WEATHER;
public static final String TABLE_NAME = "weather";
//foreign key for location table
public static final String COLUMN_LOC_KEY = "location_id";
//date stored as text in format yyyy-MM-dd
public static final String COLUMN_DATE_TEXT = "date";
//weather id as returned by API to identify the icon
public static final String COLUMN_WEATHER_ID = "weather_id";
//weather data as returned by API
//e.g. "clear" vs "sky is clear"
public static final String COLUMN_SHORT_DESC = "short_desc";
//min and max temp stored as floats
public static final String COLUMN_MIN_TEMP = "min";
public static final String COLUMN_MAX_TEMP = "max";
//stored as float representing percentage
public static final String COLUMN_HUMIDITY = "humidity";
//stored as float representing percentage
public static final String COLUMN_PRESSURE = "pressure";
//stored as float representing mph
public static final String COLUMN_WIND_SPEED = "wind";
//meteorological degrees, e.g 0 is north, 180 is south, stored as floats
public static final String COLUMN_DEGREES = "degrees";
public static Uri buildWeatherUri(long id) {
return ContentUris.withAppendedId(CONTENT_URI, id);
}
public static Uri buildWeatherLocation(String locationSetting) {
return CONTENT_URI.buildUpon().appendPath(locationSetting).build();
}
// the function used to build Cursor Loader in Fragment
public static Uri buildWeatherLocationWithStartDate(String locationSetting, String startDate) {
return CONTENT_URI.buildUpon()
.appendPath(locationSetting)
//+ just added to create another / symbol in path, but it still doesn't match the URI
.appendEncodedPath("")
//-
.appendQueryParameter(COLUMN_DATE_TEXT, startDate)
.build();
}
public static Uri buildWeatherLocationWithDate(String locationSetting, String date) {
return CONTENT_URI.buildUpon()
.appendPath(locationSetting)
.appendPath(date)
.build();
}
public static String getLocationSettingFromUri(Uri uri) {
return uri.getPathSegments().get(1);
}
public static String getDateFromUri(Uri uri) {
return uri.getPathSegments().get(2);
}
public static String getStartDateFromUri(Uri uri) {
return uri.getQueryParameter(COLUMN_DATE_TEXT);
}
}
}
因此,CursorLoader是在上面列出的函数buildWeatherLocationWithStartDate
中使用URI构建创建的。
以下是自定义ContentProvider的代码(我将修改bulkInsert
函数,因为我已将其列在问题的主要部分中):
public class WeatherProvider extends ContentProvider {
private static final int WEATHER = 100;
private static final int WEATHER_WITH_LOCATION = 101;
private static final int WEATHER_WITH_LOCATION_AND_DATE = 102;
private static final int LOCATION = 300;
private static final int LOCATION_ID = 301;
private static final UriMatcher sUriMatcher = buildUriMatcher();
private WeatherDbHelper dbHelper;
private static final SQLiteQueryBuilder queryBuilder;
static {
queryBuilder = new SQLiteQueryBuilder();
queryBuilder.setTables(
WeatherContract.WeatherEntry.TABLE_NAME + " INNER JOIN "
+ WeatherContract.LocationEntry.TABLE_NAME
+ " ON " + WeatherContract.WeatherEntry.TABLE_NAME
+ "." + WeatherContract.WeatherEntry.COLUMN_LOC_KEY
+ " = " + WeatherContract.LocationEntry.TABLE_NAME
+ "." + WeatherContract.LocationEntry._ID
);
}
private static final String sLocationSettingSelection =
WeatherContract.LocationEntry.TABLE_NAME
+ "." + WeatherContract.LocationEntry.COLUMN_LOCATION_SETTING + " = ? ";
private static final String sLocationSettingWithStartDateSelection =
WeatherContract.LocationEntry.TABLE_NAME
+ "." + WeatherContract.LocationEntry.COLUMN_LOCATION_SETTING + " = ? AND "
+ WeatherContract.WeatherEntry.COLUMN_DATE_TEXT + " >= ? ";
private static final String sLocationSettingAndDateSelection =
WeatherContract.LocationEntry.TABLE_NAME
+ "." + WeatherContract.LocationEntry.COLUMN_LOCATION_SETTING + " = ? AND"
+ WeatherContract.WeatherEntry.COLUMN_DATE_TEXT + " = ? ";
private Cursor getWetatherByLocationSetting(Uri uri, String[] projection, String sortOrder) {
String locationSetting = WeatherContract.WeatherEntry.getLocationSettingFromUri(uri);
String startDate = WeatherContract.WeatherEntry.getStartDateFromUri(uri);
String[] selectionArgs;
String selection;
if (startDate == null) {
selection = sLocationSettingSelection;
selectionArgs = new String[]{locationSetting};
} else {
selectionArgs = new String[]{locationSetting, startDate};
selection = sLocationSettingWithStartDateSelection;
}
return queryBuilder.query(dbHelper.getReadableDatabase(),
projection, selection, selectionArgs, null, null, sortOrder);
}
private Cursor getWetatherByLocationAndDateSetting(Uri uri, String[] projection, String sortOrder) {
String locationSetting = WeatherContract.WeatherEntry.getLocationSettingFromUri(uri);
String date = WeatherContract.WeatherEntry.getDateFromUri(uri);
String[] selectionArgs;
String selection;
if (date == null) {
selection = sLocationSettingSelection;
selectionArgs = new String[]{locationSetting};
} else {
selection = sLocationSettingAndDateSelection;
selectionArgs = new String[]{locationSetting, date};
}
return queryBuilder.query(dbHelper.getReadableDatabase(),
projection, selection, selectionArgs, null, null, sortOrder);
}
private static UriMatcher buildUriMatcher() {
final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
final String authority = WeatherContract.CONTENT_AUTHORITY;
//just changed the order of URIs, but it still doesn't affect the result
sUriMatcher.addURI(authority, WeatherContract.PATH_WEATHER + "/*/*", WEATHER_WITH_LOCATION_AND_DATE);
sUriMatcher.addURI(authority, WeatherContract.PATH_WEATHER + "/*", WEATHER_WITH_LOCATION);
sUriMatcher.addURI(authority, WeatherContract.PATH_WEATHER, WEATHER);
sUriMatcher.addURI(authority, WeatherContract.PATH_LOCATION, LOCATION);
sUriMatcher.addURI(authority, WeatherContract.PATH_LOCATION + "/#", LOCATION_ID);
return sUriMatcher;
}
@Override
public boolean onCreate() {
dbHelper = new WeatherDbHelper(getContext());
return true;
}
@Nullable
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
Cursor cursor = null;
switch (sUriMatcher.match(uri)) {
case WEATHER_WITH_LOCATION_AND_DATE: {
cursor = getWetatherByLocationAndDateSetting(uri, projection, sortOrder);
break;
}
case WEATHER_WITH_LOCATION: {
cursor = getWetatherByLocationSetting(uri, projection, sortOrder);
break;
}
case WEATHER: {
cursor = dbHelper.getReadableDatabase().query(
WeatherContract.WeatherEntry.TABLE_NAME,
projection,
selection,
selectionArgs,
null,
null,
sortOrder
);
break;
}
case LOCATION_ID: {
cursor = dbHelper.getReadableDatabase().query(
WeatherContract.LocationEntry.TABLE_NAME,
projection,
WeatherContract.LocationEntry._ID + " = '" + ContentUris.parseId(uri) + "'",
null,
null,
null,
sortOrder
);
break;
}
case LOCATION: {
cursor = dbHelper.getReadableDatabase().query(
WeatherContract.LocationEntry.TABLE_NAME,
projection,
selection,
selectionArgs,
null,
null,
sortOrder
);
break;
}
default: {
throw new UnsupportedOperationException(" Not supported uri! " + uri.toString());
}
}
cursor.setNotificationUri(getContext().getContentResolver(), uri);
return cursor;
}
@Nullable
@Override
public String getType(Uri uri) {
final int match = sUriMatcher.match(uri);
switch (match) {
case WEATHER_WITH_LOCATION_AND_DATE: {
return WeatherContract.WeatherEntry.CONTENT_ITEM_TYPE;
}
case WEATHER_WITH_LOCATION: {
return WeatherContract.WeatherEntry.CONTENT_TYPE;
}
case WEATHER: {
return WeatherContract.WeatherEntry.CONTENT_TYPE;
}
case LOCATION_ID: {
return WeatherContract.LocationEntry.CONTENT_ITEM_TYPE;
}
case LOCATION: {
return WeatherContract.LocationEntry.CONTENT_TYPE;
}
default: {
throw new UnsupportedOperationException(" Not supported uri! " + uri.toString());
}
}
}
@Nullable
@Override
public Uri insert(Uri uri, ContentValues values) {
final SQLiteDatabase db = dbHelper.getWritableDatabase();
Uri insertUri;
final int matcher = sUriMatcher.match(uri);
switch (matcher) {
case WEATHER: {
long _id = db.insert(WeatherContract.WeatherEntry.TABLE_NAME, null, values);
if (_id > 0) {
insertUri = WeatherContract.WeatherEntry.buildWeatherUri(_id);
} else {
throw new SQLiteException(" Couldn't insert row in db " + uri);
}
break;
}
case LOCATION: {
long _id = db.insert(WeatherContract.LocationEntry.TABLE_NAME, null, values);
if (_id > 0) {
insertUri = WeatherContract.LocationEntry.buildLocationUri(_id);
} else {
throw new SQLiteException(" Couldn't insert row in db " + uri);
}
break;
}
default: {
throw new UnsupportedOperationException(" Not supported uri! " + uri.toString());
}
}
getContext().getContentResolver().notifyChange(uri, null);
return insertUri;
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
final SQLiteDatabase db = dbHelper.getWritableDatabase();
int nRowsAffected = 0;
final int matcher = sUriMatcher.match(uri);
switch (matcher) {
case WEATHER: {
nRowsAffected = db.delete(WeatherContract.WeatherEntry.TABLE_NAME, selection, selectionArgs);
if (nRowsAffected <= 0) {
Log.d("Deleting rows ", " 0 rows affected by " + uri);
}
break;
}
case LOCATION: {
nRowsAffected = db.delete(WeatherContract.LocationEntry.TABLE_NAME, selection, selectionArgs);
if (nRowsAffected <= 0) {
Log.d("Deleting rows ", " 0 rows affected by " + uri);
}
break;
}
default: {
throw new UnsupportedOperationException(" Not supported uri! " + uri.toString());
}
}
if ((nRowsAffected != 0) && (selection != null)){
getContext().getContentResolver().notifyChange(uri, null);
}
return nRowsAffected;
}
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
final SQLiteDatabase db = dbHelper.getWritableDatabase();
int nRowsAffected = 0;
final int matcher = sUriMatcher.match(uri);
switch (matcher) {
case WEATHER: {
nRowsAffected = db.update(WeatherContract.WeatherEntry.TABLE_NAME, values, selection, selectionArgs);
if (nRowsAffected <= 0) {
Log.d("Updating rows ", " 0 rows affected by " + uri);
}
break;
}
case LOCATION: {
nRowsAffected = db.update(WeatherContract.LocationEntry.TABLE_NAME, values, selection, selectionArgs);
if (nRowsAffected <= 0) {
Log.d("Updating rows ", " 0 rows affected by " + uri);
}
break;
}
default: {
throw new UnsupportedOperationException(" Not supported uri! " + uri.toString());
}
}
if (nRowsAffected != 0) {
getContext().getContentResolver().notifyChange(uri, null);
}
return nRowsAffected;
}
@Override
public int bulkInsert(Uri uri, ContentValues[] values) {
// listed in the main part of the question
}
我希望这能帮助你找出问题所在。谢谢!
UPD2:所以,感谢评论, 1)我已经了解了Uri中路径和查询参数之间的区别(查询参数不是路径的一部分,因此Uri Matcher忽略它们)。 2)当我查看我的db代码的内幕时,结果发现我的查询没有返回值,因为我将错误的数据记录为我的外键。好吧,现在一切正常。