问题
I am listening to network activity in an android application. When events come over the network, I would like to update some TextViews that reside inside of different Fragments in my application. Right now I have a single screen application where one Fragment is on the screen at a time.
How can I call a method on an existing Fragment inside of a ViewPager, managed by a FragmentStatePagerAdapter?
I am able to "paint" the UI correctly, it gets onscreen as expected. That all works. What I am having a tough time doing is calling a method when a background thread sends a message to my containing Activity. The thread is long running and will updated each TextView frequently (think of a countdown timer).
{thread} -> Actvity.handleMessage() -> {current}Fragment.update();
I am attempting to discern what the "current", onscreen Fragment is so that I can call a method on it to update its TextViews. I am trying to use the findFragmentByTag approach I have read about, but it is always returning a null Fragment object.
Specifically I am doing this:
private String getFragmentTag(int viewPagerId, int fragmentPosition){
return "android:switcher:" + viewPagerId + ":" + fragmentPosition;
}
//Take the data from the thread (network) and react to it.
@Override
public boolean handleMessage(Message msg) {
//tell the fragment to update itself
//Log.v(TAG, "Handler: " + msg.what);
int currentFragmentIndex = pager.getCurrentItem();
Log.v(TAG, "Current Fragment Index: " + Integer.toString(currentFragmentIndex));
String fTag = getFragmentTag(pager.getId(), currentFragmentIndex );
Log.v(TAG, "Current Fragment Tag: " + fTag);
////THIS IS WHERE I AM HAVING TROUBLE!////
Log.v(TAG, "fragment: " + getSupportFragmentManager().findFragmentByTag(fTag));
////THIS ALWAYS OUTPUTS:
////fragment: null
//now do the update
//currentFragment.update(sensorDataMap);
return true;
}
Based on reading a ton and specifically this post - How to get existing fragments when using FragmentPagerAdapter
Here is the relevant code...
MainActivity.java
public class MainActivity extends FragmentActivity implements Callback {
//receive messages from the other thread
private Handler handler = new Handler(this);
//the runnable listening to the network
private UDPListener udpl;
//we will parse the handler data into this map and store the values by int keys
private SparseArray<String> sensorDataMap = new SparseArray<String>();
//for logging and convenience
private static final String TAG = "BRRT";
//set up the fragment manager
private ViewPager pager;
private ScreenFragmenStatePagerAdapter fragmentManager;
private ScreenFragment currentFragment;
@Override
protected void onCreate(Bundle savedInstanceState) {
this.requestWindowFeature(Window.FEATURE_NO_TITLE);
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//remove the titlebar
this.getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
//keep the screen on while we are running
this.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
//set up the pager to move through the fragments
pager = (ViewPager) findViewById(R.id.viewPager);
fragmentManager = new ScreenFragmenStatePagerAdapter(getSupportFragmentManager());
pager.setAdapter(fragmentManager);
//start up the thread
Log.v(TAG, "start thread");
startUDPListener(handler);
Log.v(TAG, "past start thread");
}
//start up the thread to listen to the network, pass in the handle to this thread for messages
private void startUDPListener(Handler handler){
//spawn the listener thread, pass in the context and the handler object
try{
udpl = new UDPListener(this.getApplicationContext(), handler, sensorDataMap);
//TODO remove this debug flag
//DEBUG
udpl.setDebug(true);
//DEBUG
//set up the threaded reading, event throwing
Thread reader = new Thread(udpl);
reader.start();
} catch(Exception e){
//TODO real logging
Log.v(TAG, "caught after trying to start thread");
Log.v(TAG, e.getMessage());
}
}
//fragile, as this depends on the current naming convention for these fragment IDs in support/v4.
private String getFragmentTag(int viewPagerId, int fragmentPosition){
return "android:switcher:" + viewPagerId + ":" + fragmentPosition;
}
//Take the data from the thread (network) and react to it. This will get back unparsed strings
//and have to parse them in this thread, with the UI
@Override
public boolean handleMessage(Message msg) {
//tell the fragment to update itself
//Log.v(TAG, "Handler: " + msg.what);
int currentFragmentIndex = pager.getCurrentItem();
Log.v(TAG, "Current Fragment Index: " + Integer.toString(currentFragmentIndex));
String fTag = getFragmentTag(pager.getId(), currentFragmentIndex );
Log.v(TAG, "Current Fragment Tag: " + fTag);
Log.v(TAG, "fragment: " + getSupportFragmentManager().findFragmentByTag(fTag));
//Log.v(TAG, "Current Fragment Null? " + (currentFragment == null));
//now do the update
//currentFragment.update(sensorDataMap);
return true;
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
@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_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
}
ScreenFragmenStatePagerAdapter .java
public class ScreenFragmenStatePagerAdapter extends FragmentStatePagerAdapter {
//the set of screens we are interested in.
private ArrayList<ScreenConfig> screens = new ArrayList<ScreenConfig>(6);
//for logging and convenience
private static final String TAG = "CC";
//convenience variable to let us know what screen we are pointing at, logically
private ScreenConfig currentScreenConfig;
public ScreenFragmenStatePagerAdapter(FragmentManager fm) {
super(fm);
//set up the data.
setupScreenData();
}
@Override
public Fragment getItem(int pos) {
//Log.v(TAG, "fragment pos: " + pos);
//hopefully this will always work
currentScreenConfig = screens.get(pos);
ScreenFragment sf;
if (currentScreenConfig.eventCount() == 1){
sf = OneUpScreen.newInstance();
} else {
sf = FourUpScreen.newInstance();
}
sf.setConfig(currentScreenConfig);
return sf;
}
@Override
public int getCount() {
return screens.size();
}
//class that maps the various events to their descriptions in a screen by screen way
//do all the grunt work of mapping screens to events
private void setupScreenData(){
//TODO convert this stuff to JSON so we can pass them around in Bundles
//a 1up screen
ScreenConfig configGun = new ScreenConfig("Gun");
configGun.addEvent(204, "TimeToGun","Ttg", "TTG");
screens.add(configGun);
//the rest are 4up screens
ScreenConfig configBowman = new ScreenConfig("Bowman");
configBowman.addEvent(5, "ExTws","Tws", "TWS");
configBowman.addEvent(34, "ExLayTimeOnStrb", "LayTimeOnStrb", "Time Stb");
configBowman.addEvent(37, "ExLayTimeOnPort", "LayTimeOnPort", "Time Prt");
configBowman.addEvent(113, "ExNextMarkTwa", "NextMarkTwa", "TWA");
screens.add(configBowman);
ScreenConfig configPit = new ScreenConfig("Pit");
configPit.addEvent(88, "ExMarkTime","MarkTime", "Time Mark");
configPit.addEvent(113, "ExNextMarkTwa", "NextMarkTwa", "Next TWA");
configPit.addEvent(111, "ExNextMarkRng", "NextMarkRng", "Next Range");
configPit.addEvent(112, "ExNextMarkBrg", "NextMarkBrg", "Next Brg");
screens.add(configPit);
ScreenConfig configUpwindTrimmer = new ScreenConfig("Upwind Trimmer");
configUpwindTrimmer.addEvent(5, "ExTws","Tws", "TWS");
configUpwindTrimmer.addEvent(1, "ExBsp", "Bsp", "BSP");
configUpwindTrimmer.addEvent(54, "ExTargBspN", "TargBspN", "Tgt BSP");
configUpwindTrimmer.addEvent(4, "ExTwa", "Twa", "TWA");
screens.add(configUpwindTrimmer);
ScreenConfig configDownwindTrimmer = new ScreenConfig("Downwind Trimmer");
configDownwindTrimmer.addEvent(5, "ExTws","Tws", "TWS");
configDownwindTrimmer.addEvent(4, "ExTwa", "Twa", "TWA");
configDownwindTrimmer.addEvent(53, "ExTargTwaN", "TargTwaN", "Tgt BSP");
configDownwindTrimmer.addEvent(58, "ExPolarBspPercent", "PolarBspPercent", "P Bsp %");
screens.add(configDownwindTrimmer);
ScreenConfig configHelmsman = new ScreenConfig("Helmsman");
configHelmsman.addEvent(5, "ExTws","Tws", "TWS");
configHelmsman.addEvent(1, "ExBsp", "Bsp", "BSP");
configHelmsman.addEvent(6, "ExTwd", "Twd", "TWD");
configHelmsman.addEvent(4, "ExTwa", "Twa", "TWA");
screens.add(configHelmsman);
}
}
activty_main.xml
<android.support.v4.view.ViewPager
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/viewPager"
android:layout_width="fill_parent"
android:layout_height="fill_parent" />
回答1:
Ok, I gave up on the normal way and moved to an architecture based on EventBus by green robot.
Here are the relevant java files to help you see where I ended up on this issue. I basically moved to posting to an EventBus in my network thread and receiving those events in my Fragments directly. It simplified my code greatly. There is plenty of optimization to do on the code (things like pulling Fragment and Activity lifecycle handlers into a central class or interface) but that is in the future.
Here is the MainActivity.java file. Now all it does (essentially) is set up the viewpager and kick off the network thread.
public class MainActivity extends FragmentActivity {
//the runnable listening to the network
private UDPListener udpl;
//for logging and convenience
private static final String TAG = "BRRT";
//set up the fragment manager
private ViewPager pager;
private ScreenFragmenStatePagerAdapter fragmentManager;
//we will parse the handler data into this map and store the values by int keys
private SparseArray<String> sensorDataMap = new SparseArray<String>();
@Override
protected void onCreate(Bundle savedInstanceState) {
this.requestWindowFeature(Window.FEATURE_NO_TITLE);
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//remove the titlebar
this.getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
//keep the screen on while we are running
this.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
//set up the pager to move through the fragments
pager = (ViewPager) findViewById(R.id.viewPager);
fragmentManager = new ScreenFragmenStatePagerAdapter(getSupportFragmentManager());
pager.setAdapter(fragmentManager);
}
@Override
public void onResume(){
//start up the thread
Log.v(TAG, "start thread");
startUDPListener();
Log.v(TAG, "past start thread");
super.onResume();
}
//start up the thread to listen to the network, pass in the handle to this thread for messages
private void startUDPListener(){
//spawn the listener thread, pass in the context and the handler object
try{
udpl = new UDPListener(this.getApplicationContext(), sensorDataMap);
//TODO remove this debug flag
//DEBUG
udpl.setDebug(true);
//DEBUG
//set up the threaded reading, event throwing
Thread reader = new Thread(udpl);
reader.start();
} catch(Exception e){
//TODO real logging
Log.v(TAG, "caught after trying to start thread");
Log.v(TAG, e.getMessage());
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
@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_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
}
Here is a Fragment. All it does is configure its view, and listen for events on the EventBus.
public class OneUpScreen extends ScreenFragment {
private Button name;
private TextView c00Title, c00Data;
//DEBUG
public String title;
//DEBUG
private int counter = 0;
//for logging and convenience
private static final String TAG = "BRRT";
@Override
public void onResume(){
EventBus.getDefault().register(this);
super.onResume();
}
@Override
public void onDestroy(){
EventBus.getDefault().unregister(this);
super.onDestroy();
}
//process the bus messaging
public void onEventMainThread(ExpeditionEvent event){ <-- READ IT FROM THE BUS
Log.v(TAG, "OneUp event received: " + event.getEventId() + " : " + event.getEventScreenValue());
c00Data.setText(""+counter);
counter++;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.one_up_screen_layout, container, false);
name = (Button) v.findViewById(R.id.lblTitle);
c00Title = (TextView) v.findViewById(R.id.c00Title);
c00Data = (TextView) v.findViewById(R.id.c00Data);
Bundle bundle = this.getArguments();
//initial set up of the text on the screen
//null check
name.setText((bundle.getString("name") == null) ? "error" : bundle.getString("name"));
c00Title.setText((bundle.getString("c00Title") == null) ? "error" : bundle.getString("c00Title"));
title = bundle.getString("c00Title");
return v;
}
public OneUpScreen(){
}
public static OneUpScreen newInstance() {
OneUpScreen frag = new OneUpScreen();
return frag;
}
//attach the config for this instance.
@Override
public void setConfig(ScreenConfig sc){
//set up the data to paint the screen for the first time
Bundle b = new Bundle();
//now we have to parse some stuff into a bundle and send the bundle to the fragment
b.putString("name", sc.getName());
b.putString("c00Title", sc.getEvents().get(0).getCleanName());
b.putInt("c00Data", sc.getEvents().get(0).getEventID());
//pass it along
this.setArguments(b);
}
}
Here is what I call in the network listener, this is in the run) method of the Runnable...
//throw out an event for each item
EventBus.getDefault().post(new ExpEvent(key, value)); <-- PUT IT ON THE BUS
Ta-DA!!!
I hope this will help the next person who is having trouble with ViewPager, FragmentStatePagerAdapters, Fragments or threading in Android.
来源:https://stackoverflow.com/questions/26347217/calling-methods-on-the-current-fragment-in-a-viewpager-from-activity