我使用来自 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"); }
}
}