Gratis- och betalapp med samma kodbas

Diskussion i 'Frågor, support och diskussion' startad av e7andy, 29 sept 2012.

  1. e7andy

    e7andy Professional Droid Hedersmedlem

    Blev medlem:
    14 okt 2009
    Inlägg:
    2 349
    Mottagna gillanden:
    835
    Telefon:
    Huawei P10 Plus

    MINA ENHETER

    Telefon:
    Huawei P10 Plus
    Telefon 2:
    Nexus 5
    Telefon 3:
    ADP1
    Övrigt:
    LG G Watch R, ChromeCast
    Jag har efter lanseringen av MantisDroid insett att jag även behöver publicera en gratisversion, så att man kan ladda hem appen och se om den är något att ha innan man betalar för fullversionen. Visst, man har 15 minuter på sig att testa en betalapp, men det är inte många som gör det eftersom 15 minuter är så väldigt kort tid.

    Lösningen är alltså att skapa en gratisversion av min app med begränsad funktionalitet.

    Hur gör man en gratisversion utan att duplicera all kod?

    Det är faktiskt ganska enkelt. En app kan använda en annan app och referera till den som ett library (bibliotek). På så sätt kan den nya appen utnyttja allt i den andra. Dock kan inte en app som markerats som library längre vara en app som går att installera på en enhet. Därför behövs det 3 projekt: En betalapp, en gratisapp och en library-app. I mitt fall så ligger all kod i betalappen som redan är publicerad på Play Store.
    1. Skapa library-appen genom att skapa ett nytt projekt med lämpligt paketnamn, ex. se.nextsource.android.mantisdroid
      Det är samma paketnamn som i betalappen, men det gör ingenting.
    2. Öppna library-appen i Eclipse, ta fram properties för projektet, klicka på Android och markera checkboxen "Is Library".
    3. Flytta kod och resurser som ska vara gemensamma från betalappen till library-appen.
    4. Öppna properties för betalappen, klicka på Android och lägg till library-appen som ett Library.
    5. Skapa gratisappen som ett nytt projekt med nytt paketnamn, ex. se.nextsource.android.mantisdroidfree
    6. Öppna properties för gratisappen, klicka på Android och lägg till library-appen som ett Library.
    7. Deklarera de komponententer (activity, service, receiver, provider etc. och även permissions, uses-library etc.) som gratis- och betalappen använder från library-appen.
    8. Nu har du 2 appar med exakt samma kodbas men med olika paketnamn.
    9. För att särskilja versionerna mellan varandra så jag lagt till en ny Application-klass i gratis- och betalapp-projekten som ärver från en Application-klassen som ligger i library-appen. Vid onCreate så sätts vilken version som körs i superklassen och sedan anropas super.onCreate(). I superklassens onCreate så slår jag av och på olika funktioner som ska skilja applikationerna åt.
    Ref: http://developer.android.com/tools/projects/projects-eclipse.html

    Problem jag stötte på
    -Se till att ingen duplicering av kod eller res-filer finns mellan library-appen och i de appar som tar in biblioteket för då bygger det inte!
    -Från SDK 14 så går det inte att använda resId i switch-case i ett library eftersom de inte längre är final så ändra det med refactor-funktionen i Eclipse till if-else. Irriterande, men det finns en bra förklaring:
    http://android-developers.blogspot.se/2011/10/changes-to-library-projects-in-android.html
    -Proguard fick svårt att obfuskera koden i biblioteket så jag var tvungen att lägga till följande i proguard.cfg:
    Kod:
    -keep public class com.google.android.vending.licensing.ILicensingService
    -dontwarn org.xmlpull.v1.**
    -dontwarn android.support.**
    Obs! Tänk på att lägga till det i alla 3 proguard.cfg.
    -Inget problem egentligen, men för att signera applikationen så behöver jag skriva in 4 lösenord; 2 i library-appen och 2 i appen jag bygger.

    Resultatet
    Nu när allt är klart så behöver jag bara göra följande för att införa kodändringar, bygga och publicera mina 2 appar:

    1. Gör ändringar och fixar i library-appen.
    2. Ändra versionsnumret i apparna så att de stegas upp.
    3. Kör kommandot: ant release på båda apparna så att de bygger, obfuskeras och signeras för release.
    4. Testkör båda apparna så att de fungerar och ändringarna är testade.
    5. Lägg upp de 2 nya apk:erna på Play Store.
    6. Klart!

    De 2 apparna
    MantisDroid
    MantisDroid Free
     
    Last edited: 29 sept 2012
  2. Ibasto

    Ibasto Infant Droid Medlem

    Blev medlem:
    8 jul 2014
    Inlägg:
    8
    Mottagna gillanden:
    0

    MINA ENHETER

    nyfiken

    Har läst på din beskrivning och ett gäng andra ett otal gånger.
    Sitter i exakt samma sits med en betalapp ( min första app ) och behovet av en trailversion.

    Följde din beskrivning och skapade ett lib som fungerade tills jag upptäckte att det var smidigare i Android Studio att skapa olika build flavors så det slutade med att jag skrev om koden igen och har nu en free och en paid funkar klockrent. All "vanlig kod" ligger i en main sen har resp flavor en StartActivity där paid bara startar main och sen avslutar sig själv.
    I free är tanken att jag ska kolla installationsdatum för att sen avgöra om jag ska starta main eller visa att testperioden är slut. Nu är jag lite nyfiken på hur du gör i din trailversion.
    För min del så är jag helt nöjd om majoriteten av användarna inte kommer runt spärren. Jag kan köpa om du resetar hela telefon eller ändrar datum. det tror jag inte folk orkar med för att köra min lilla app utan att betala. Däremot får det gärna vara nivån över att det räcker med att installera om appen för att få en ny trailperiod

    /Ibasto
     
  3. e7andy

    e7andy Professional Droid Hedersmedlem

    Blev medlem:
    14 okt 2009
    Inlägg:
    2 349
    Mottagna gillanden:
    835
    Telefon:
    Huawei P10 Plus

    MINA ENHETER

    Telefon:
    Huawei P10 Plus
    Telefon 2:
    Nexus 5
    Telefon 3:
    ADP1
    Övrigt:
    LG G Watch R, ChromeCast
    Jag har ingen trial-version utan det är en fungerande app som är gratis och har begränsad funktionalitet.
    Fullversionen är en annan app.

    Jag har däremot funderat på hur man kan göra en trial-version och den lösning jag kommit fram till är att om man kör i en trial-period så måste appen kontakta min server och få en token eller liknande. Googlekontot är identifierare. Om trial-perioden har gått ut så slutar den att kontakta servern. På så sätt så begränsar jag trafiken till min server.
    Om någon installerar om för att starta en ny trial-period så har min server koll på att trial-perioden redan är förbrukad.
     
  4. e7andy

    e7andy Professional Droid Hedersmedlem

    Blev medlem:
    14 okt 2009
    Inlägg:
    2 349
    Mottagna gillanden:
    835
    Telefon:
    Huawei P10 Plus

    MINA ENHETER

    Telefon:
    Huawei P10 Plus
    Telefon 2:
    Nexus 5
    Telefon 3:
    ADP1
    Övrigt:
    LG G Watch R, ChromeCast
    Jag har inte kollat om Google har någon funktion för att hålla koll på datum för installationer och på så sätt hantera trial-perioder. Det är värt att kolla upp.
     
  5. Zooklubba

    Zooklubba Android Medlem

    Blev medlem:
    10 jul 2010
    Inlägg:
    6 448
    Mottagna gillanden:
    2 199

    MINA ENHETER

    Ibasto gillar detta.
  6. Ibasto

    Ibasto Infant Droid Medlem

    Blev medlem:
    8 jul 2014
    Inlägg:
    8
    Mottagna gillanden:
    0

    MINA ENHETER

    @e7andy
    För min del så känns serverlösningen overkill för min lilla app. För större/seriösare appar är den nog det bästa.

    @Zooklubba
    Det är nått i den stilen som jag tittar på just nu men den behöver kombineras men nån form av backup, annars räcker det att avinstallera och installera om appen för att få en "ny period" och det känns lite för enkelt.

    Tittar just nu på att göra backup på en fil där jag lagrar installationsdatum, än så länge utan att få det att lira riktigt.
    http://developer.android.com/guide/topics/data/backup.html

    Alla tips mottages tacksamt

    /Ibasto
     
  7. e7andy

    e7andy Professional Droid Hedersmedlem

    Blev medlem:
    14 okt 2009
    Inlägg:
    2 349
    Mottagna gillanden:
    835
    Telefon:
    Huawei P10 Plus

    MINA ENHETER

    Telefon:
    Huawei P10 Plus
    Telefon 2:
    Nexus 5
    Telefon 3:
    ADP1
    Övrigt:
    LG G Watch R, ChromeCast
    Jag har aktiverat backup-funktionen i min app, men inte fått det att fungera.
    Exempelkoden är ju väldigt enkel och kort så det borde fungera direkt.

    Här kan du läsa om hur långt jag kommit:
    https://swedroid.se/forum/showthread.php?t=86154
     
  8. Ibasto

    Ibasto Infant Droid Medlem

    Blev medlem:
    8 jul 2014
    Inlägg:
    8
    Mottagna gillanden:
    0

    MINA ENHETER

    hantering av trailversion

    Så, då verkar det fungera.
    Som roockie vill jag gärna ha era synpunkter. Framförallt fungerar detta som jag har tänkt? Det ska inte räcka att avinstallera appen och installera om. Skulle nån stackare göra en factoryreset så bjuder jag gladeligen på 30 dagar till :)

    koden är ett hopklipp av olika exempel och egen kod så den kanske inte är det vackraste men det är min andra app så jag överlever. hoppas ni gör det oxå.

    Som tidigare nämnts så använder jag Android Studio och build flavors med Gradle. All kod ligger i mappen main sen har jag 2 st olika StartActivity med varsin layout. Jag väljer i "build variant" vilken app jag ska bygga.

    Trail:
    Kod:
    package se.ollivergarden.hoppapp;
    
    
    import android.app.Activity;
    import android.app.backup.BackupManager;
    import android.content.Intent;
    import android.content.pm.PackageManager;
    import android.os.Bundle;
    import android.util.Log;
    import android.widget.TextView;
    import android.widget.Toast;
    import android.content.Context;
    import java.io.File;
    import java.io.IOException;
    import java.io.RandomAccessFile;
    import java.util.Date;
    
    /**
     *
     * @author Ibasto
     * @version 1.0
     */
    public class StartActivity extends Activity {
        static final String msg = "Trail, startactivity : ";
        static final Object[] sDataLock = new Object[0];
        public static final String MyPREFERENCES = "file" ;
        File mDataFile;
        BackupManager mBackupManager;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            Log.d(msg, " onCreate() event");
            super.onCreate(savedInstanceState);
            setContentView(R.layout.main_trail);
            mDataFile = new File(getFilesDir(), StartActivity.MyPREFERENCES);
            mBackupManager = new BackupManager(this);
            launch(this);
        }
    
    
        public void launch(Context context){
            RandomAccessFile file;
            long longDate;
            Date installationDate;
            Date currentDate = new Date();
            int diffDays = 0;
            int trailPeriod = 30;
    
            boolean notExpired = false;
    
            synchronized (StartActivity.sDataLock) {
                boolean exists = mDataFile.exists();
                try {
                    file = new RandomAccessFile(mDataFile, "rw");
                    if (exists) {
                        Log.v(msg, "datafile exists");
                        longDate = file.readLong();
                        installationDate = new Date(longDate);
                        Log.v(msg, "  installationDate = " + installationDate);
                        diffDays = ((int)((currentDate.getTime()/(24*60*60*1000))
                                -(int)(installationDate.getTime()/(24*60*60*1000))));
                    }
                    else {
                        Log.v(msg, "creating default datafile");
                        try {
                            longDate = context
                                    .getPackageManager()
                                    .getPackageInfo("se.ollivergarden.hoppapp", 0)
                                    .firstInstallTime;
                            writeDataToFileLocked(file, longDate);
                            buText.setText("no Backup");
                            mBackupManager.dataChanged();
    
                        }
                        catch (PackageManager.NameNotFoundException nnf){
                            Log.e(msg, "NameNotFoundException: " + nnf.toString());
                        }
                    }
                } catch (IOException ioe) {
                    Log.e(msg, "IOException: " + ioe.toString());
                }
            }
    
            //Evaluation
            notExpired = diffDays < trailPeriod;
    
            Log.v(msg, "Diffdays: " + diffDays + "notExpired: " + notExpired);
    
    
            //Trail period is still valid, start main activity
            if ( notExpired ){
                Log.d(msg, "Launch true");
                Intent intent = new Intent(this, MainActivity.class);
                startActivity(intent);
            }
            //Trail period has ended. Show error message
            else{
                Log.d(msg, "Launch false");
                //TODO stränghantering
                Toast.makeText(StartActivity.this,
                        "trail period is over", Toast.LENGTH_LONG).show();
            }
        }
        void writeDataToFileLocked(RandomAccessFile file, long instDate)throws IOException {
            file.setLength(0L);
            file.writeLong(instDate);
            Log.v(msg, "NEW STATE: instDate=" + instDate);
        }
    
    
    
    }
    
    Full:
    Kod:
    package se.ollivergarden.hoppapp;
    
    import android.app.Activity;
    import android.content.Context;
    import android.content.Intent;
    import android.content.SharedPreferences;
    import android.os.Bundle;
    import android.text.util.Linkify;
    import android.util.Log;
    import android.view.Gravity;
    import android.view.Menu;
    import android.view.View;
    import android.widget.ArrayAdapter;
    import android.widget.Spinner;
    import android.widget.TextView;
    
    import java.util.ArrayList;
    
    
    /**
     *
     * @author Ibasto
     * @version 1.0
     */
    public class StartActivity extends Activity {
       String msg = "Android, full, startActivity: ";
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            Log.d(msg, "onCreate() event");
            super.onCreate(savedInstanceState);
            Intent intent = new Intent(this, MainActivity.class);
            startActivity(intent);
            finish();
        }
    }
    
     
  9. e7andy

    e7andy Professional Droid Hedersmedlem

    Blev medlem:
    14 okt 2009
    Inlägg:
    2 349
    Mottagna gillanden:
    835
    Telefon:
    Huawei P10 Plus

    MINA ENHETER

    Telefon:
    Huawei P10 Plus
    Telefon 2:
    Nexus 5
    Telefon 3:
    ADP1
    Övrigt:
    LG G Watch R, ChromeCast
    Har du testat att avinstallera och installera på nytt och sett att en ny trial-period inte börjar?
    Den filen du skriver till lagras i internal storage och när appen avinstalleras så raderas de filer som appen skapat där. För att förhindra det så måste du skriva på external storage, men inte i den katalog du får med getExternalFilesDir() för de filerna raderas också:
    http://developer.android.com/training/basics/data-storage/files.html

    Tips: Det heter trial. Inte trail.
     
  10. Ibasto

    Ibasto Infant Droid Medlem

    Blev medlem:
    8 jul 2014
    Inlägg:
    8
    Mottagna gillanden:
    0

    MINA ENHETER

    När jag avinstallerar så startar den upp första gången, men den andra gången jag startar upp så verkar den läsa in backupen på nått automagiskt sätt.
    Man kan oxå se att jag har pillat med trail versionen ett tag. Det är inte många rader kod som är samma som 24/7 men paketnamnet är kvar.

    Se utskrift från logcat:
    Kod:
    09-12 22:45:52.201  10610-10610/se.ollivergarden.hoppapptrail D/Trail, startactivity :﹕ onCreate() event
    09-12 22:45:52.241  10610-10610/se.ollivergarden.hoppapptrail V/Trail, startactivity :﹕ creating default datafile
    09-12 22:45:52.241  10610-10610/se.ollivergarden.hoppapptrail V/Trail, startactivity :﹕ NEW STATE: instDate=1406225716959
    09-12 22:45:52.241  10610-10610/se.ollivergarden.hoppapptrail V/Trail, startactivity :﹕ Diffdays: 0notExpired: true
    09-12 22:45:52.241  10610-10610/se.ollivergarden.hoppapptrail D/Trail, startactivity :﹕ Launch true
    09-12 22:45:52.301  10610-10610/se.ollivergarden.hoppapptrail D/Android, main, MainActivity :﹕ onCreate() event
    09-12 22:45:52.401  10610-10610/se.ollivergarden.hoppapptrail D/libEGL﹕ loaded /system/lib/egl/libEGL_adreno200.so
    09-12 22:45:52.401  10610-10610/se.ollivergarden.hoppapptrail D/libEGL﹕ loaded /system/lib/egl/libGLESv1_CM_adreno200.so
    09-12 22:45:52.411  10610-10610/se.ollivergarden.hoppapptrail D/libEGL﹕ loaded /system/lib/egl/libGLESv2_adreno200.so
    09-12 22:45:52.411  10610-10610/se.ollivergarden.hoppapptrail I/Adreno200-EGL﹕ <qeglDrvAPI_eglInitialize:265>: EGL 1.4 QUALCOMM build: AYELDER_AU_LINUX_ANDROID_JB_2.5.5.04.02.02.092.023+PATCH[ES]_msm8960_JB_2.5.5_CL3556704_release_ENGG (CL3556704)
        Build Date: 05/17/13 Fri
        Local Branch:
        Remote Branch: quic/jb_2.5.5
        Local Patches: 34c9e193f12610d3e68dabd6198d2c4bfbc66974 RB: Update the master timestamp of the hw_image in rb_texture_update_aliased
        Reconstruct Branch: AU_LINUX_ANDROID_JB_2.5.5.04.02.02.092.023 + 01d3c78 + e6f0547 +  LOCAL_PATCH[ES]
    09-12 22:45:52.441  10610-10610/se.ollivergarden.hoppapptrail D/OpenGLRenderer﹕ Enabling debug mode 0
    09-12 22:46:12.763  10610-10610/se.ollivergarden.hoppapptrail D/Trail, startactivity :﹕ onCreate() event
    09-12 22:46:12.793  10610-10610/se.ollivergarden.hoppapptrail V/Trail, startactivity :﹕ datafile exists
    09-12 22:46:12.793  10610-10610/se.ollivergarden.hoppapptrail V/Trail, startactivity :﹕ installationDate = Thu Jul 24 20:15:16 CEST 2014
    09-12 22:46:12.793  10610-10610/se.ollivergarden.hoppapptrail V/Trail, startactivity :﹕ Diffdays: 50notExpired: false
    09-12 22:46:12.793  10610-10610/se.ollivergarden.hoppapptrail D/Trail, startactivity :﹕ Launch false
     
  11. Ibasto

    Ibasto Infant Droid Medlem

    Blev medlem:
    8 jul 2014
    Inlägg:
    8
    Mottagna gillanden:
    0

    MINA ENHETER

    Tackar, det har jag inte sett. Tror jag har gjort konsekvent fel iallafall :)
     
  12. Ibasto

    Ibasto Infant Droid Medlem

    Blev medlem:
    8 jul 2014
    Inlägg:
    8
    Mottagna gillanden:
    0

    MINA ENHETER

    När jag har lagt upp den på play så lirar det inte längre. Dvs man får en ny trialperiod så fort man installerar om.

    Fortsättning följer...

    /Ibasto