【问题标题】:How to correctly handle data management with SharedPreferences?如何使用 SharedPreferences 正确处理数据管理?
【发布时间】:2020-08-24 11:53:35
【问题描述】:

现在,我正在“优化”我的应用程序。我仍然是一个初学者,所以我所做的基本上是将方法从我的 MainActivity.class 移动到它们单独的类。我相信它叫Encapsulation (如果我错了,请纠正我)

我的应用程序需要:

  1. 从 YouTube 应用获取 YouTube 播放列表链接(带有 Intent,android.intent.action.SEND)。
  2. 使用该链接通过 YouTubeApi 和 Volley 从 Google 服务器获取数据。
  3. 读取收到的数据并将其添加到arrayList<String>

我的YouTubeUsage.java 类应该做的是使用 YouTubeApi 和 Volley 获取数据,然后使用 SharedPreferences 存储数据。保存数据后,将使用我的方法getVideoIds() 在我的ConvertActivity.class(这是专门为android.intent.action.SEND 创建的活动)中读取数据,然后在我的createRecyclerView() 方法中为我的listView 设置适配器。

YouTubeUsage.java

public class YoutubeUsage {

    private Boolean results = false;
    private String mResponse;
    private ArrayList<String> videoIds = new ArrayList<>();
    String Url;

    public String getUrl(String signal) {
        String playlistId = signal.substring(signal.indexOf("=") + 1);
        this.Url = "https://www.googleapis.com/youtube/v3/playlistItems?part=contentDetails%2C%20snippet%2C%20id&playlistId=" +
                playlistId + "&maxResults=25&key=" + "API_KEY";

        return this.Url;
    }            

    public void fetch(String Url, final Context context){
        RequestQueue queue = Volley.newRequestQueue(context);
        StringRequest request = new StringRequest(Request.Method.GET, Url,
                new Response.Listener<String>() {
                    @Override
                    public void onResponse(String response) {
                        sharedPreferences(response, context);
                    }
                }, new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError error) {
                Log.e("VolleyError", Objects.requireNonNull(error.getMessage()));
            }
        });
        queue.add(request);
    }
    private void sharedPreferences(String response, Context context){
        SharedPreferences m = PreferenceManager.getDefaultSharedPreferences(context);
        SharedPreferences.Editor editor = m.edit();
        if (m.contains("serverResponse")){
            if (!m.getString("serverResponse", "").equals(response)){
                editor.remove("serverResponse");
                editor.apply();
                updateSharedPreferences(response, context);
            }
        } else{
            updateSharedPreferences(response, context);
        }

    }
    private void updateSharedPreferences(String mResponse, Context mContext){
        SharedPreferences m = PreferenceManager.getDefaultSharedPreferences(mContext);
        SharedPreferences.Editor editor = m.edit();
        editor.putString("serverResponse", mResponse);
        editor.apply();
    }
}

ConvertActivity.java

public class ConvertActivity extends AppCompatActivity {

    YoutubeUsage youtubeUsage = new YoutubeUsage();
    ArrayList<String> videoIDs = new ArrayList<>();

    String Url = "";

    ListView listView;
    MyCustomAdapter myCustomAdapter;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_convert);
        listView = findViewById(R.id.listview_convert);
        Intent intent = getIntent();
        String action = intent.getAction();
        String type = intent.getType();
        if ("android.intent.action.SEND".equals(action) && "text/plain".equals(type)) {
            Url = youtubeUsage.getUrl(Objects.requireNonNull(intent.getStringExtra("android.intent.extra.TEXT")));
        }
        //I would like to avoid the try/catch below       
        try {
            videoIDs = getVideoIDs(Url, this);
            createRecyclerView(videoIDs);
            Log.i("ResponseVideoIDs", String.valueOf(videoIDs.size()));
        } catch (JSONException e) {
            e.printStackTrace();
        }
    }

    private ArrayList<String> getVideoIDs(String Url, Context context) throws JSONException {
        ArrayList<String> rawVideoIDs = new ArrayList<>();
        youtubeUsage.fetch(Url, context);
        SharedPreferences m = PreferenceManager.getDefaultSharedPreferences(context);
        String serverResponse = m.getString("serverResponse", "");
        JSONObject jsonObject = new JSONObject(serverResponse);
        JSONArray jsonArray = jsonObject.getJSONArray("items");
        for (int i = 0; i<jsonArray.length(); i++){
            JSONObject jsonObject1 = jsonArray.getJSONObject(i);
            JSONObject jsonVideoId =  jsonObject1.getJSONObject("contentDetails");
            rawVideoIDs.add(jsonVideoId.getString("videoId"));
        }
        return rawVideoIDs;
    }
    private void createRecyclerView(ArrayList<String> videoIDs){
        myCustomAdapter = new MyCustomAdapter(this, videoIDs);
        listView.setAdapter(myCustomAdapter);
        myCustomAdapter.notifyDataSetChanged();

    }
}

一切正常,但是,我的 sharedPreferences 永远不会更新。这意味着,如果我将 YouTube 播放列表从 YouTube 应用程序共享到我的应用程序,其中包含 3 个项目,它将正常工作。 Listview 将显示 3 个项目及其相应的 ID。但是,如果我再次分享 YouTube 播放列表,我的应用仍会保留我之前分享的播放列表的数据(即使我关闭它),显示项目编号和上一个链接的 ID。如果我继续一遍又一遍地共享同一个播放列表,它最终会显示正确的项目数和正确的 ID。

我完全可以将 YouTubeUsage.java 中的所有方法放在我的 ConvertActivity.class 中,以防止我使用 SharedPreferences 在两个 java 类之间传输数据。但是,JSON 会引发异常。这意味着我必须用 try/catch 封装我的代码。我想避免这些,因为我需要对 Volley 刚刚收到的数据进行大量操作(检查班级规模,查找特定字符串)。我发现在这些 try/catch 中这样做并不像我想要的那样工作。 (即在 try/catch 之外,即使我在 try/catch 中更新了这些值,这些值也保持不变。

我想知道两件事。

  1. 如何解决这个问题?
  2. 这是最有效的方法(优化)吗? (我想也许 使用 Gson 将 VolleyResponse 转换为字符串,然后存储字符串文件,但我不知道这是否是最好的方法,因为它应该是 临时数据。感觉和以前差不多)。

谢谢!

【问题讨论】:

  • 我可以建议一个简化的开始吗?在您的fetch 方法中,将sharedPreferences(response, context) 行替换为updateSharedPreferences(response, context),然后删除方法sharedPreferences。结果将是相同的,但流程会更加清晰。其次,您的onResponse 方法是否被成功调用,正如您所期望的那样,并且使用有意义的response 字符串;在这个方法的第一行放一个Log.d()来检查。
  • 我将sharedPreferences(response, context) 替换为updateSharedPreferences(response, context)onResponse 方法被正确调用:我检查了 Log.d("ResponseCheck", response); 并返回完整的 JSON 文件。但是,android.intent.action.SEND 需要多次调用才能真正在 logcat 中显示响应。如果我使用带有 android.intent.action.SEND 的不同 YouTube 链接,JSON 不会更新。我对它为什么这样做感到困惑。是因为 SharedPreferences 还是 Volley Request?
  • 也许您的 RequestQueue 在收到响应之前就超出了范围。将声明 RequestQueue queue; 放在类级别,而不是在您的 fetch 方法中。
  • 我已将RequestQueue queue; 置于类级别(低于public class YouTubeUsage{)并将fetch 方法的第一行更改为queue = Volley.newRequestQueue(context);。截至目前,当应用程序在后台运行时 JSON 不会更新(当我处理多任务时。我可能应该实现OnPause?)但是当我关闭应用程序并再次打开它时它会更新。但是,即使我从 Volley 收到正确的响应,recyclerView 仍然显示来自先前响应的数据...它仅在我关闭应用程序并再次执行时显示正确的数据...
  • 尝试将您对 createRecyclerView 的调用转移到您的 Activity 的 onResume 方法中。

标签: json android-studio youtube-api sharedpreferences android-volley


【解决方案1】:

关于事件顺序的假设存在问题。 Volley 会异步处理请求,所以建议在这里实现观察者模式。

创建一个仅包含以下内容的新 Java 文件:

interface MyNetworkResponse {
   void goodResponse(String responseString);
}

然后确定ConvertActivityimplements MyNetworkResponse并创建方法:

void goodResponse(String responseString) {
   // handle a positive response here, i.e. extract the JSON and send to your RecyclerView.
}

在您的活动中。

在您的 YoutubeUsage 构造函数中,传入 Activity 上下文 (YoutubeUsage),然后将其存储在名为 ctxYoutubeUsage 实例变量中。

onCreate中,创建YoutubeUsage的实例并传入this

onResponse 中只需致电ctx.goodResponse(response)

将以下块修改为:

if ("android.intent.action.SEND".equals(action) && "text/plain".equals(type)) {
        Url = youtubeUsage.getUrl(Objects.requireNonNull(intent.getStringExtra("android.intent.extra.TEXT")));
        youtubeUsage.fetch(Url);
}

onCreate 中删除try/catch

而且根本不需要使用SharedPreferences

更新

试试这个代码:

MyNetworkResponse.java

interface MyNetworkResponse {
    void goodResponse(String responseString);
    void badResponse(VolleyError error);
}

YoutubeUsage.java

class YoutubeUsage {

private RequestQueue queue;
private MyNetworkResponse callback;

YoutubeUsage(Object caller) {
    this.callback = (MyNetworkResponse) caller;
    queue = Volley.newRequestQueue((Context) caller);
}

static String getUrl(String signal) {
    String playlistId = signal.substring(signal.indexOf("=") + 1);
    return "https://www.googleapis.com/youtube/v3/playlistItems?part=contentDetails%2C%20snippet%2C%20id&playlistId=" + playlistId + "&maxResults=25&key=" + "API_KEY";
}

void fetch(String url){
    StringRequest request = new StringRequest(Request.Method.GET, url,
            new Response.Listener<String>() {
                @Override
                public void onResponse(String response) {
                    callback.goodResponse(response);
                }
            }, new Response.ErrorListener() {
        @Override
        public void onErrorResponse(VolleyError error) {
            callback.badResponse(error);
        }
    });
    queue.add(request);
}

}

ConvertActivity.java

public class ConvertActivity extends AppCompatActivity implements MyNetworkResponse {

YoutubeUsage youtubeUsage;
ArrayList<String> videoIDs = new ArrayList<>();

ListView listView;
MyCustomAdapter myCustomAdapter;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_convert);
    listView = findViewById(R.id.listview_convert);
    youtubeUsage = new YoutubeUsage(this);
    Intent intent = getIntent();
    String action = intent.getAction();
    String type = intent.getType();
    if ("android.intent.action.SEND".equals(action) && "text/plain".equals(type)) {
        String url = YoutubeUsage.getUrl(Objects.requireNonNull(intent.getStringExtra("android.intent.extra.TEXT")));
        youtubeUsage.fetch(url);
    }
}

private ArrayList<String> getVideoIDs(String serverResponse) throws JSONException {
    ArrayList<String> rawVideoIDs = new ArrayList<>();
    JSONObject jsonObject = new JSONObject(serverResponse);
    JSONArray jsonArray = jsonObject.getJSONArray("items");
    for (int i = 0; i < jsonArray.length(); i++) {
        JSONObject jsonObject1 = jsonArray.getJSONObject(i);
        JSONObject jsonVideoId = jsonObject1.getJSONObject("contentDetails");
        rawVideoIDs.add(jsonVideoId.getString("videoId"));
    }
    return rawVideoIDs;
}

private void createRecyclerView(ArrayList<String> videoIDs) {
    myCustomAdapter = new MyCustomAdapter(this, videoIDs);
    listView.setAdapter(myCustomAdapter);
    myCustomAdapter.notifyDataSetChanged();
}

@Override
public void goodResponse(String responseString) {
    Log.d("Convert:goodResp", "[" + responseString + "]");
    try {
        ArrayList<String> rawVideoIDs = getVideoIDs(responseString);
        createRecyclerView(rawVideoIDs);
    } catch (JSONException e) {
        // handle JSONException, e.g. malformed response from server.
    }
}

@Override
public void badResponse(VolleyError error) {
    // handle unwanted server response.
}

}

【讨论】:

  • 我不确定我是否正确理解了我需要传递给YouTubeUsage 构造函数的内容。首先,我为YouTubeUsage 创建了一个构造函数,并在类级别(YouTubeUsage)添加了Context ctx;。在我的ConvertActivity 中,我调用了 YouTubeUsage 类并将this 作为参数传递:youtubeUsage = new YoutubeUsage(this);。但是,我不明白如何在 Volley 的OnResponse 中调用ctx.goodResponse(response)。 Android Studio 说:Cannot resolve method "goodResponse(java.lang.String)" ctx 应该是 Context 对象还是 MyNetworkResponse 对象?
  • ctx 的类型为 MyNetworkResponse。对不起,如果我不清楚。打字速度非常快!称之为更有意义的东西。我在想Context
猜你喜欢
  • 2011-07-19
  • 2020-03-07
  • 2016-08-23
  • 2013-08-18
  • 2016-12-11
  • 2016-04-14
  • 2011-12-05
  • 2021-09-24
  • 1970-01-01
相关资源
最近更新 更多