• 35755阅读
  • 1回复

[转载]Cross-Platform Applications in iOS and Android Stores with Qt [复制链接]

上一主题 下一主题
离线XChinux
 

只看楼主 倒序阅读 楼主  发表于: 2013-12-13
关键词: Qt,IOS,Android
原文见:  http://blog.qt.digia.com/blog/2013/12/10/cross-platform-applications-in-ios-and-android-stores-with-qt/



Cross-Platform Applications in iOS and Android Stores with Qt


Published Tuesday December 10th, 2013 | by Caroline Chao                
With Qt you can develop truly cross-platform applications thatwill work on desktop, embedded and mobile platforms. Qt 5.2 introducesfull support for Android and iOS platforms and thus enables the creationof cross-platform mobile apps that are packaged and deployed to bothGoogle Play and App Store from one code base. Instead of writing twoversions of the same code with two different technologies, you can writeit once with Qt!
Using Qt Creator IDE 3.0 which comes with Qt 5.2, you can develop,deploy and debug your application on Android and iOS devices as well ason the simulators.
You can do all of your iOS development in Qt Creator thanks to theexperimental iOS plugin. After creating the project and testing it ondevices/emulators, you need to launch XCode for creating the archive,inputting the store related details (like icons) and finally to deployit to the App Store.
For Android versions, you can do pretty much everything from withinQt Creator, which creates and signs the Android APK package for GooglePlay deployment.
For details, see the Tutorial video: Getting Started with Qt for Mobile.
To give you a couple of nice examples to play around with Qt 5.2, weare introducing two Qt Quick-based cross-platform applications that arenow available in Google Play and App Store:
- Quick Forecast, a web-connected weather application
- Flyingbus, a game about.. well.. a flying bus
Let’s have a closer look!
Quick Forecast  
There are already thousands of weather applications in the market –is Quick Forecast the revolutionary weather application we were alldreaming of! No, it is not.   But what’s inside and the prospect of what you can achieve with Qt 5.2 is worth a look!
Quick Forecast is a cross-platform weather application with nodependencies on Qt Widgets. Based on Qt 5.2, its user interface iswritten using Qt Quick Controls and Layouts with a custom style done with the styling API. The application backend is written using the Qt/C++ APIs.
Quick Forecast runs on desktop and mobile platforms while beingprimarily designed and styled to target mobile devices. A complex UIdesigned for desktop usually doesn’t fit well on small screens; theopposite works better. For the purpose of this demo, we think this is agood compromise. For real use cases, you would probably create anapplication with two different UI layouts targeting desktop and mobile,but using the same backend. With Qt Quick, and especially when using QtQuick Controls, this is a rather straightforward task.
Qt Quick Controls, introduced in Qt 5.1, are the Qt Quick equivalents to Qt Widgets. With Qt 5.2, new features and improvementshave been added and Quick Forecast shows some of them:
  • Transient scroll bars
  • Flicking in ScrollView
  • BusyIndicator
  • Added baseline support in Qt Quick Layouts
  • Extended styling APIs

See it live yourself!


The same code is used on all platforms. In order to preserve thedesign’s scalability and assure relevant font sizes on different screensand screen orientations, a ratio is used to adjust the geometryaccordingly.
Below is the current weather forecast for Oslo; chilly.
  

Try it out!
  
Quick Forecast source code is available from:
https://qt.gitorious.org/qt-labs/weather-app
Note: The weather data is provided by yr.no. In order to use the yr.no weather data service, refer to their terms and conditions of use. http://om.yr.no/verdata/free-weather-data (The information is only available in Norwegian)
Flyingbus  

Flyingbus was an older Qt Quick demo game from theearly days of Qt Quick which has now been revived with some enhancedgraphics work as well as being ported to use Qt Quick 2. The actualporting job (from Qt 4.7 + Qt Quick 1 to Qt 5.2 + Qt Quick 2) was astraightforward 15-minute replacement of import statements.
Flyingbus demonstrates how it is possible to create a cross platformgame that looks good on a variety of form factors.  There are soundeffects played from Qt Quick using Qt Multimedia on both iOS andAndroid.  If you are using a high DPI device like a Retina display iPadthen the graphics should be displayed in higher quality.

Try it out!
  


二笔 openSUSE Vim N9 BB10 XChinux@163.com 网易博客 腾讯微博
承接C++/Qt、Qt UI界面、PHP及预算报销系统开发业务
离线XChinux

只看该作者 1楼 发表于: 2013-12-13
转自: http://blog.qt.digia.com/blog/2013/12/12/implementing-in-app-purchase-on-android/




Implementing in app purchase on Android


Published Thursday December 12th, 2013 | by Eskil Abrahamsen Blomfeldt                
In-app purchase is an increasingly popular form ofapplication monetization, especially in saturated markets such as themobile apps markets, since it enables users to download applications forfree and try them out before they pay. When the only thing you have tolose is time, the threshold for installing an unknown application islikely a lot lower than if you had to pay up front.
But you already know this, because you have been asking us regularlyhow to do it in Qt for the past few months. Our short answer is that Qtdoes not have a cross-platform API for this, at least not yet, so youwill have to add some platform-specific code to your application.
This blog post is the long answer. Using a simple game as an example,I’ll go through each of the steps to enable in-app purchases in anAndroid application. The application source is also available, so you can take a look at it before doing your own implementation.
So what is it?
For those of you who have not been asking us about this, and therefore cannot be proven to know what in-app purchases are, I’ll give a very quick overview.
In brief, in-app purchase is this: Instead of paying todownload and run an application, there are instead features of theapplication that are available for purchase when you are already runningit. Some examples of the types of purchases the application can provideare:
  • Subscription to content, like in an online magazine with monthly content updates.
  • Consumable items, e.g. a magic potion in a game, of which you can buy an unlimited amount.
  • Or a permanent purchase to unlock vital features of the application. For instance, the Savefunction in a photo editing application might be disabled until you’vepaid an in-app fee, so that users can test the application before theydecide if it’s worth the price.

The way this all works in Google Play is that you add one or more“In-app Products” to the store listing for your application. You giveeach item a product ID (SKU), and add code in your applicationto launch the purchase process. The process itself is out of your hands.The user will be asked to confirm the purchase, and your applicationwill be informed about the status of the transaction asynchronously. Iwill not go into the details of the APIs in this blog, so if you’replanning to implement something like this, make sure you read the Android documentation on the subject first.
The example
As my example, I’ve written a simple Hangman game in QML.Consonants are free (except for the traditional non-monetary penalty ofwrong guesses), but you have to pay a minimal fee for vowels. Theapplication is available for download,so you can quickly run it and see it in action, but rather than buy anyvowels in the downloaded game, you can compile it yourself from source,upload it as a draft to your personal Google Play Developer Console andrun it unpublished. As long as your personal version of the applicationremains unpublished, you can test the full purchase process withoutactually being charged. Just make sure you add your test account in the Settings page of your Developer Console.
I should note that if this were a proper game, I would probably haveusers pay for packs of, say, 50 vowels or so, both to avoid overpricingand to avoid going through the steps for purchasing every time they wantto guess for a vowel. But for this example, it would only make the codemore complex, so I’ve left it as simple as possible.
The game
First, a quick run-through of how the game works: I started bydeveloping everything in the game on my desktop machine, leaving out theplatform-specific purchase code for now. This approach has severalbenefits over writing directly for device:
  • It makes iterative testing faster, as the application does not need to be deployed before it can be run.
  • It makes it easy to test that the application adapts to differentscreen sizes, as I can easily resize my main window and see it adapt inreal time.
  • And it forces me to write the application in a cross-platform way,so that it can be ported to other platforms later with relatively littleeffort.

Regardless of what type of application I’m writing, I’ll usually tryto take this approach, and if the application depends on inputs orfeatures that are only available on the target platform, I’ll make aneffort to simulate them in the desktop build.


The game itself is just a scene in QML, hooked up to a Data object written in C++. The Dataobject is used to manage the current solution word and the guesses. Youplay the game by selecting letters at the bottom. If you select avowel, you will be asked to pay before it is tested against thesolution. You can also wager a guess for the full solution if you thinkyou’ve got it. That part is free
The word list is based on the public domain ENABLE dictionary by AlanBeale and M. Cooper. This initially contained a lot of words we hadnever heard before, but Paul Olav Tvete limited it to words used in theQt documentation or on the Qt Development mailing list, so the gameshould be familiar to avid Qt users
As you make guesses, they will either be displayed as part of thesolution if they are contained in the word, or one line of the hangmanpicture will be added to the square in the middle of the screen. Whenthe entire drawing is finished, the game will be over. If you manage toguess the solution before this happens, you win the game.

You can click on the Reset button in the top left corner to get a new puzzle, picked randomly from the ENABLE dictionary, or you can hit the Reveal button to give up and show the solution.
I won’t go into any more detail about the game itself. Theinteresting part here is the in-app purchasing, so I’ll spend the restof the blog post on that.
On iOS
Before I continue with the technical details, I’ll briefly mention iOSas well: As I said, while the the example code is not 100%cross-platform, it is structured to be easily adaptable to otherplatforms, attempting to minimize the amount of extra code you have towrite to port it. And in fact, Andy Nichols already wrote some code tomove the game to iOS which is nearly done, but not quite ready forrelease just yet. He will blog about this later on, but the code we haveso far is already in the source repository for you to look at.
In-app purchase: Structure
So, I wanted to finish the desktop version of the game beforeimplementing the Android-specific part. As an abstraction of theplatform-specific code I would have to add in later, I identified theneed for the following function to take care of actually purchasing avowel from the market place:
  1. void buyVowel(const QChar &vowel);

Since the Android in-app purchase APIs are asynchronous, I also added a signal which is emitted when the purchase succeeds:
  1. signals:
  2.     void vowelBought(const QChar &vowel);

When this signal is emitted, it means the transaction has been made,and I add the selected vowel to the list of current guesses. In order torun the application on desktop (and other platforms as well), I add afile named data_default.cpp with the following default implementation of the buyVowel() function:
  1. void Data::buyVowel(const QChar &vowel)
  2. {
  3.     emit vowelBought(vowel);
  4. }

This code will never be compiled on Android, but for other platforms,it will imitate a successful purchase. To avoid compiling the code onAndroid, I add the following to my .pro file:
  1. android: SOURCES += data_android.cpp
  2. else: SOURCES += data_default.cpp

Now it’s quite easy for me to add an Android-specific implementation of buyVowel() in data_android.cpp, and also to add implementations for other platforms down the road.
The Java code
Since the Android APIs are based in Java, I made the main bulk of myimplementation in Java and then later accessed this through JNI from myC++ application. I won’t go into detail about the actual usage of theAndroid APIs, since that’s already thoroughly documented in the official documentation. I will however highlight a few areas of particular interest in the Java code for my game.
First of all, I needed to add the Android-specific files to my project. I started by adding an AndroidManifest.xml file to my project using Qt Creator.

Qt Creator gives you the option to quickly add a default manifest to your app.
I chose to put the manifest in the subdirectory android-source.After adding this directory, all my Android-specific files can go intoit. In general, it should contain the files you want to add to theAndroid project and the directory structure should follow the regular Android project directory structure.The contents of the directory will later be merged with a template fromQt, so you should only put your additions and modifications here. Thereis no need to include an entire Android project.
Next, I added a new Activity subclass. Java sources need to go in the srcdirectory to be recognized correctly when building the package, and in asubpath which matches the package namespace of the class. In my case Iplaced the class in android-source/src/org/qtproject/example/hangman/.
To make sure Qt is loaded correctly, I had to subclass Qt’s default Activity class. This is very important.
  1. import org.qtproject.qt5.android.bindings.QtActivity;
  2. public class HangmanActivity extends QtActivity

I also had to make sure to call into the super class from all reimplementations of methods. Like here:
  1. @Override
  2. public void onCreate(Bundle savedInstanceState)
  3. {
  4.     super.onCreate(savedInstanceState);
  5.     bindService(new Intent("com.android.vending.billing.InAppBillingService.BIND"),
  6.                 m_serviceConnection, Context.BIND_AUTO_CREATE);
  7. }

In my game, the Activity is a singleton, so I store a static reference to the object in the constructor:
  1. private static HangmanActivity m_instance;
  2. public HangmanActivity()
  3. {
  4.     m_instance = this;
  5. }

(My C++ Data class has the same logic. I’m doing this sothat I can facilitate the communication between the Java and the C++code using static methods. For a more complex example, it’s alsopossible to store references and pointers in each C++ and Java objectthat maps it to its equivalent in the other language, but that is notnecessary in this game.)
In my Activity class in Java, I implemented a method to handle the request for purchasing a vowel:
  1. public static void buyVowel(char vowel)

And I also added a native callback method which I can call when I’vereceived the asynchronous message that the vowel has been purchased:
  1. private static native void vowelBought(char vowel);

The native keyword indicates that the method is implemented in native code. I’ll come back to the implementation of that later.
My buyVowel() method follows the documentation closely. The main part to note is the following snippet:
  1. Bundle buyIntentBundle = m_instance.m_service.getBuyIntent(3,
  2.                                                            m_instance.getPackageName(),
  3.                                                            "vowel",
  4.                                                            "inapp",
  5.                                                            "" + vowel);

This code will create a Buy Intent for API version 3, the package name of my application (note that this is the application package in the Google Play store and AndroidManifest.xml, not the package namespace of the Java class), and the in-app product identified as “vowel”. In addition, I’m passing “inapp” as the type and I’m passing the actual vowel requestedas the developer payload. The latter will be returned back to me alongwith the message of a successful purchase, so that I can easily informmy application of which letter was actually purchased.
The message informing my application whether the purchase was successful or not is delivered in the method onActivityResult(). In this method I can retrieve several pieces of information about the purchase in JSON format:
  1. JSONObject jo = new JSONObject(purchaseData);
  2. String sku = jo.getString("productId");
  3. int purchaseState = jo.getInt("purchaseState");
  4. String vowel = jo.getString("developerPayload");
  5. String purchaseToken = jo.getString("purchaseToken");

I quickly verify that it’s the correct product and that it was successfully purchased (purchaseState == 0). If this is the case, I inform my native code of the purchase, and I immediately consume it:
  1. if (sku.equals("vowel") && purchaseState == 0) {
  2.     vowelBought(vowel.charAt(0));
  3.     // Make sure we can buy a vowel again
  4.     m_service.consumePurchase(3, getPackageName(), purchaseToken);
  5.     return;
  6. }

Consuming the purchase is very important in this case, as you willnot be able to purchase the same product again later unless it has beenconsumed. So for consumable items, like these vowels, which you shouldbe able to purchase an unlimited number of times, we must consume themimmediately after they have been registered in the application. Forpermanent purchases (imagine if I also had a slightly more expensiveproduct called “Free vowels forever”), you would skip this step. Youcould then later query Google Play for the product and it would tell youthat it has already been purchased.
Finally, in order to be able to access the billing API, I need to addits interface to my project, as explained in the Android documentation.I copy the file IInAppBillingService.aidl into subdirectory android-source/src/com/android/vending/billing.
AndroidManifest.xml
A few modifications are necessary to the default AndroidManifest.xmlfile. This is the file which describes your application to the devicethat is running it, and also to the Google Play store which needs theinformation to properly advertise it to the correct devices.
Like for all Android applications, I need to set an icon, a name, apackage name, etc. In the source tree, I’ve left the package name empty.This is intentional, as you will need a unique package name for yourinstance of the application in order to register it in Google Play. Makesure you set this before building. I’ve also locked the screenorientation to “portrait”, because that’s how the application wasdesigned.
Specifically for using the in-app purchase API, I need to declare that I am using the “BILLING” permission:
  1. <uses-permission android:name="com.android.vending.BILLING"/>

If I neglect to add this, then my application will get an exception when trying to access the APIs.
In addition, I need to set my own Activity class as the main activity rather than Qt’s class:
  1. <activity ... android:name="org.qtproject.example.hangman.HangmanActivity">

This ensures that the HangmanActivity class will be instantiated when the device launches the application.
The native code
All the Android-specific native code is in the data_android.cpp file. As mentioned, it needs a platform-specific implementation of the buyVowel() function:
  1. void Data::buyVowel(const QChar &vowel)
  2. {
  3.     QAndroidJniObject::callStaticMethod("org/qtproject/example/hangman/HangmanActivity",
  4.                                               "buyVowel",
  5.                                               "(C)V",
  6.                                               jchar(vowel.unicode()));
  7. }

The only thing this code does is issue a call to the Java methoddescribed in the previous section. Thus, it will launch an asynchronousrequest for the vowel and we will wait until the payment goes throughbefore doing anything else.
In addition, we need to implement the native vowelBought() method, which will be called from Java when the purchase is successful:
  1. static void vowelBought(JNIEnv *, jclass /*clazz*/, jchar c)
  2. {
  3.     Data::instance()->vowelBought(QChar(c));
  4. }

This is just a regular C++ function, with the exception that it will always get a JNIEnv pointer as its first argument, and a jclassargument which is a reference to the declaring class in Java (sincethis is a static method.) As you can see, it simply accesses the Data singleton and emits the vowelBought signal to register the purchased vowel.
Finally, the native method is registered using a standard boilerplate when the library is loaded. Check the code to see the full thing.
Putting it in the store
Then we’ve reached the final step, which was to actually upload the APKto the Google Play market and add the products for purchase there. Notethat you do not have to publish the application in order to test thein-app purchases: You can keep the APK in Draft mode and mark the products as “To be activated”,in which case you have to handle the distribution of the applicationyourself, but the Google accounts listed in the “LICENSE TESTING”section of your Developer Console Settings will be able to make testpurchases once they have installed it. You can also publish a Betaversion of your application in the store, in which case you can managewho will be able to download it and make test purchases using GoogleGroups.
I started by registering a listing for my application. Once this wasdone, and I’d filled out the necessary pieces of information, I had toupload the APK. You cannot register any products in the listing before you’ve uploaded an APK.(Make sure you sign the package with your private key before uploadingit. The whole process of generating a key and signing the package can bedone from inside Qt Creator, in the Project -> Run -> Deploy configurations.)
Once this has been done, I can add a product. I click on the “In-appproducts” tab, and select to create a new product. Then I fill out thenecessary information:
Google Play registration of my “vowel” product
I had to make sure I picked “Managed product” here, as this is theonly type supported by API version 3. It means that Google Play willremember that the product was purchased for you, and you will need toexplicitly consume it before you can purchase the same product again.
When the product has been added, I can add some more details:
You can add descriptions and pricing information to your products in the developer console.
I’ve added a short description of the item, and set the price to theminimum possible price (6 NOK which is approximately 1 USD). I also makesure to mark the product “To be activated” so that it can be purchased.When the application is published, the product will become activatedautomatically.
Done
And that’s it. I can now run the application on my devices and purchasevowels as much as I want. Until the application is published into“Production”, no transactions will actually be carried through, so youcan test your local build without fearing that you’ll run out of money.
But do note that if you decide to use Digia’s version of theapplication, then purchases are real, since Google Play has a setminimum price.
Good luck!
二笔 openSUSE Vim N9 BB10 XChinux@163.com 网易博客 腾讯微博
承接C++/Qt、Qt UI界面、PHP及预算报销系统开发业务
快速回复
限100 字节
 
上一个 下一个