Android。从数据库中获取数据,然后进行网络查询。如何实施?

Android. Fetch data from database and then make network query. How to implement?

我对 Android 开发还很陌生,目前正在尝试编写一个应用程序来显示多个城市明天的天气。抱歉,我可能会在这个问题中使用任何不正确的术语。

我想达到的目标:

应用程序将从本地数据库获取数据,然后对从数据库获取的数据构建 HTTP 查询,获得 JSON 响应并形成列表元素。

我目前拥有的:

除 SQL 功能外的所有内容。

这是我的主要 activity 代码的快照。我使用 LoaderCallbacks<List<Weather>>onCreateLoader(int i, Bundle bundle) 中使用所需参数构建 URI,发送 HTTP 查询并通过 WeatherLoader(this, uriList) 获取数据,并使用 [=16= 形成 List 的元素结果].

public class WeatherActivity extends AppCompatActivity
implements LoaderCallbacks<List<Weather>>,
SharedPreferences.OnSharedPreferenceChangeListener {

private static final int WEATHER_LOADER_ID = 1;
private WeatherAdapter mAdapter;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.weather_activity);

    ListView weatherListView = (ListView) findViewById(R.id.list);

    mEmptyStateTextView = (TextView) findViewById(R.id.empty_view);
    weatherListView.setEmptyView(mEmptyStateTextView);
    mAdapter = new WeatherAdapter(this, new ArrayList<Weather>());
    weatherListView.setAdapter(mAdapter);

    ...

    weatherListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
        @Override
        public void onItemClick(AdapterView<?> adapterView, View view, int position, long l) {
            Weather currentWeather = mAdapter.getItem(position);
            Uri forecastUri = Uri.parse(currentWeather.getUrl());
            Intent websiteIntent = new Intent(Intent.ACTION_VIEW, forecastUri);
            startActivity(websiteIntent);
        }
    });

    ConnectivityManager connMgr = (ConnectivityManager)
            getSystemService(Context.CONNECTIVITY_SERVICE);
    NetworkInfo networkInfo = connMgr.getActiveNetworkInfo();
    if (networkInfo != null && networkInfo.isConnected()) {
        LoaderManager loaderManager = getLoaderManager();
        loaderManager.initLoader(WEATHER_LOADER_ID, null, this);
    } else {
        View loadingIndicator = findViewById(R.id.loading_indicator);
        loadingIndicator.setVisibility(View.GONE);
        mEmptyStateTextView.setText(R.string.no_internet_connection);
    }
}

@Override
public Loader<List<Weather>> onCreateLoader(int i, Bundle bundle) {

    SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this);
    String tempUnit = sharedPrefs.getString(
            getString(R.string.settings_temp_unit_key),
            getString(R.string.settings_temp_unit_default));

    List<String> uriList = new ArrayList<>();

    /*** 
     *
     * Here we input cities for which we want to see the forecast
     *
     * ***/

    List<String> cities = new ArrayList<>();
    cities.add("London,uk");
    cities.add("Kiev,ua");
    cities.add("Berlin,de");
    cities.add("Dubai,ae");

    //For each city in the list generate URI and put it in the URIs list
    for (String city : cities){
        Uri baseUri = Uri.parse(OWM_REQUEST_URL);
        Uri.Builder uriBuilder = baseUri.buildUpon();

        uriBuilder.appendQueryParameter("q", city);
        uriBuilder.appendQueryParameter("cnt", "16");
        uriBuilder.appendQueryParameter("units", tempUnit);
        uriBuilder.appendQueryParameter("appid", "some_key");

        uriList.add(uriBuilder.toString());
    }

    return new WeatherLoader(this, uriList);
}

@Override
public void onLoadFinished(Loader<List<Weather>> loader, List<Weather> weatherList) {

    mAdapter.clear();

    // If there is a valid list of forecasts, then add them to the adapter's
    // data set. This will trigger the ListView to update.
    if (weatherList != null && !weatherList.isEmpty()) {
        mAdapter.addAll(weatherList);
    }
}

@Override
public void onLoaderReset(Loader<List<Weather>> loader) {
    mAdapter.clear();
}

如您所见,城市 "hardcoded" 通过 List<String> cities = new ArrayList<>();onCreateLoader(int i, Bundle bundle)。这就是为什么我决定在我的应用程序中实现 SQL 城市存储。我知道如何使用 ContentProviderCursorAdapter.

在 android 应用程序中实现 SQL 功能

那么问题是什么?

如果我是正确的,如果我们想查询本地数据库,我们应该使用 LoaderManager.LoaderCallbacks<Cursor>

不幸的是,我无法想象如何将当前 LoaderCallbacks<List<Weather>>LoaderCallbacks<Cursor> 合并为一个 activity 以使其按我的意愿工作。

其实我想改变 List<String> cities = new ArrayList<>(); 在类似的事情上 Cursor cursor = new CursorLoader(this, WeatherEntry.CONTENT_URI, projection, null, null, null); 根据 CursorLoader returns.

的结果构建 URI

但是,我们应该在单独的线程中进行 SQL 查询,并在单独的线程中进行 HTTP 查询(!)。我们应该嵌套threads/loaders(在sql获取数据和return一个List<T>范围内的http查询)吗?甚至无法想象这怎么可能,如果可以...

请帮帮我,我卡住了!

好吧,我第一眼看的不是很明显,但我终于解决了这个问题。

在上面的问题中,我们有一个硬编码的城市列表:

List<String> cities = new ArrayList<>();
cities.add("London,uk");
cities.add("Kiev,ua");
cities.add("Berlin,de");
cities.add("Dubai,ae");

即使我们假设我们会将其更改为数据库查询,如下所示:

// Connect to a DB 
...
Cursor forecastCitiesDataCursor = mDb.query(true, WeatherContract.WeatherEntry.TABLE_NAME, projection,
                    null, null, null,
                    null, null, null);
...
// Fetch data from cursor

...我们将在主线程上进行 SQL 查询。所以我们需要一个解决方案。

我找到的最好的东西是 将 SQL 查询放入 CustomLoader class 并在构造函数中传递所需的参数(在我的例子中,它是构建 HTTP 查询的 SharedPreferences 参数)。

这是我的代码:

WeatherActivity.java

public class WeatherActivity extends AppCompatActivity implements LoaderCallbacks<List<Weather>>,
        SharedPreferences.OnSharedPreferenceChangeListener {
    ...
    @Override
    public Loader<List<Weather>> onCreateLoader(int i, Bundle bundle) {

        SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this);
        String tempUnit = sharedPrefs.getString(
                getString(R.string.settings_temp_unit_key),
                getString(R.string.settings_temp_unit_default));

        return new WeatherLoader(this, tempUnit);
    }

    @Override
    public void onLoadFinished(Loader<List<Weather>> loader, List<Weather> weatherList) {
        // Hide loading indicator because the data has been loaded
        View loadingIndicator = findViewById(R.id.loading_indicator);
        loadingIndicator.setVisibility(View.GONE);

        // Set empty state text to display "No forecasts found."
        mEmptyStateTextView.setText(R.string.no_forecasts);

        // Clear the adapter of previous forecasts data
        mAdapter.clear();

        // If there is a valid list of forecasts, then add them to the adapter's
        // data set. This will trigger the ListView to update.
        if (weatherList != null && !weatherList.isEmpty()) {
            mAdapter.addAll(weatherList);
        }
    }

WeatherLoader.java

public class WeatherLoader extends AsyncTaskLoader<List<Weather>> {
...
    // Pass parameters here from WeatherActivity
    public WeatherLoader(Context context, String tmpUnit) {
        super(context);
        mTempUnit = tmpUnit;
    }

    @Override
    protected void onStartLoading() {
        forceLoad();
    }

    /**
     * This is on a background thread.
     */
    @Override
    public List<Weather> loadInBackground() {

        // List for storing built URIs
        List<String> uriList = new ArrayList<>();
        // List for storing forecast cities
        List<String> cities = new ArrayList<>();

        // Define a projection that specifies the columns from the table we care about.
        ...

        Cursor forecastCitiesDataCursor = mDb.query(true, WeatherContract.WeatherEntry.TABLE_NAME, projection,
                null, null, null,
                null, null, null);

        // Get list of cities from cursor
        ...

        //For each city in the list generate URI and put it in the URIs list
        for (String city : cities){
            Uri baseUri = Uri.parse(OWM_REQUEST_URL);
            Uri.Builder uriBuilder = baseUri.buildUpon();

            uriBuilder.appendQueryParameter("q", city);
            uriBuilder.appendQueryParameter("cnt", "16");
            uriBuilder.appendQueryParameter("units", mTempUnit);
            uriBuilder.appendQueryParameter("appid", /*some id*/);

            uriList.add(uriBuilder.toString());
        }

        if (uriList == null) {
            return null;
        }

        // Perform the network request, parse the response, and extract a list of forecasts.
        List<Weather> forecasts = QueryUtils.fetchForecastData(uriList);
        return forecasts;
    }

那么我们得到了什么?

我们在 ArrayAdapter 的工作中实现了持久数据存储,然后用于执行 HTTP 查询。 SQL 查询在单独的线程上,我们不会对应用程序性能有任何问题。

希望解决方案对某人有所帮助,祝你有愉快的一天!