2

我使用来自 tutsplus.com 的关于为 Android 构建 twitter 客户端的教程。我已经构建了整个应用程序,当我运行它时,Eclipse 的 logcat 中出现以下错误。

android.os.NetworkOnMainThreadException - 阅读一些东西,它必须处理我正在调用的 API 级别。目前我正在为 API 15 的 4.0.3 构建。在 API 11 之后,您不能在与 UI 相同的线程中进行网络调用。这背后的原因不是为了让 UI 停止或崩溃。网络调用必须在 AsyncTask 或 Service 中。

我的问题/问题/问题的长短是教程制作者无助于纠正问题,所以这就是我在这里的原因。我在下面发布代码,希望有人可以帮助我将网络部分移动到 AsyncTask 或服务中。

package com.jasonsdesign.tweetxy;

import twitter4j.ProfileImage;
import twitter4j.Twitter;
import twitter4j.TwitterException;
import twitter4j.TwitterFactory;
import twitter4j.auth.AccessToken;
import twitter4j.auth.RequestToken;
import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.database.DatabaseUtils;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
import android.os.Bundle;
import android.provider.BaseColumns;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.ListView;

public class TweetxyActivity extends Activity implements OnClickListener {

/**developer account key for this app*/
public final static String TWIT_KEY = "";
/**developer secret for the app*/
public final static String TWIT_SECRET = "";
/**app url*/
public final static String TWIT_URL = "tweetxy-android:///";

private String LOG_TAG = "TweetxyActivity";

/**Twitter instance*/
private Twitter tweetxyTwitter;
/**request token for accessing user account*/
private RequestToken tweetxyRequestToken;
/**shared preferences to store user details*/
private SharedPreferences tweetxyPrefs;
/**main view for the home timeline*/
private ListView homeTimeline;
/**database helper for update data*/
private TweetxyDataHelper timelineHelper;
/**update database*/
private SQLiteDatabase timelineDB;
/**cursor for handling data*/
private Cursor timelineCursor;
/**adapter for mapping data*/
private UpdateAdapter timelineAdapter;
/**broadcast receiver for when new updates are available*/
private BroadcastReceiver tweetxyStatusReceiver;

//set the profile image display
ProfileImage.ImageSize imageSize = ProfileImage.NORMAL;

/*
 * onCreate behaves differently on first run and subsequent runs
 * - if first run take to Twitter sign in page to grant the app permission
 * - subsequent runs fetch and present the user home timeline
 */
@Override
public void onCreate(Bundle savedInstanceState) {

    super.onCreate(savedInstanceState);
    //get the preferences
    tweetxyPrefs = getSharedPreferences("TweetxyPrefs", 0);
    //find out if the user preferences are set
    if(tweetxyPrefs.getString("user_token", null)==null) {
            //no user preferences so prompt to sign in
        setContentView(R.layout.main);
            //get a twitter instance for authentication
        tweetxyTwitter = new TwitterFactory().getInstance();
            //pass developer key and secret
        tweetxyTwitter.setOAuthConsumer(TWIT_KEY, TWIT_SECRET);
            //try to get request token
        try 
        {
                //get authentication request token
            tweetxyRequestToken = tweetxyTwitter.getOAuthRequestToken(TWIT_URL);
        }
        catch(TwitterException te) { Log.e(LOG_TAG, "TE "+te.getMessage()); }
        //setup button for click listener
        Button signIn = (Button)findViewById(R.id.signin);
        signIn.setOnClickListener(this);
    }
    else 
    {
        //user preferences are set - get timeline
        setupTimeline();
    }

}

/**
 * Click listener handles sign in and tweet button presses
 */
public void onClick(View v) {
    //find view
    switch(v.getId()) {
        //sign in button pressed
    case R.id.signin:
            //take user to twitter authentication web page to allow app access to their twitter account
        String authURL = tweetxyRequestToken.getAuthenticationURL();
        startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(authURL)));
        break;
        //user has pressed tweet button
    case R.id.tweetbtn:
            //launch tweet activity
        startActivity(new Intent(this, TweetxyTweet.class));
        break;
    default:
        break;
    }
}

/*
 * onNewIntent fires when user returns from Twitter authentication Web page
 */
@Override
protected void onNewIntent(Intent intent) {

    super.onNewIntent(intent);
    //get the retrieved data
    Uri twitURI = intent.getData();
    //make sure the url is correct
    if(twitURI!=null && twitURI.toString().startsWith(TWIT_URL)) 
    {
        //is verification - get the returned data
        String oaVerifier = twitURI.getQueryParameter("oauth_verifier");
        //attempt to retrieve access token
        try
        {
                //try to get an access token using the returned data from the verification page
            AccessToken accToken = tweetxyTwitter.getOAuthAccessToken(tweetxyRequestToken, oaVerifier);
                //add the token and secret to shared prefs for future reference
            tweetxyPrefs.edit()
                .putString("user_token", accToken.getToken())
                .putString("user_secret", accToken.getTokenSecret())
                .commit();
                //display the timeline
            setupTimeline();

        }
        catch (TwitterException te)
        { Log.e(LOG_TAG, "Failed to get access token: "+te.getMessage()); }
    }
}

/**
 * setupTimeline displays the user's main home Twitter timeline
 */

private void setupTimeline() {

    //set the layout
    setContentView(R.layout.timeline);
        //setup onclick listener for tweet button
    LinearLayout tweetClicker = (LinearLayout)findViewById(R.id.tweetbtn);
    tweetClicker.setOnClickListener(this);
        //retrieve the timeline
    try 
    {
            //get reference to the list view
        homeTimeline = (ListView)findViewById(R.id.homeList);
            //instantiate database helper
        timelineHelper = new TweetxyDataHelper(this);
            //get the database
        timelineDB = timelineHelper.getReadableDatabase();

        //query the database, most recent tweets first
        timelineCursor = timelineDB.query("home", null, null, null, null, null, "update_time DESC");
        //manage the updates using a cursor
        startManagingCursor(timelineCursor);
        //instantiate adapter
        timelineAdapter = new UpdateAdapter(this, timelineCursor);
        //apply the adapter to the timeline view
        //this will make it populate the new update data in the view
        homeTimeline.setAdapter(timelineAdapter);
        //instantiate receiver class for finding out when new updates are available
        tweetxyStatusReceiver = new TwitterUpdateReceiver();
        //register for updates
        registerReceiver(tweetxyStatusReceiver, new IntentFilter("TWITTER_UPDATES"));

        //start the service for updates now
        this.getApplicationContext().startService(new Intent(this.getApplicationContext(), TimelineService.class));
    }
    catch(Exception te) { Log.e(LOG_TAG, "Failed to fetch timeline: "+te.getMessage()); }
}

/**
 * Class to implement broadcast receipt for new updates
 */
class TwitterUpdateReceiver extends BroadcastReceiver 
{
    /**
     * When new updates are available, a broadcast is received here
     */

    @Override
    public void onReceive(Context context, Intent intent) {
        //delete db rows
        int rowLimit = 100;
        if(DatabaseUtils.queryNumEntries(timelineDB, "home")>rowLimit) {
            String deleteQuery = "DELETE FROM home WHERE "+BaseColumns._ID+" NOT IN " +
                    "(SELECT "+BaseColumns._ID+" FROM home ORDER BY "+"update_time DESC " +
                            "limit "+rowLimit+")";  
            timelineDB.execSQL(deleteQuery);
        }       

        timelineCursor = timelineDB.query("home", null, null, null, null, null, "update_time DESC");
        startManagingCursor(timelineCursor);
        timelineAdapter = new UpdateAdapter(context, timelineCursor);
        homeTimeline.setAdapter(timelineAdapter);
    }
}

/*
 * When the class is destroyed, close database and service classes
 */
@Override
public void onDestroy() {
    super.onDestroy();
    try 
    {
        //stop the updater service
        stopService(new Intent(this, TimelineService.class));
        //remove receiver register
        unregisterReceiver(tweetxyStatusReceiver);
        //close the database
        timelineDB.close();
    }
    catch(Exception se) { Log.e(LOG_TAG, "unable to stop service or receiver"); }
}

}

4

2 回答 2

3

除了https://developer.android.com/reference/android/os/AsyncTask.html提供的东西,你还需要什么?您对 AsyncTask 的使用有什么具体问题吗?

你把有问题的行放在doInBackground() 中。如果它有结果,您可以将结果处理代码放在 doInBackground() 中,如果它不操作 UI。如果它确实对 UI 进行了更改,则将其放入 onPostExecute() 中。

然后将引发异常的行替换为:

新的 MyTask().execute(param);

请记住,这之后的代码会立即执行。

于 2012-05-20T20:07:04.120 回答
0

我认为没有必要保留您的问题,您目前面临的大多数问题都可以通过 AsyncTask 的使用来解决,该使用在Here上有很好的记录。我建议您不要将本教程用作您的应用程序/项目的框架,而是将其用作了解如何设置自己的项目的指南。如果您创建自己的 AsyncTask 并使用它从网络获取数据会更好,因为在主任务或除 AsynTasks 之外的大多数任务中获取网络相关数据通常会因为它抛出的异常而被皱眉。

于 2014-07-11T04:37:47.337 回答