One of my old question had to do with viewing pdf files in monotouch ( I managed to accomplish this). Port of the iOS pdf viewer for xamarin
My issue is as following: if I start to close and open a pdf view( view with catiledlayer) really fast and often my app crashes with a:
Got a SIGSEGV while executing native code. This usually indicates
a fatal error in the mono runtime or one of the native libraries
used by your application.
After researching around the internet for a few days I found a post saying something along the lines of: The image back store is being cleaned and this is causing the error.
Edit:
Ok, I have come to the conclusion that my app is cleaning the memory and my pointers are turning into nulls. I called Gc.Collect() a couple of times and this seems to be the root of the problem.
I have removed all my calls to GC.Collect() and I currently running a stress test and will update as I identify the issue.
After running some more tests this is what I found out:
The error seems to orignate from the TiledLayerDelegate : CALayerDelegate class.
The app only crashes if the method Dispose from CALayerDelegate is called, overriding the method as empty seems to prevent the app from crashing.
Running the app seems to cause no issue whatsoever anymore. It is apparent that something is going really wrong on the Dispose method of the CALayerDelegate.
Last finding: Running the app like a monkey tends to heat up the app a good bit. I assume this is due to the intensive rendering of pdf pages ( they are huge sheets about 4,000 X 3,000 pxs)
protected override void Dispose (bool disposing)
{
try{
view = null;
GC.Collect (2);
//base.Dispose (disposing);
}catch(Exception e) {
//System.Console.Write(e);
}
}
Now more than anything, I am just wondering if the phone heating up is really as I assume nothing more than the CPU rendering the sheets and is normal. Does anyone have any ideas as to how best deal with the Dispose override?
Last Edit: for anyone wanting to prevents crashes this is what my last version of the layer view class looks like.
public class TiledPdfView : UIView {
CATiledLayer tiledLayer;
public TiledPdfView (CGRect frame, float scale)
: base (frame)
{
tiledLayer = Layer as CATiledLayer;
tiledLayer.LevelsOfDetail = 4; //4
tiledLayer.LevelsOfDetailBias = 4;//4
tiledLayer.TileSize = new CGSize (1024, 1024);
// here we still need to implement the delegate
tiledLayer.Delegate = new TiledLayerDelegate (this);
Scale = scale;
}
public CGPDFPage Page { get; set; }
public float Scale { get; set; }
public override void Draw (CGRect rect)
{
// empty (on purpose so the delegate will draw)
}
[Export ("layerClass")]
public static Class LayerClass ()
{
// instruct that we want a CATileLayer (not the default CALayer) for the Layer property
return new Class (typeof (CATiledLayer));
}
protected override void Dispose (bool disposing)
{
Cleanup ();
base.Dispose (disposing);
}
private void Cleanup ()
{
InvokeOnMainThread (() => {
tiledLayer.Delegate = null;
this.RemoveFromSuperview ();
this.tiledLayer.RemoveFromSuperLayer ();
});
}
Apple's sample code around that is not really great. Looking at the source of your tiled view I do not see a place where you set the layer delegate to nil. Under the hood, CATiledLayer creates a queue to call the tiled rendering in the background. This can lead to races and one way to work around this is explicitly nilling the delegate. Experiments showed that this can sometimes block, so expect some performance degradation. Yes, this is a bug and you should file a feedback - I did so years ago.
I'm working on a commercial PDF SDK (and we have a pretty popular Xamarin wrapper) and we moved away from CATiledLayer years ago. It's a relatively simple solution but the nature of PDF is that to render a part, one has to traverse the whole render tree - it's not always easy to figure out what is on screen and what is not. Apple's renderer is doing an ok-ish job on that and performance is okay, but you'll get a better performance if you render into one image and then move that around/re-render as the user scrolls. (Of course, this is trickier and harder to get right with memory, especially on retina screens.)
If you don't have the time to move away from CATiledLayer, some people go with the nuclear option and also manually remove the layer from the view. See e.g. this question for more details.
Related
I am trying to create a Xamarin.Forms app that will run on both iOS and Android. Eventually I need instances of the app to communicate with each other via Bluetooth, but I'm stuck on getting the iOS side to do anything with Bluetooth. I originally tried to work with Plugin.BluetoothLE and Plugin.BLE, but after a week and a half I was not able to get advertising or scanning to work on either OS with either plugin, so I decided to try implementing simple Bluetooth interaction using the .NET wrappers of the platform APIs, which at least are well documented. I did get scanning to work fine on the Android side. With iOS, though, what I have right now builds just fine, and runs on my iPad without errors, but the DiscoveredPeripheral handler is never called, even though the iPad is just a few inches from the Android tablet and presumably should be able to see the same devices. I have verified this by setting a breakpoint in that method, which is never reached; and when I open the Bluetooth Settings on the iPad to make it discoverable the app version on the Android tablet can see it, so I don't think it's an iPad hardware issue.
It seems obvious that there is simply some part of the process I don't know to do, but it's not obvious (to me) where else to look to find out what it is. Here is the code for the class that interacts with the CBCentralManager (as far as I understand from what I've read, this should include everything necessary to return a list of peripherals):
using MyBluetoothApp.Shared; // for the interfaces and constants
using CoreBluetooth;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Xamarin.Forms;
[assembly: Dependency(typeof(MyBluetoothApp.iOS.PeripheralScanner))]
namespace MyBluetoothApp.iOS
{
public class PeripheralScanner : IPeripheralScanner
{
private readonly CBCentralManager manager;
private List<IPeripheral> foundPeripherals;
public PeripheralScanner()
{
this.foundPeripherals = new List<IPeripheral>();
this.manager = new CBCentralManager();
this.manager.DiscoveredPeripheral += this.DiscoveredPeripheral;
this.manager.UpdatedState += this.UpdatedState;
}
public async Task<List<IPeripheral>> ScanForService(string serviceUuid)
{
return await this.ScanForService(serviceUuid, BluetoothConstants.DEFAULT_SCAN_TIMEOUT);
}
public async Task<List<IPeripheral>> ScanForService(string serviceUuid, int duration)
{
CBUUID uuid = CBUUID.FromString(serviceUuid);
//this.manager.ScanForPeripherals(uuid);
this.manager.ScanForPeripherals((CBUUID)null); // For now I'd be happy to see ANY peripherals
await Task.Delay(duration);
this.manager.StopScan();
return this.foundPeripherals;
}
private void DiscoveredPeripheral(object sender, CBDiscoveredPeripheralEventArgs args)
{
this.foundPeripherals.Add(new CPeripheral(args.Peripheral));
}
private void UpdatedState(object sender, EventArgs args)
{
CBCentralManagerState state = ((CBCentralManager)sender).State;
if (CBCentralManagerState.PoweredOn != state)
{
throw new Exception(state.ToString());
}
}
}
}
Can anyone point me in the direction of understanding what I'm missing?
EDIT: O...K, I've discovered quite by accident that if I do this in the shared code:
IPeripheralScanner scanner = DependencyService.Get<IPeripheralScanner>();
List<IPeripheral> foundPeripherals = await scanner.ScanForService(BluetoothConstants.VITL_SERVICE_UUID);
twice in a row, it works the second time. I feel both more hopeful and much more confused.
The underlying problem was that in the first instantiation of PeripheralScanner, ScanForService was being called before State was updated. I tried many ways of waiting for that event to be raised so I could be sure the state was PoweredOn, but nothing seemed to work; polling loops simply never reached the desired state, but if I threw an Exception in the UpdatedState handler it was thrown within milliseconds of launch and the state at that time was always PoweredOn. (Breakpoints in that handler caused the debugging to freeze with the output Resolved pending breakpoint, which not even the VS team seems to be able to explain).
Reading some of the Apple developer blogs I found that this situation is most often avoided by having the desired action occur within the UpdatedState handler. It finally soaked into my thick head that I was never seeing any effects from that handler running because the event was being raised and handled on a different thread. I really need to pass the service UUID to the scanning logic, and to interact with a generic List that I can return from ScanForService, so just moving it all to the handler didn't seem like a promising direction. So I created a singleton for flagging the state:
internal sealed class ManagerState // .NET makes singletons easy - Lazy<T> FTW
{
private static readonly Lazy<ManagerState> lazy = new Lazy<ManagerState>(() => new ManagerState());
internal static ManagerState Instance { get { return ManagerState.lazy.Value; } }
internal bool IsPoweredOn { get; set; }
private ManagerState()
{
this.IsPoweredOn = false;
}
}
and update it in the handler:
private void updatedState(object sender, EventArgs args)
{
ManagerState.Instance.IsPoweredOn = CBCentralManagerState.PoweredOn == ((CBCentralManager) sender).State;
}
then poll that at the beginning of ScanForService (in a separate thread each time because, again, I will not see the updates in my base thread):
while (false == await Task.Run(() => ManagerState.Instance.IsPoweredOn)) { }
I'm not at all sure this is the best solution, but it does work, at least in my case. I guess I could move the logic to the handler and create a fancier singleton class for moving all the state back and forth, but that doesn't feel as good to me.
Universal App with MVVMLight.
So I started wondering why all the SDK examples were done from code behind rather than using a solid Wrapper class.
So I wanted to write a reusable wrapper class. No luck. Even tried adding that wrapper to a ViewModel, still no luck.
Works fine from MainView.xaml.cs
IBandInfo[] pairedBands = BandClientManager.Instance.GetBandsAsync().Result;
if (pairedBands.Length > 0)
{
using (IBandClient bandClient = await BandClientManager.Instance.ConnectAsync(pairedBands[0]))
{
}
}
The moment I move to any kind of OOP or View Model, ConnectAsync will never return or throw exception. I have tried this 20 different ways, is the SDK broken? What Is happening? No message, no throw, just never returns.
If I throw in Code behind, wallah it works just fine and returns the client in 1/2 second.
I have spend 5-6 hours so far on this. I wanted to create a solid wrapper class for the SDK so I could call easy calls from Model and do things like StartListener(MicrosoftBandSensor sensorToActivate).
Any suggestions?
-- For Phil's comment
I was trying to create backing variables for both client and bandinfo which would be held in a class that the VM uses. I wrote my class as IDisposable so I could dispose of both when I was done with my wrapper. I may be using this wrong to be honest.
MicrosoftBand.MicrosoftBandClient = BandClientManager.Instance.ConnectAsync(pairedBands[0]).Result;
Is what I wanted to call making it a sync call since I wanted to make the calls to bandinfo and client in the constructor then hold both until the class was destroyed and just recall the vars when needed.
My VM has :
public BandInformation MicrosoftBand
{
get { return _microsoftBand; }
set { Set(() => MicrosoftBand, ref _microsoftBand, value); }
}
If they didn't pass the bandclient in the constructor I would use:
private async Task InitBand(IBandInfo bandInfo)
{
if (bandInfo == null)
{
var allBands = await BandClientManager.Instance.GetBandsAsync();
if (allBands.Length > 0)
{
bandInfo = allBands[0];
}
}
var bandClient = await BandClientManager.Instance.ConnectAsync(bandInfo);
MicrosoftBandInfo = bandInfo;
MicrosoftBandClient = bandClient;
if (MicrosoftBandClient == null)
{
AddErrorMessage("This sample app requires a Microsoft Band paired to your device.Also make sure that you have the latest firmware installed on your Band, as provided by the latest Microsoft Health app.");
}
}
This seems fine working with BandInfo. I get back a solid seeming to work object For the client I get "thread exited" and nothing else.
Note: I had it in a try catch throwaway version at one point and nothing threw n exception either.
I assume you can do this like you would any other IDisposable where you handle the disposing yourself.
I can reinstantiate the BandClient each time, just figured I needed to detach the events at some point, meaning I had to keep ahold of the bandclient. I could keep it until done and would add and remove events as I needed each time.
It's likely your blocking call to .Result within your VM constructor is what was causing the hang. IBandClientManager.ConnectAsync() may implicitly display UI (a Windows Runtime dialog asking the user to confirm that she wants to use that specific Bluetooth device). If you've blocked the UI thread when it attempts to display UI, you've now gotten yourself into a deadlock.
Calling Task.Result is almost never a good idea, much less doing so within a constructor where you have little idea on which thread the constructor is executing. If you're working with an async API (such as the Band SDK) then your best bet is to keep that interaction async as well. Instead, defer calling ConnectAsync() until you actually need to, and do so from an async method in your VM. (Deferring the connection is a good idea anyway because you want to minimize the time connected to the Band to preserve battery life.) Then call Dispose() as early as possible to close the Bluetooth connection.
So I went and looked at a bunch of examples. Finally I landed on the GravityHeroUAP demo on the MSDN site. https://msdn.microsoft.com/en-us/magazine/mt573717.aspx?f=255&MSPPError=-2147217396
I looked at his code and the source: https://github.com/kevinash/GravityHeroUWP
He was essentially doing what I wanted to do.
However, I noticed something Bizarre. In his viewmodel everything was static!
public static IBandInfo SelectedBand
{
get { return BandModel._selectedBand; }
set { BandModel._selectedBand = value; }
}
private static IBandClient _bandClient;
public static IBandClient BandClient
{
get { return _bandClient; }
set
{
_bandClient = value;
}
}
I ended up copying this pattern (though had to throw away my favorite MVVM lib in the process, though I am sure I can get it back).
My common pattern in my VM's:
public string ExceptionOnStart {
get { return _exceptionOnStart; }
set { Set(() => ExceptionOnStart, ref _exceptionOnStart, value); }
}
It seems to be working now!
That and I got data way too fast for the
await Windows.Storage.FileIO.AppendLinesAsync(dataFile, new List<string> { toWrite });
Thank you for the help Phil, it got me looking in the right direction!
Thank you very, very much. Spent WAY to long on this. Mark
I'm fairly new to writing BlackBerry applications, so maybe this is a stupid thing I'm overlooking. I have to use JDE 5 (client requirement) to support the older BlackBerry Curve 8520 phones.
What I am experiencing is that as soon as I place a DateField on my interface, the application slows down considerably, causing the UI to stutter. Even a simple layout that only has a single DateField and a button has the same effect. Then, as soon as I move on to the next layout, everything is fine again.
One of the layouts are created as follows (please comment if this is the incorrect way of doing it):
public void displaySomeLayout() {
final ButtonField okButton = new ButtonField("OK");
final DateField dobField = new DateField("Birthday", System.currentTimeMillis(), DateField.DATE);
/* some other non-ui code */
UiApplication.getUiApplication().invokeLater(new Runnable() {
public void run() {
applicationFieldManager.addAll(new Field[] {
dobField,
okButton
});
}
});
}
The application then just slows down a lot. Sometimes, after a minute of so it starts responding normally again, sometimes not.
The displaySomeLayout() method is called from the contructor of the Screen extending class. And then applicationFieldManager is a private VerticalFieldManager which is instantiated during class construction.
I'm not sure the problem is in the code that you've shown us. I think it's somewhere else.
However, here are a couple recommendations to improve the code you've shown:
Threading
First of all, the code you show essentially is being run in the Screen subclass constructor. There is almost no difference between this code:
public MyScreen() {
Field f = new ButtonField("Hello", ButtonField.CONSUME_CLICK);
add(f);
}
and this:
public MyScreen() {
addField();
}
private void addField() {
Field f = new ButtonField("Hello", ButtonField.CONSUME_CLICK);
add(f);
}
So, because your code is being run in the screen class's constructor, it should already be running on the UI thread. Therefore, there's no reason to use UiApplication.getUiApplication().invokeLater() here. Instead, just use this:
public void displaySomeLayout() {
final ButtonField okButton = new ButtonField("OK");
final DateField dobField = new DateField("Birthday", System.currentTimeMillis(), DateField.DATE);
/* some other non-ui code */
applicationFieldManager.add(dobField);
applicationFieldManager.add(okButton);
}
Sometimes, you do need to use invokeLater() to run UI code, even when you're already on the UI thread. For example, if your code is inside the Manager#sublayout() method, which runs on the UI thread, adding new fields directly will trigger sublayout() to be called recursively, until you get a stack overflow. Using invokeLater() can help there, by deferring the running of a block of code until sublayout() has completed. But, from the constructor of your screen class, you don't need to do that.
ObjectChoiceField
I'm also worried about the ObjectChoiceField you said you were using with 250 choices. You might try testing this field with only 10 or 20 choices, and see if that makes a difference.
But, even if the 250 choice ObjectChoiceField isn't the cause of your performance problems, I would still suggest a different UI.
On BlackBerry Java, you can use the AutoCompleteField. This field can be given all the country choices that you are now using. The user starts typing the first couple letters of a country, and quickly, the list narrows to just those which match. I personally think this is a better way to get through a very large list of choices.
Here can be found a sample code
https://github.com/PVoLan/TestActivityDispose
We have two activities. One has a button leading to second activity. Second activity has 30 TextViews (simulating a complex UI) and a back button.
Switching between activities forward and back causes GREF quantity growing quickly. It requires about 60 times to click forward and back to overflow 2k limit and crash application.
Android log can be found in repository. As you can see from log, GREF overflow occurs most because of TextViews (1543 GREFs). Another GREFS are:
Button (55 GREFs) - backButton, obviously
OnClickListenerImplementor (55 GREFs) - backButton.Click listenters
Activity2 (54 GREFs)
Intent (54 GREFs) - activity starters
So, as we can see, activity resources are not freed when activity finishes (although OnDestroy is called)
How can I free all this GREFs properly?
The problem is that there are two GCs in the process (Dalvik & Mono), and neither knows how much memory the other is using. For example, all Mono sees for TextView instances is a really small object (largely an IntPtr and other supporting fields from Java.Lang.Object):
namespace Java.Lang {
public class Object {
IntPtr handle;
// ...
}
}
namespace Android.Widget {
public class TextView : Java.Lang.Object {
// ...
}
}
That is, for most of the bound types, there are no data members of consequence, and the C# wrappers are quite tiny. Mono doesn't know -- and can't know -- that there's a Java object associated with Object.handle, and (more importantly) how much memory that object is referencing.
Consequently, you occasionally need to help it:
// https://github.com/PVoLan/TestActivityDispose/blob/master/Test/Activity2.cs
public class Activity2 {
// ...
protected override void OnDestroy ()
{
Android.Util.Log.Info("----------", "Destroy");
base.OnDestroy ();
GC.Collect ();
}
}
The added GC.Collect() call will give Mono's GC a chance to execute and collect the garbage objects. After adding that line, repeatedly tapping "Hello World, Click Me!" and "Back" levels out at 93-126 grefs (depending on which activity you're on).
I get a strange crash when using the new PageCurl effect reading a PDF with MonoTouch and iOS 5.0.
I've made a simple test case project for MonoDevelop 2.8 and uploaded on GitHub here:
https://github.com/Emasoft/IpaziaPDFReader
It seems that something is getting GCd too early and killing the application, but I can't find what. I've tried to dispose everything in many ways, but in vain. I've already submitted the project tarball to the Xamarin team, but they weren't able to solve the problem.
Is there something broken in the iOS NavigationController memory management? Or am I missing something?
Any help is appreciated, thanks!
UPDATE: I've tried to remove all subviews and sublayers before disposing the objects in all classes, but it still crashing. The only way I found to avoid the crash is to NEVER dispose of the PDF pages, adding them to a List before releasing them, but this is not a viable solution, because in that way memory is consumed rapidly for PDF with many pages and the app crashes anyway when unable to allocate memory for the next page.
Another way to avoid the crashes is to dispose of the PDF pages BEFORE turning the pages, forcing the dispose method on the page controller before creating a new page controller, but in this way the current page will become blank and the transition curls an useless empty page. No solution seems to work.
I've updated project on GitHub with the 3 different solutions I've tried (look in the PageDataSource class), you can uncomment them one at time to see the problems.
//SOLUTION 1
void ForcingPageControllerDispose (BookPageController oldPageController)
{
// --- IF YOU UNCOMMENT THIS, THE CRASHES GO AWAY, BUT THE PAGE IN THE TRANSITION IS BLANK, SO IS NOT VIABLE
currentPageController.View.RemoveFromSuperview ();
currentPageController.Dispose ();
}
//SOLUTION 2
void DisposeThePageControllerWhenDidFinishAnimating (BookPageController oldPageController, UIPageViewController pageViewController)
{
// --- IF YOU UNCOMMENT THIS, THE CRASHES STILL HAPPEN
pageViewController.DidFinishAnimating += delegate(object sender, UIPageViewFinishedAnimationEventArgs e) {
if (currentPageController != null) {
currentPageController.View.RemoveFromSuperview ();
currentPageController.Dispose ();
Console.WriteLine ("currentPageController disposed for page: " + currentPageController.PageIndex);
}
};
}
//SOLUTION 3
void BackupUnusedPagesToAvoidBeingGCd (BookPageController oldPageController)
{
// --- IF YOU UNCOMMENT THIS, THE CRASHES GO AWAY, BUT THE PAGES ARE NOT GARBAGE COLLECTED AND AFTER MANY PAGES IPHONE IS OUT OF MEMORY AND IT CRASHES THE APP
if (parentController.book_page_controllers_reference_list.Contains (currentPageController) == false)
parentController.book_page_controllers_reference_list.Add (currentPageController);
}
I've already submitted the project tarball to the Xamarin team, but they weren't able to solve the problem.
I'm pretty sure the person assigned to your case will come up with the solution. The bigger the test case the more time it can take.
From a quick view the following, in your AppDelegate.cs, is wrong:
PageTurnViewController viewController = new PageTurnViewController ("PageTurnViewController", null);
window.AddSubview (viewController.View);
since the local viewController instance won't have any reference to it once FinishedLaunching returns and the GC will be able to collect it. However it's needed (on the native side) for keep the View fully valid. This can lead to crashes (there could be other cases too, that's the first and only file I checked this morning).
The solution is to promote the viewController to a field. That will make it alive even when the method returns, making it unavailable to collection.
UPDATE
I had a quick look at your code on github.
You are adding (sub)view but you never remove them (when the GC will Dispose them it won't remove them from the super view);
You are losing references to views, e.g. in PageDataSource.cs
newPageController = new BookPageController (nextPageIndex, parentController.currentPDFdocument, parentController);
return newPageController;
After the first page there will already be a reference stored in newPageController which will be overwritten and make the object collectable bug the GC. Since (sub)views are never removed there could still be native reference to them leading to crashes.
For debugging you can add your own finalizers, e.g.
~BookPageController ()
{
Console.WriteLine ("bu-bye");
}
and put breakpoints in them. If they get hit, while you think it's still in use, then you likely found a problem.
Since you are taking a dependency on iOS 5 new features, you should also adopt the new View Controller Containment APIs in iOS 5 that solve a few problems with view controllers.
I suggest you check the WWDC Video and slides for for Session 102 "Implement UIViewController Containment".