📜 ⬆️ ⬇️

JavaScript to APK. The pitfalls of developing for Android for those who are stoned by PhoneGap. Building bridges from java to javascript

Hi, Habra!

I love JavaScript games and try to make their code bulletproof to port to all platforms. Six months ago, I already wrote about building Android applications and today I would like to reveal the topic in more detail.

Immediately I warn you that I had to give up PhoneGap, because The experience of using it in two projects upset me. It does an excellent job with “Hello World” applications, but with a pipeline assembly, nuances emerge in a row.
')
Why PhoneGap did not go:
1. It is initially empty. Constantly have to connect all new and new modules.
2. Many modules are written crookedly. They either take a lot of excess, or they behave unexpectedly. For example, from two modules for Android to send SMS, one did not work, the second sent true under any conditions.
3. Elementary things are not solved, like getting an EMEI phone. You need to constantly finish.



I did not understand the essence of PhoneGap. Initially, I was expecting one button “to do well”, and it is not really doing nothing. Under each platform, I still need to put the SDK. Under each task - to search and install the module. The modules themselves are also limited. They can do something only for a part of the platforms, and if needed for others, they have to look again for modules that I can do on other devices. A lot of garbage and unnecessary things, but you want to build builds with a minimum of costs. All these factors make writing natively. And here reefs begin to get out.

Why CocoonJS did not go:
With CocoonJS worked a little, so no special questions arose. Canvas builds really work faster. But in general, I did not see the point of working with CocoonJS, since He paid.

As for the assembly for other platforms and other nuances - there will be a separate article about it, and further discussion on this topic or the PhoneGap topic is beyond the scope of this one.

Let's get to the point



To begin, let's start with the basics - WebView with a running HTML page on full screen. In onCreate MainActivity, we write:

vw = (WebView) findViewById(R.id.webview); vw.setVerticalScrollBarEnabled(false); //   vw.setHorizontalScrollBarEnabled(false); //   vw.getSettings().setJavaScriptEnabled(true); //  JavaScript vw.getSettings().setDomStorageEnabled(true); //  localStorage  .. vw.getSettings().setSupportZoom(false); //  , ..       vw.getSettings().setSupportMultipleWindows(false); //   . // ..     SPA  vw.addJavascriptInterface(new WebAppInterface(this), "API"); //    JavaScript. //       Java.  JavaScript`   API vw.loadUrl("file:///android_asset/index.html"); //    vw.setWebViewClient(new WebViewClient()); 

All disputable situations will be resolved in Java. Remember, you write for Bada or SmartTV - there is always some standard functionality that allows you to throw bridges in JavaScript. In our case for Android, we threw an instance of the WebAppInterface class, and the class itself will look like this:

 public class WebAppInterface { Context mContext; /** Instantiate the interface and set the context */ WebAppInterface(Context c) { mContext = c; } /**   ,    JavaScript */ @JavascriptInterface public void sendSms(String phoneNumber, String message) { ... -   } } 

Reef: Working with such bridges can usually be asynchronous or unpredictable and full of surprises.

If you need Java to inform JavaScript about any event out of the blue, the easiest way to reach it is to knock at the URL:

 vw.loadUrl("javascript: ... -   JavaScript"); 

The underwater stone: In Android `s> 4 from Samsung` s at the touch event DOM elements can be highlighted in blue.



Pay attention to this nuance. Typical "protection" will not help you:

 * { -webkit-tap-highlight-color: rgba(255, 255, 255, 0); -webkit-focus-ring-color: rgba(255, 255, 255, 0); outline: none; -moz-user-select: -moz-none; -o-user-select: none; -khtml-user-select: none; -webkit-user-select: none; user-select: none; -webkit-text-size-adjust: none; } 

To get around the bug, add:

 if (document.addEventListener) { document.addEventListener("touchstart", function () { }, true); } 

But this does not always solve the problem. Perhaps the problem is affected by the layout itself. For example, take two applications: Sudoku and Test. In Sudoku, the board is laid out with a table, and for the table such a decision helped. In the test, the buttons are. It seems that everything is by the standards of HTML5 semantics, and everything should be more than fine, but in fact you have to finish off with the following CSS combos:

 .some_button:focus, .some_button:focus:active { background-color: rgba(0, 0, 0, 0); } 

I also noticed that the blue selection does not appear if the touch event had to be exactly on the button text (the text must be very large).

The underwater rock: In Android, <4 fonts can spread out.



The way to deal with the bug - either to check or disable fonts. Although, on the other hand, perhaps I had the curves themselves fonts.

Reef: Android is case sensitive.

If you have got a picture with .JPG among the heap of resources .jpg - you will hardly ever notice the difference in the browser, but the picture will not load in WebView.

Reef: Android is sensitive to reserved words.

For example, I had a folder named classijizm in assets. Android refused to build the project and could not clearly explain the error. Renamed klassijizm - earned. Again, in the usual browser, Android didn’t have such problems.

Reef: Audio tag on Android is not working.

More precisely, it works in the browser, but does not work when you use it inside WebView. To get around this limitation, you can skip the bridge and rewrite it with native code. Add to onCreate:

 mp = new MediaPlayer(); 

And for WebView we extend JavaScript interface:

 @JavascriptInterface public void audio(String url) { try { soundClick = getAssets().openFd(url); mp.reset(); mp.setDataSource(soundClick.getFileDescriptor(), soundClick.getStartOffset(), soundClick.getLength()); mp.prepare(); mp.start(); } catch (IOException e) { e.printStackTrace(); } } 

Reef: Instead of closing, Android collapses applications. Therefore, if you use Audio - you need to at least turn off the sound.

The essence of the problem is that, suppose you have a game running. The user has left the application, but continues to hear sounds from a running game. Therefore, from Java, you need to knock into JavaScript and ask to stop the game.

 @Override public void onBackPressed() { vw.loadUrl("javascript: windowClose();"); MainActivity.this.finish(); } @Override public void onPause() { super.onPause(); vw.loadUrl("javascript: windowClose();"); MainActivity.this.finish(); } @Override public void onResume() { super.onResume(); vw.loadUrl("javascript: windowOpen();"); } @Override public void onDestroy() { super.onDestroy(); vw.loadUrl("javascript: windowClose();"); MainActivity.this.finish(); } 

The command MainActivity.this.finish (); I'm trying to close the application at every opportunity. So you can be more confident that Android next time just starts everything from the beginning, and will not try to restore anything. It is clear that in games like Sudoku you can’t do that, but in most games you can, because they are quite simple (the same FlappyBirds or Tests). I advise you to be afraid of attempts by Android to return everything as it was, because other bugs appear.

Reef : Android with onResume does not always successfully restore applications. And indeed on some devices there are problems with re-launching.



For example, it can stop timers or miraculously stop reacting to resize. Therefore, in any strange situation, call resize and recheck timers.

Reef : when minimizing / opening the application, several WebViews can occur that will work in parallel and interfere with each other.

To surely get away from such a problem, we add in the manifesto:

 <activity ... //      WebView    . android:configChanges="keyboardHidden|orientation|screenSize" ... //            android:clearTaskOnLaunch="true" android:noHistory="true" android:launchMode="singleTask" > 

To make our JavaScript application look even better, you can run it full screen by removing the black bar from the top. To do this, add to the manifest:

 <application ... android:theme="@android:style/Theme.NoTitleBar.Fullscreen"> 

Reef : localStorage, in which you save the data will be destroyed after the application is closed.

To save data between two calls, you need to save the data in the native SharedPreferences. Prokin two bridges to save and load:

 @JavascriptInterface public void saveSomeThing(String message, String id) { if(numberDataForSave > Integer.parseInt(id)) return; numberDataForSave = Integer.parseInt(id); SharedPreferences preferences = getSharedPreferences("com.example.something", MODE_PRIVATE); SharedPreferences.Editor editor = preferences.edit(); editor.putString("somethingID", message); editor.commit(); } @JavascriptInterface public String loadSomeThing() { SharedPreferences preferences = getSharedPreferences("com.example.something", MODE_PRIVATE); String message = preferences.getString("somethingID", ""); return message; } 

The underwater stone: Methods work asynchronously (or did it seem to me ?!). If saving is called very often, the data may come in the wrong order.

In fact, the bug will look like this - not the last line has been saved, but the previous one. To solve the problem, we numbered data in JavaScript and native code. In the method for saving there is a variable numberDataForSave, which checks the index of the stored data. If the index is less than the last stored, the data is ignored.

Reef : The layout of the main activity is usually a lot of superfluous.

For one WebView in full screen, we do not need so much. You can cut by leaving:

 <?xml version="1.0" encoding="utf-8"?> <WebView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/webview" android:layout_width="fill_parent" android:layout_height="fill_parent" android:scrollbars="none" /> 

The underwater stone: Sent SMS without a SIM card. SMS did not go away, but callback returned true.

If you are using PhoneGap, it is possible that the SMS sending module for Android is written crookedly (in any case, I personally encountered this problem). It returns true on any outcome. To implement this normally, let's build a bridge for sending SMS from Java to JavaScript:

 @JavascriptInterface public void sendSms(String phoneNumber, String message) { SmsManager sms = SmsManager.getDefault(); sms.sendTextMessage(phoneNumber, null, message, sendSmsPendingIntent, null); } 

And add the method to the class MainActivity:

 private PendingIntent registerSentSmsReceiver() { String SENT = "SMS_SENT"; PendingIntent sentPI = PendingIntent.getBroadcast(MainActivity.this, 0, new Intent(SENT), 0); sendSmsReceiver = new BroadcastReceiver() { public void onReceive(Context arg0, Intent arg1) { switch (getResultCode()) { case Activity.RESULT_OK: vw.loadUrl("javascript: smsSend(true);"); break; default: vw.loadUrl("javascript: smsSend(false);"); break; } } }; registerReceiver(sendSmsReceiver, new IntentFilter(SENT)); return sentPI; } 

To prevent the application from crashing with the new class, you need to update onDestroy:

 @Override public void onDestroy() { super.onDestroy(); if (sendSmsReceiver != null) { unregisterReceiver(sendSmsReceiver); } } 

Remember, sendSmsReceiver always needs to be checked out when destroying.

The underwater stone: On Android, from Samsung, WebView slows down on touch events. Moreover, it can even stop the rendering when the finger is held.

From this bugs will not go anywhere. So did it. This was one of the reasons for promoting CocoonJS. If you wish, you can rewrite the canvas element to the native one (using all the same bridges), but this is already trash. It’s better to write everything in Java at once. But, nevertheless, it still makes sense to compile an APK file, since besides Samsung, there are a lot of phones from other manufacturers, and everything may not be so bad there. Well, in the same Tizenʻe in this regard, a fairy tale in general.

I apologize in advance for the crooked moments in Java, since still my javascript profile. Well, the demo is Russian and English from the article, if anyone is interested.

Source: https://habr.com/ru/post/214531/


All Articles