内容提供者故障
Content Provider fault
在我的应用程序中,我使用了内容提供程序。如您所知,内容提供者是客户端和 SQLite
之间的中间人。在我的例子中,我使用 volley 从服务器检索数据,将它们存储在 SQLite
中,最后使用 ContentResolver
对象和 LoaderManager
接口(具有 onCreateLoader、onLoadFinished、onLoaderReset)读取它们).我还使用了一项服务,因为我想在应用程序关闭时 运行 我的网络服务。
我的服务
public class MyService extends IntentService {
private final String LOG_TAG = MyService.class.getSimpleName();
public MyService() {
super("My Service");
}
@Override
protected void onHandleIntent(Intent intent) {
updateCityList();
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
public void updateCityList() {
cityList.clear();
// Instantiate the RequestQueue.
RequestQueue queue = Volley.newRequestQueue(this);
// Request a string response from the provided URL.
JsonArrayRequest jsObjRequest = new JsonArrayRequest(Request.Method.GET,
API.API_URL, new Response.Listener<JSONArray>() {
@Override
public void onResponse(JSONArray response) {
Log.d(TAG, response.toString());
//hidePD();
// Parse json data.
// Declare the json objects that we need and then for loop through the children array.
// Do the json parse in a try catch block to catch the exceptions
try {
for (int i = 0; i < response.length(); i++) {
JSONObject post = response.getJSONObject(i);
MyCity item = new MyCity();
item.setName(post.getString("title"));
item.setImage(API.IMAGE_URL + post.getString("image"));
ContentValues imageValues = new ContentValues();
imageValues.put(MyCityContract.MyCityEntry._ID, post.getString("id"));
imageValues.put(MyCityContract.MyCityEntry.COLUMN_NAME, post.getString("title"));
imageValues.put(MyCityContract.MyCityEntry.COLUMN_ICON, post.getString("image"));
getContentResolver().insert(MyCityContract.MyCityEntry.CONTENT_URI, imageValues);
cityList.add(item);
cityList.add(item);
}
} catch (JSONException e) {
e.printStackTrace();
}
// Update list by notifying the adapter of changes
myCityAdpapter.notifyDataSetChanged();
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
VolleyLog.d(TAG, "Error: " + error.getMessage());
//hidePD();
}
});
queue.add(jsObjRequest);
}
static public class AlarmReceiver extends BroadcastReceiver{
@Override
public void onReceive(Context context, Intent intent) {
Intent sendIntent = new Intent(context, MyService.class);
context.startService(sendIntent);
}
}
}
MainActivityFragment
public class MainActivityFragment extends Fragment implements
LoaderManager.LoaderCallbacks<Cursor>{
static public ArrayList<MyCity> cityList;
public String [] MY_CITY_PROJECTIONS = {MyCityContract.MyCityEntry._ID,
MyCityContract.MyCityEntry.COLUMN_NAME,
MyCityContract.MyCityEntry.COLUMN_ICON};
private static final String LOG_TAG =
MainActivityFragment.class.getSimpleName();
public static MyCityAdpapter myCityAdpapter;
private static final int CURSOR_LOADER_ID = 0;
private GridView mGridView;
public MainActivityFragment() {
// Required empty public constructor
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Add this line in order for this fragment to handle menu events.
setHasOptionsMenu(true);
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.refresh, menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
if (id == R.id.action_refresh) {
return true;
}
return super.onOptionsItemSelected(item);
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
// inflate fragment_main layout
final View rootView = inflater.inflate(R.layout.fragment_main_activity, container, false);
cityList = new ArrayList<>();
// initialize our FlavorAdapter
myCityAdpapter = new MyCityAdpapter(getActivity(), null, 0, CURSOR_LOADER_ID);
// initialize mGridView to the GridView in fragment_main.xml
mGridView = (GridView) rootView.findViewById(R.id.flavors_grid);
// set mGridView adapter to our CursorAdapter
mGridView.setAdapter(myCityAdpapter);
Cursor c =
getActivity().getContentResolver().query(MyCityContract.MyCityEntry.CONTENT_URI,
new String[]{MyCityContract.MyCityEntry._ID},
null,
null,
null);
if (c.getCount() == 0){
updateCityData();
}
// initialize loader
getLoaderManager().initLoader(CURSOR_LOADER_ID, null, this);
super.onCreate(savedInstanceState);
return rootView;
}
@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args){
return new CursorLoader(getActivity(),
MyCityContract.MyCityEntry.CONTENT_URI,
MY_CITY_PROJECTIONS,
null,
null,
null);
}
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
myCityAdpapter.swapCursor(data);
}
@Override
public void onLoaderReset(Loader<Cursor> loader){
myCityAdpapter.swapCursor(null);
}
public void updateCityData() {
Intent alarmIntent = new Intent(getActivity(), MyService.AlarmReceiver.class);
//Wrap in a pending intent which only fires once.
PendingIntent pi = PendingIntent.getBroadcast(getActivity(), 0,alarmIntent,PendingIntent.FLAG_ONE_SHOT);//getBroadcast(context, 0, i, 0);
AlarmManager am=(AlarmManager)getActivity().getSystemService(Context.ALARM_SERVICE);
//Set the AlarmManager to wake up the system.
am.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + 5000, pi);
}
}
我刚刚设置了一个警报管理器,让我的服务在 5 秒后 运行。这真的只是为了测试。无论如何,这是我的问题。当我第一次启动该应用程序时,我的屏幕上没有显示任何内容。但是,当我退出并再次启动它时,我可以在我的网格视图中看到所有图像。为什么会这样?为了更清楚
当我第一次启动应用程序时:
10-16 12:07:00.799 16685-16685/theo.testing.androidcustomloaders D/ContentValues: [{"id":"15","title":"The Gate of Larissa","image":"larissa17.png"},{"id":"14","title":"Larissa Fair","image":"larissa14.png"},{"id":"13","title":"Larissa Fair","image":"larissa13.png"},{"id":"12","title":"AEL FC Arena","image":"larissa12.png"},{"id":"11","title":"AEL FC Arena","image":"larissa11.png"},{"id":"10","title":"Alcazar Park","image":"larissa10.png"},{"id":"9","title":"Alcazar Park","image":"larissa9.png"},{"id":"8","title":"Church","image":"larissa8.png"},{"id":"7","title":"Church","image":"larissa7.png"},{"id":"6","title":"Old trains","image":"larissa6.png"},{"id":"5","title":"Old trains","image":"larissa5.png"},{"id":"4","title":"Munipality Park","image":"larissa4.png"},{"id":"3","title":"Munipality Park","image":"larissa3.png"},{"id":"2","title":"Ancient Theatre - Larissa","image":"larissa2.png"},{"id":"1","title":"Ancient Theatre - Larissa","image":"larissa1.png"}]
为了显示数据,我需要退出应用程序并重新启动它。为什么会这样?我的代码有问题吗?
LoadManager 不处理您在数据库中的更改,因为它与数据库没有任何连接。你必须注册观察者才能处理这些东西。
在您的 myCityProvider
中,在 query(...)
方法中缺少方法 setNotificationUri
。应该设置在最后。
这里修改了你的query
方法:
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
Cursor retCursor;
switch (sUriMatcher.match(uri)) {
// All Flavors selected
case MY_CITY: {
retCursor = myCityDbHelper.getReadableDatabase().query(
MyCityContract.MyCityEntry.TABLE_MY_CITY,
projection,
selection,
selectionArgs,
null,
null,
sortOrder);
break;
}
// Individual flavor based on Id selected
case MY_CITY_WITH_ID: {
retCursor = myCityDbHelper.getReadableDatabase().query(
MyCityContract.MyCityEntry.TABLE_MY_CITY,
projection,
MyCityContract.MyCityEntry._ID + " = ?",
new String[]{String.valueOf(ContentUris.parseId(uri))},
null,
null,
sortOrder);
break;
}
default: {
// By default, we assume a bad URI
throw new UnsupportedOperationException("Unknown uri: " + uri);
}
}
if (retCursor != null) {
retCursor.setNotificationUri(getContext().getContentResolver(), uri);
}
return retCursor;
}
我检查了您的 git 存储库,我认为您应该修复您的 MainActivityFragment
。你在 onCreateView
中做所有事情,但你应该在这里做所有与视图相关的事情或只是 return 膨胀视图。之后,您可以在 onViewCreated
.
中完成剩下的工作
你应该这样做:
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_main_activity, container, false);
}
@Override
public void onViewCreated(View rootView, @Nullable Bundle savedInstanceState) {
super.onViewCreated(rootView, savedInstanceState);
myCityAdpapter = new MyCityAdpapter(getActivity(), null, 0);
mGridView = (GridView) rootView.findViewById(R.id.flavors_grid);
mGridView.setAdapter(myCityAdpapter);
getLoaderManager().initLoader(CURSOR_LOADER_ID, null, this);
}
@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
switch (id) {
case CURSOR_LOADER_ID:
return new CursorLoader(getActivity(),
MyCityContract.MyCityEntry.CONTENT_URI,
null,
null,
null,
null);
default:
throw new IllegalArgumentException("id not handled!");
}
}
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
switch (loader.getId()) {
case CURSOR_LOADER_ID:
if (data == null || data.getCount() == 0) {
updateCityData();
} else {
myCityAdpapter.swapCursor(data);
}
break;
}
}
@Override
public void onLoaderReset(Loader<Cursor> loader) {
myCityAdpapter.swapCursor(null);
}
多亏了:
- 如果应用程序打开,加载程序将加载他拥有的所有内容(可以是 0 个项目),但如果没有任何项目,它将调用服务下载更多内容并存储在数据库中
- 如果您通过服务添加任何数据,将再次调用 onLoadFinished 并刷新适配器
在我的应用程序中,我使用了内容提供程序。如您所知,内容提供者是客户端和 SQLite
之间的中间人。在我的例子中,我使用 volley 从服务器检索数据,将它们存储在 SQLite
中,最后使用 ContentResolver
对象和 LoaderManager
接口(具有 onCreateLoader、onLoadFinished、onLoaderReset)读取它们).我还使用了一项服务,因为我想在应用程序关闭时 运行 我的网络服务。
我的服务
public class MyService extends IntentService {
private final String LOG_TAG = MyService.class.getSimpleName();
public MyService() {
super("My Service");
}
@Override
protected void onHandleIntent(Intent intent) {
updateCityList();
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
public void updateCityList() {
cityList.clear();
// Instantiate the RequestQueue.
RequestQueue queue = Volley.newRequestQueue(this);
// Request a string response from the provided URL.
JsonArrayRequest jsObjRequest = new JsonArrayRequest(Request.Method.GET,
API.API_URL, new Response.Listener<JSONArray>() {
@Override
public void onResponse(JSONArray response) {
Log.d(TAG, response.toString());
//hidePD();
// Parse json data.
// Declare the json objects that we need and then for loop through the children array.
// Do the json parse in a try catch block to catch the exceptions
try {
for (int i = 0; i < response.length(); i++) {
JSONObject post = response.getJSONObject(i);
MyCity item = new MyCity();
item.setName(post.getString("title"));
item.setImage(API.IMAGE_URL + post.getString("image"));
ContentValues imageValues = new ContentValues();
imageValues.put(MyCityContract.MyCityEntry._ID, post.getString("id"));
imageValues.put(MyCityContract.MyCityEntry.COLUMN_NAME, post.getString("title"));
imageValues.put(MyCityContract.MyCityEntry.COLUMN_ICON, post.getString("image"));
getContentResolver().insert(MyCityContract.MyCityEntry.CONTENT_URI, imageValues);
cityList.add(item);
cityList.add(item);
}
} catch (JSONException e) {
e.printStackTrace();
}
// Update list by notifying the adapter of changes
myCityAdpapter.notifyDataSetChanged();
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
VolleyLog.d(TAG, "Error: " + error.getMessage());
//hidePD();
}
});
queue.add(jsObjRequest);
}
static public class AlarmReceiver extends BroadcastReceiver{
@Override
public void onReceive(Context context, Intent intent) {
Intent sendIntent = new Intent(context, MyService.class);
context.startService(sendIntent);
}
}
}
MainActivityFragment
public class MainActivityFragment extends Fragment implements
LoaderManager.LoaderCallbacks<Cursor>{
static public ArrayList<MyCity> cityList;
public String [] MY_CITY_PROJECTIONS = {MyCityContract.MyCityEntry._ID,
MyCityContract.MyCityEntry.COLUMN_NAME,
MyCityContract.MyCityEntry.COLUMN_ICON};
private static final String LOG_TAG =
MainActivityFragment.class.getSimpleName();
public static MyCityAdpapter myCityAdpapter;
private static final int CURSOR_LOADER_ID = 0;
private GridView mGridView;
public MainActivityFragment() {
// Required empty public constructor
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Add this line in order for this fragment to handle menu events.
setHasOptionsMenu(true);
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.refresh, menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
if (id == R.id.action_refresh) {
return true;
}
return super.onOptionsItemSelected(item);
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
// inflate fragment_main layout
final View rootView = inflater.inflate(R.layout.fragment_main_activity, container, false);
cityList = new ArrayList<>();
// initialize our FlavorAdapter
myCityAdpapter = new MyCityAdpapter(getActivity(), null, 0, CURSOR_LOADER_ID);
// initialize mGridView to the GridView in fragment_main.xml
mGridView = (GridView) rootView.findViewById(R.id.flavors_grid);
// set mGridView adapter to our CursorAdapter
mGridView.setAdapter(myCityAdpapter);
Cursor c =
getActivity().getContentResolver().query(MyCityContract.MyCityEntry.CONTENT_URI,
new String[]{MyCityContract.MyCityEntry._ID},
null,
null,
null);
if (c.getCount() == 0){
updateCityData();
}
// initialize loader
getLoaderManager().initLoader(CURSOR_LOADER_ID, null, this);
super.onCreate(savedInstanceState);
return rootView;
}
@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args){
return new CursorLoader(getActivity(),
MyCityContract.MyCityEntry.CONTENT_URI,
MY_CITY_PROJECTIONS,
null,
null,
null);
}
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
myCityAdpapter.swapCursor(data);
}
@Override
public void onLoaderReset(Loader<Cursor> loader){
myCityAdpapter.swapCursor(null);
}
public void updateCityData() {
Intent alarmIntent = new Intent(getActivity(), MyService.AlarmReceiver.class);
//Wrap in a pending intent which only fires once.
PendingIntent pi = PendingIntent.getBroadcast(getActivity(), 0,alarmIntent,PendingIntent.FLAG_ONE_SHOT);//getBroadcast(context, 0, i, 0);
AlarmManager am=(AlarmManager)getActivity().getSystemService(Context.ALARM_SERVICE);
//Set the AlarmManager to wake up the system.
am.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + 5000, pi);
}
}
我刚刚设置了一个警报管理器,让我的服务在 5 秒后 运行。这真的只是为了测试。无论如何,这是我的问题。当我第一次启动该应用程序时,我的屏幕上没有显示任何内容。但是,当我退出并再次启动它时,我可以在我的网格视图中看到所有图像。为什么会这样?为了更清楚
当我第一次启动应用程序时:
10-16 12:07:00.799 16685-16685/theo.testing.androidcustomloaders D/ContentValues: [{"id":"15","title":"The Gate of Larissa","image":"larissa17.png"},{"id":"14","title":"Larissa Fair","image":"larissa14.png"},{"id":"13","title":"Larissa Fair","image":"larissa13.png"},{"id":"12","title":"AEL FC Arena","image":"larissa12.png"},{"id":"11","title":"AEL FC Arena","image":"larissa11.png"},{"id":"10","title":"Alcazar Park","image":"larissa10.png"},{"id":"9","title":"Alcazar Park","image":"larissa9.png"},{"id":"8","title":"Church","image":"larissa8.png"},{"id":"7","title":"Church","image":"larissa7.png"},{"id":"6","title":"Old trains","image":"larissa6.png"},{"id":"5","title":"Old trains","image":"larissa5.png"},{"id":"4","title":"Munipality Park","image":"larissa4.png"},{"id":"3","title":"Munipality Park","image":"larissa3.png"},{"id":"2","title":"Ancient Theatre - Larissa","image":"larissa2.png"},{"id":"1","title":"Ancient Theatre - Larissa","image":"larissa1.png"}]
为了显示数据,我需要退出应用程序并重新启动它。为什么会这样?我的代码有问题吗?
LoadManager 不处理您在数据库中的更改,因为它与数据库没有任何连接。你必须注册观察者才能处理这些东西。
在您的 myCityProvider
中,在 query(...)
方法中缺少方法 setNotificationUri
。应该设置在最后。
这里修改了你的query
方法:
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
Cursor retCursor;
switch (sUriMatcher.match(uri)) {
// All Flavors selected
case MY_CITY: {
retCursor = myCityDbHelper.getReadableDatabase().query(
MyCityContract.MyCityEntry.TABLE_MY_CITY,
projection,
selection,
selectionArgs,
null,
null,
sortOrder);
break;
}
// Individual flavor based on Id selected
case MY_CITY_WITH_ID: {
retCursor = myCityDbHelper.getReadableDatabase().query(
MyCityContract.MyCityEntry.TABLE_MY_CITY,
projection,
MyCityContract.MyCityEntry._ID + " = ?",
new String[]{String.valueOf(ContentUris.parseId(uri))},
null,
null,
sortOrder);
break;
}
default: {
// By default, we assume a bad URI
throw new UnsupportedOperationException("Unknown uri: " + uri);
}
}
if (retCursor != null) {
retCursor.setNotificationUri(getContext().getContentResolver(), uri);
}
return retCursor;
}
我检查了您的 git 存储库,我认为您应该修复您的 MainActivityFragment
。你在 onCreateView
中做所有事情,但你应该在这里做所有与视图相关的事情或只是 return 膨胀视图。之后,您可以在 onViewCreated
.
你应该这样做:
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_main_activity, container, false);
}
@Override
public void onViewCreated(View rootView, @Nullable Bundle savedInstanceState) {
super.onViewCreated(rootView, savedInstanceState);
myCityAdpapter = new MyCityAdpapter(getActivity(), null, 0);
mGridView = (GridView) rootView.findViewById(R.id.flavors_grid);
mGridView.setAdapter(myCityAdpapter);
getLoaderManager().initLoader(CURSOR_LOADER_ID, null, this);
}
@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
switch (id) {
case CURSOR_LOADER_ID:
return new CursorLoader(getActivity(),
MyCityContract.MyCityEntry.CONTENT_URI,
null,
null,
null,
null);
default:
throw new IllegalArgumentException("id not handled!");
}
}
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
switch (loader.getId()) {
case CURSOR_LOADER_ID:
if (data == null || data.getCount() == 0) {
updateCityData();
} else {
myCityAdpapter.swapCursor(data);
}
break;
}
}
@Override
public void onLoaderReset(Loader<Cursor> loader) {
myCityAdpapter.swapCursor(null);
}
多亏了:
- 如果应用程序打开,加载程序将加载他拥有的所有内容(可以是 0 个项目),但如果没有任何项目,它将调用服务下载更多内容并存储在数据库中
- 如果您通过服务添加任何数据,将再次调用 onLoadFinished 并刷新适配器