#android #service #android-contentprovider
#Android #Обслуживание #android-contentprovider
Вопрос:
В моем приложении я использую контент-провайдера. Как вы знаете, контент-провайдер является посредником между клиентом и. SQLite
В моем случае я извлекаю данные с сервера с помощью volley, сохраняю их SQLite
и, наконец, считываю их с помощью ContentResolver
объекта и LoaderManager
интерфейса (который имеет onCreateLoader,onLoadFinished,onLoaderReset). Я также использую сервис, так как хочу запустить свой веб-сервис, когда приложение закрыто.
MyService
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 секунд. Это только для тестирования на самом деле. В любом случае, вот моя проблема. Когда я запускаю приложение в первый раз, на моем экране ничего не отображается. Однако, когда я выхожу и запускаю его снова, я вижу все изображения в своем gridview. Почему это происходит? Чтобы сделать более понятным
Когда я запускаю приложение в первый раз:
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"}]
Для отображения данных мне нужно выйти из приложения и запустить его снова. Почему это происходит? Что-то не так с моим кодом?
Комментарии:
1. Интересно, какое отношение ваша проблема имеет к контент-провайдеру.
2. Итак, в первый раз изображения еще не загружены? И во второй раз изображения уже загружены?
3. @greenapps. Yes. Изображения необходимо загружать при первом запуске приложения. И как только вы это сделаете, вы снова откроете приложение, чтобы увидеть их.
4. и на самом деле с контент-провайдером все в порядке, так как я тоже немного поработал с ним.
5. опубликуйте свой пользовательский
ContentProvider
код, в вашей службе вызывайте только методы поставщика, чтобы вставлять / обновлять / удалять ваши данные
Ответ №1:
LoadManager не обрабатывает ваши изменения в базе данных, поскольку он не имеет к ней никакого отношения. Вы должны зарегистрировать observer для обработки этого материала.
В вашем 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
но вы должны делать здесь все, что связано с просмотром, или просто возвращать завышенный вид. И после этого вы можете сделать все остальное 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 будет вызван снова и обновит адаптер
Комментарии:
1. Боже! Сейчас он работает. Вы были правы. Я забыл setNotificationUri . Большое спасибо, сэр!
2. Еще раз извините. Я заметил кое-что еще. Если я ввожу новое изображение на своем сервере и запускаю приложение, оно не отображается в сетке! Чтобы увидеть это новое изображение, мне нужно зайти в настройки -> MyApp -> Очистить данные и запустить его снова.lol. Почему это происходит? Нужно ли что-то делать с методом обновления в ContentProvider? Спасибо.
3. или мне нужно создать syncadapter?
4. Пожалуйста, прочтите последнее предложение, которое я написал «если приложение откроется, загрузчик загрузит все, что у него есть (может быть 0 элементов), но если каких-либо элементов нет, он вызовет службу, чтобы загрузить больше и сохранить в БД». После первого запуска у вас уже есть какие-либо элементы, поэтому
updateCityData
они не запускаются. Попробуйте принудительно запустить его после вызоваswapCursor
, а после этогоupdateCityData
перед разбором изображений удалите все из базы данных (или проверьте, что у вас есть, и вставьте только то, что является новым).5. ДА. Теперь лучше. Но когда я удаляю изображение со своего сервера, это изображение все еще там. Пожалуйста, посмотрите мой обновленный код в github. Я также добавил syncadapter.