I could use some help identifying an issue in my code regarding fragments and webviews. I tried implementing some of the solutions in the other threads unsuccessfully. I've tested this same fragment being replaced without the webview being created inside and there is no leak. Any ideas? If not, can anyone pose an alternate solution?
Here's my webview fragment:
public class CustomWebViewFragment extends PageFragment
{
private LinearLayout mWebContainer;
private WebView mWebView;
/**
* public View onCreateView(LayoutInflater inflater, ViewGroup container,
* Bundle savedInstanceState)
*/
#SuppressLint("SetJavaScriptEnabled")
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
View v = inflater.inflate(R.layout.fragment_one, container, false);
//If I comment this line out, there is no memory leak
mWebView = new WebView(this.getActivity().getApplicationContext());
return v;
}
/**
* public void onDestroy()
*/
#Override
public void onDestroy()
{
super.onDestroy();
if (mWebView != null)
{
mWebView.loadUrl("about:blank");
mWebView.destroy();
mWebView = null;
}
}
}
Here's how I'm changing fragments:
#Override
public void onNavSelected(String page)
{
if (page != null && !page.equals(""))
{
System.gc();
if (page.equalsIgnoreCase(GlobalConstants.PAGE_1))
{
mCurrent = getFragment(); // Creates a new fragment
getSupportFragmentManager().beginTransaction()
.replace(R.id.main_fragment, mCurrent).commit();
}
}
}
Change
//If I comment this line out, there is no memory leak
mWebView = new WebView(this.getActivity().getApplicationContext());
&
#Override
public void onDestroy()
{
super.onDestroy();
if (mWebView != null)
{
mWebView.loadUrl("about:blank");
mWebView.destroy();
mWebView = null;
}
}
To
mWebView = new WebView(getActivity());
&
#Override
public void onDestroy()
{
// null out before the super call
if (mWebView != null)
{
mWebView.loadUrl("about:blank");
mWebView = null;
}
super.onDestroy();
}
Related
I have two reuse fragments, and the second on is a flutter fragment, which behave strange.
above Android API 29, flutter fragment will render above first fragment after the detail activity pop out of stack .
under Android API 29, flutter fragment always render above all views in the main activity.
I create this repository to reproduce this error.
This unsolved question is similar to mine.
this code shows how I reuse fragments
private void switchTo(String tag) {
FragmentManager supportFragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction = supportFragmentManager.beginTransaction();
if (tag.equals("home")) {
fragmentTransaction.show(homeFragment);
fragmentTransaction.hide(notificationsFragment);
} else {
fragmentTransaction.show(notificationsFragment);
fragmentTransaction.hide(homeFragment);
}
fragmentTransaction.commitNowAllowingStateLoss();
}
this is where flutter fragment add to the view
public View onCreateView(#NonNull LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState) {
FragmentManager fragmentManager = getChildFragmentManager();
if (mRepGoalsFlutterFragment1 == null) {
mRepGoalsFlutterFragment1 = FlutterFragment.withNewEngine()
.build();
fragmentManager
.beginTransaction()
.add(R.id.card_view7,
mRepGoalsFlutterFragment1,
TAG_REP_GOALS_FLUTTER_FRAGMENT_1)
.commit();
}
return inflater.inflate(R.layout.fragment_notifications, container, false);
}
In my Fragment, I have to detachFlutterEngine myself to fix this problem.
private FlutterView myFlutterView;
private boolean isFlutterHidden = false;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = super.onCreateView(inflater, container, savedInstanceState);
myFlutterView = FlutterBoostUtils.findFlutterView(view);
return view;
}
#Override
public void onHiddenChanged(boolean hidden) {
super.onHiddenChanged(hidden);
isFlutterHidden = hidden;
updateFlutterViewHidden();
}
#SuppressLint("VisibleForTests")
void updateFlutterViewHidden() {
if (myFlutterView == null) return;
if (isFlutterHidden) {
myFlutterView.setVisibility(View.GONE);
} else {
myFlutterView.setVisibility(View.VISIBLE);
}
}
#Override
public void onResume() {
super.onResume();
updateFlutterViewHidden();
}
Combine your posts with Github issue(https://github.com/flutter/flutter/issues/59968),the solution is create self class extends FlutterFragment, get flutterSurfaceView in the onFlutterSurfaceViewCreated.
private FlutterSurfaceView flutterView;
#Override
public void onFlutterSurfaceViewCreated(#NonNull FlutterSurfaceView flutterSurfaceView) {
super.onFlutterSurfaceViewCreated(flutterSurfaceView);
LogUtils.d(TAG, "onFlutterSurfaceViewCreated开始");
this.flutterView = flutterSurfaceView;
LogUtils.d(TAG, "onFlutterSurfaceViewCreated结束");
}
Then setVisibility in onResume.
#SuppressLint("解决FlutterFragment切换到前台显示的问题")
void updateFlutterViewVisibility() {
if (flutterView == null) {
return;
}
if (isFlutterHidden) {
flutterView.setVisibility(View.GONE);
} else {
flutterView.setVisibility(View.VISIBLE);
}
}
#Override
public void onResume() {
LogUtils.d(TAG, "onResume开始");
super.onResume();
updateFlutterViewVisibility();
LogUtils.d(TAG, "onResume结束");
}
#Override
public void onHiddenChanged(boolean hidden) {
LogUtils.d(TAG, "onHiddenChanged开始");
super.onHiddenChanged(hidden);
isFlutterHidden = hidden;
updateFlutterViewVisibility();
LogUtils.d(TAG, "onHiddenChanged结束");
}
Create your self class extends FlutterFragment like this:
FlutterFragment.CachedEngineFragmentBuilder(
YourFlutterFragment::class.java,
YourFlutterFragmentEngineId
)
.shouldAttachEngineToActivity(false)
.build<YourFlutterFragment>()
I have a tableLayout in a fragment that´s not showing anything when the information used to populate the cells is taken from the savedInstanceState bundle from OnCreateVIew()
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = initializeViews(inflater,container);
if (savedInstanceState!= null){
cellsTextArrayList = savedInstanceState.getStringArrayList("extractedTextEt");
for (String word: cellsTextArrayList){
addRow(word);
}
}
return rootView;
}
private void addRow(String word){
final TableRow tr = new TableRow(getActivity().getApplicationContext());
final View view = LayoutInflater.from(getActivity().getApplicationContext()).inflate(R.layout.cell_layout, null);
EditText actualCell = view.findViewById(R.id.cell_text);
actualCell.setText(word);
tr.addView(view);
tableLayout.addView(tr);
}
#Override
public void onSaveInstanceState (Bundle outState)
{
cellsTextArrayList.add("first");
cellsTextArrayList.add("second");
cellsTextArrayList.add("third");
outState.putStringArrayList("extractedTextEt", cellsTextArrayList);
super.onSaveInstanceState(outState);
}
The cellsTextArrayList is being restored from the savedInstanceState object, the addRow() method is the one that´s not working. I ran the code on an Activity and it worked so I think this has to do with the Fragment lifecycle.
using onActivityCreated in fragment.
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
...
if (savedInstanceState != null) {
//Restore the fragment's state here
}
}
and then in the activity, you have to save the fragment's instance in onSaveInstanceState() and restore in onCreate().
class MyActivity extends Activity {
private MyFragment
public void onCreate(Bundle savedInstanceState) {
...
if (savedInstanceState != null) {
//Restore the fragment's instance
mMyFragment = getSupportFragmentManager().getFragment(savedInstanceState, "myFragmentName");
...
}
...
}
#Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
//Save the fragment's instance
getSupportFragmentManager().putFragment(outState, "myFragmentName", mMyFragment);
}
}
#JavascriptInterface
public void switchView() {
//sync the BottomBar Icons since a different Thread is running
Handler refresh = new Handler(Looper.getMainLooper());
refresh.post(new Runnable() {
public void run()
{
MapFragment mapFragment = new MapFragment();
FragmentManager fragmentManager = ((MainActivity) mContext).getFragmentManager();
FragmentTransaction transaction = fragmentManager.beginTransaction();
transaction.replace(R.id.content, mapFragment);
transaction.commit();
}
});
}
When i run this code everything is fine, but when i add the line
mapFragment.setUrl("www.examplestuff.com");
the app crashes with Attempt to invoke virtual method 'void android.webkit.WebView.loadUrl(java.lang.String)' on a null object reference
My Fragment class looks like this
public WebView mapView;
private String thisURL;
public void setUrl(String url) {
thisURL = url;
mapView.loadUrl(thisURL);
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_map,container, false);
mapView = (WebView)view.findViewById(R.id.mapView);
this.setUrl("file:///android_asset/www/MapView.html");
mapView.setWebViewClient(new WebViewClient());
WebSettings webSettings = mapView.getSettings();
webSettings.setJavaScriptEnabled(true);
//allow cross origin - like jsonP
webSettings.setAllowUniversalAccessFromFileURLs(true);
return view;
}
Also call there the method this.setURL() and works fine.
What I am doing wrong?
Has the FragmentManager no access of the instance WebView of the fragment???
This be because when you call setUrl it invokes this method:
public void setUrl(String url) {
thisURL = url;
mapView.loadUrl(thisURL);
}
the line mapView.loadUrl(thisURL); accesses the mapView. However you are likely calling setUrl before the Android system has called onCreateView, therefore mapView is null, causing said crash.
public void setUrl(String url) {
thisURL = url;
if(mapView != null) {
mapView.loadUrl(thisURL);
}
}
and
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_map,container, false);
mapView = (WebView)view.findViewById(R.id.mapView);
if(thisUrl != null) {
mapView.loadUrl(thisURL);
}
... other code
Then mapFragment.setUrl("www.examplestuff.com"); would work
A better solution would be to understand more the Activity & Fragment lifecycles and not call setUrl when the Fragment is in an invalid state :-) You are probably calling setUrl when really you should be passing the Url as an intent extra when the fragment is created. https://developer.android.com/training/basics/fragments/communicating.html
I am using an android QR code processing library - android QR code.
I am extending the DecoderActivity for scanner and now I want the scanner to be inside a fragment, I have used LocalActivityManager to embed Activity inside a fragment. Here is the code:
public class QrCodeProcessorFragment extends SherlockFragment {
private static final String KEY_STATE_BUNDLE = "localActivityManagerState";
private LocalActivityManager mLocalActivityManager;
Button generate_qr_code;
private QuickPayManagerActivity parent;
protected LocalActivityManager getLocalActivityManager() {
return mLocalActivityManager;
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Bundle state = null;
if (savedInstanceState != null) {
state = savedInstanceState.getBundle(KEY_STATE_BUNDLE);
}
mLocalActivityManager = new LocalActivityManager(getActivity(), true);
mLocalActivityManager.dispatchCreate(state);
}
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// This is where you specify you activity class
Intent i = new Intent(getSherlockActivity(), CaptureActivity.class);
Window w = mLocalActivityManager.startActivity("tag", i);
View currentView = w.getDecorView();
ViewGroup vg = (ViewGroup) (currentView.getParent());
if (vg != null)
vg.removeView(currentView);
currentView.setVisibility(View.VISIBLE);
currentView.setFocusableInTouchMode(true);
((ViewGroup) currentView)
.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
return currentView;
}
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putBundle(KEY_STATE_BUNDLE,
mLocalActivityManager.saveInstanceState());
}
#Override
public void onResume() {
System.out.println("lam onresume");
super.onResume();
mLocalActivityManager.dispatchResume();
}
#Override
public void onPause() {
System.out.println("lam onpause");
super.onPause();
mLocalActivityManager.dispatchPause(getActivity().isFinishing());
}
#Override
public void onStop() {
super.onStop();
mLocalActivityManager.dispatchStop();
}
#Override
public void onDestroy() {
super.onDestroy();
mLocalActivityManager.dispatchDestroy(getActivity().isFinishing());
}}
QR scanner is not working now. I debugged the library code, it is expecting an activity instance and I am passing the activity instance which is inside the fragment. Hope I am clear on this. Please help!
I found a library called Barcode Fragment library which uses a fragment for hosting the scanner functionality. Works fine but hasn't supported portrait mode support, I have done changes to library as suggested in here. It worked like a charm :)
I am adding a Map to a fragment with this code:
public class MapFragment : Fragment
{
private MapActivity map=null;
public override Android.Views.View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
return new FrameLayout(Activity);
}
public override void OnActivityCreated(Bundle savedInstanceState)
{
base.OnActivityCreated(savedInstanceState);
map = new MapView(Activity,"XXXXX-v0jt5Z-XXXXXX");
//HOW TO ADD THE VIEW HERE???
}
}
My question is, in Mono for Android how to I add the Map to the View.
Note: In Java I would write this:
((ViewGroup)getView()).addView(map);
Footnote: This example uses code from the Java MapFragment source code: https://github.com/commonsguy/cw-omnibus/blob/master/Maps/NooYawkFragments/src/com/commonsware/android/mapfrags/MapFragment.java
Maps can not be added to Fragments as is, they need a hosting environment.
Here's the solution for a MapFragment:
namespace BahaiResearch.com
{
public class MyMapFragment : Fragment
{
// FROM http://stackoverflow.com/questions/5109336/mapview-in-a-fragment-honeycomb
private static String KEY_STATE_BUNDLE = "localActivityManagerState";
public override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
Bundle state = null;
if (savedInstanceState != null) {
state = savedInstanceState.GetBundle(KEY_STATE_BUNDLE);
}
mLocalActivityManager = new LocalActivityManager(Activity, true);
mLocalActivityManager.DispatchCreate(state);
}
public override Android.Views.View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
//This is where you specify you activity class
Intent i = new Intent(Activity, typeof(SteamLocationMapActivity));
Window w = mLocalActivityManager.StartActivity("tag", i);
View currentView=w.DecorView;
currentView.Visibility = ViewStates.Visible;
currentView.FocusableInTouchMode = true;
((ViewGroup) currentView).DescendantFocusability = DescendantFocusability.AfterDescendants;
return currentView;
}
private LocalActivityManager mLocalActivityManager;
protected LocalActivityManager GetLocalActivityManager() {
return mLocalActivityManager;
}
public override void OnSaveInstanceState(Bundle outState)
{
base.OnSaveInstanceState(outState);
outState.PutBundle(KEY_STATE_BUNDLE,mLocalActivityManager.SaveInstanceState());
}
public override void OnResume()
{
base.OnResume();
mLocalActivityManager.DispatchResume();
}
public override void OnPause()
{
base.OnPause();
mLocalActivityManager.DispatchPause(Activity.IsFinishing);
}
public override void OnStop()
{
base.OnStop();
mLocalActivityManager.DispatchStop();
}
}
}