пятница, мая 06, 2016

Gradle, conditionally include or exclude LeakCanary or something else

This example shows how to include/exculde LeakCanary and AndroidDevMetrics.

Note:
when LeakCanary (or AdnroidDevMetrics) is excluded, "standart" initialization code like

  MyApp
    onCreate() {
      LeakCanary.install(this);

will not be compiled: LeakCanary class will be not found.
So in this case I have to use reflection and Class.forName.


def useLeakCanary = false
def useAndroidDevMetrics = true

apply plugin: 'com.android.application'

if (useAndroidDevMetrics) {
    apply plugin: 'com.frogermcs.androiddevmetrics'
}

buildscript {
    repositories {
        mavenLocal()
        jcenter()
    }
    dependencies {
        ...
        classpath 'com.frogermcs.androiddevmetrics:androiddevmetrics-plugin:0.4'
    }
}

android {
    ...

    buildTypes {
        release {
            useLeakCanary = false
            buildConfigField "boolean", "USE_LEAK_CANARY", "false"

            buildConfigField "boolean", "USE_ANDROID_DEV_METRICS", "false"
            ...
        }

        debug {
            useLeakCanary = true
            buildConfigField "boolean", "USE_LEAK_CANARY", "$useLeakCanary"

            buildConfigField "boolean", "USE_ANDROID_DEV_METRICS", "$useAndroidDevMetrics"
        }
    }
}

dependencies {
    ...

    if (useLeakCanary) {
        debugCompile 'com.squareup.leakcanary:leakcanary-android:1.3.1'
        releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.3.1'
        testCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.3.1'
    }
}

-------------------


public class MyApplication extends Application {

    @Override
    public void onCreate() {
        super.onCreate();

        if (BuildConfig.USE_ANDROID_DEV_METRICS) {
            AndroidDevMetricsWrapper.initWith this
        }

        if (BuildConfig.USE_LEAK_CANARY) {
            LeakCanaryWrapper.install this
        }
    }
}

-------------------

class AndroidDevMetricsWrapper {

    public static void initWith(Application app) {
        try {
            Class leakCanaryClass = Class.forName "com.frogermcs.androiddevmetrics.AndroidDevMetrics"
            Method initWith = leakCanaryClass.getMethod "initWith", Context.class
            initWith.invoke null, app
        } catch (Exception e) {
            Log.e "AndroidDevMetricsWrapper", Log.getStackTraceString(e)
        }
    }
}

public class LeakCanaryWrapper {

    public static void install(Application app) {
        try {
            Class leakCanaryClass = Class.forName "com.squareup.leakcanary.LeakCanary"
            Method init = leakCanaryClass.getMethod "install", Application.class
            init.invoke null, app
        } catch (Exception e) {
            Log.e "LeakCanaryWrapper", Log.getStackTraceString(e)
        }
    }

}


How to use custom gradle generated constant in android java code

It is really easy to define gradle-generated constants and use those constants in android java code.
For example, I want to use a build type dependent constant RETRY_COUNT.

Gradle's class BuildType has a method:
    public void buildConfigField(String type, String name, String value)

This method can be used like this:

android {
...

    buildTypes {
        release {
            buildConfigField "int", "RETRY_COUNT", "3"
...
        }

        debug {
            buildConfigField "int", "RETRY_COUNT", "10"
...
        }
    }
}

Next, I rebuild my project, open BuildConfig and see:

public final class BuildConfig {
...
public static final boolean RETRY_COUNT = 10;
}



вторник, марта 15, 2016

Custom view, EditText, проблема при восстановлении состояния (use saveEnabled(fals))

С большим интересом потратил несколько часов на локализацию следующей проблемы:
- у меня есть custom view
- это композитный view, внутри которого используется EditText
- таких view у меня на экране несколько
- custom view обрабатывает сохранение и восстановление состояния в методах onSaveInstanceState и onRestoreInstanceState
- дебаггер показывает, что вызов этих методов и их работа выполняются корректно.

Но: после смены ориентации экрана в каждом из этих view содержится текст, который был введён в самый нижний из этих view.

Решение:
для EditText, входящего в состав композитного view, запретить сохранение состояния:

<EditText
    android:id="@+id/value"
    android:saveEnabled="false"
    .../>


понедельник, февраля 29, 2016

Robolectric, getActivityInfo, NullPointerException at start

I have created a robolectric test, when I started one, I have got an exception:

java.lang.NullPointerException
at org.robolectric.res.builder.DefaultPackageManager.getActivityInfo(DefaultPackageManager.java:173)
at org.robolectric.util.ActivityController.getActivityInfo(ActivityController.java:65)
at org.robolectric.util.ActivityController.attach(ActivityController.java:51)
at org.robolectric.util.ActivityController$1.run(ActivityController.java:121)
at org.robolectric.shadows.ShadowLooper.runPaused(ShadowLooper.java:304)
at org.robolectric.shadows.CoreShadowsAdapter$2.runPaused(CoreShadowsAdapter.java:45)
at org.robolectric.util.ActivityController.create(ActivityController.java:118)
at org.robolectric.util.ActivityController.create(ActivityController.java:129)
at ru.simpls.brs2.mobbank.ui.modules.freereqpayment.ui.FreeReqPaymentActivityTest.init(FreeReqPaymentActivityTest.java:29)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:24)
at org.robolectric.RobolectricTestRunner$2.evaluate(RobolectricTestRunner.java:251)
at org.robolectric.RobolectricTestRunner.runChild(RobolectricTestRunner.java:188)
at org.robolectric.RobolectricTestRunner.runChild(RobolectricTestRunner.java:54)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.robolectric.RobolectricTestRunner$1.evaluate(RobolectricTestRunner.java:152)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:78)
at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:212)
at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:68)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:140)

Process finished with exit code -1

Here is my test's declaration:

@RunWith(RobolectricGradleTestRunner.class)
@Config(
        constants = BuildConfig.class,
        sdk = 21,
        manifest = "src/main/AndroidManifest.xml")
public class FreeReqPaymentActivityTest {...}

In my project I have some modules. I have imported BuildConfig from another module, not from one where test is defined.

So when I have changed the import to a correct module, and problem was solved.

среда, декабря 09, 2015

First launch of Activity with Google Maps is very slow / Существенная задержка при запуске activity с google map

I have a "main" activity - and an activity with mapView. When that activity-with-mapView starts for a first time, it is really slow.

clocksmith's post gave me an idea to start initialization from main activity in a separate thread. And it really solves the problem.

Here is my code from "main" activity:

    public void onCreate(Bundle savedInstanceState) {
        ...

        Runnable initMap = () -> {
            BaseApplication.d("Start init mapView");
            MapView mapView = new MapView(Home.this);
            mapView.onCreate(null);
            BaseApplication.d("... done");
        };
        new Thread(initMap).start();
    }

mapView is never used - it's only for initialization purpose.

And here is a stack trace - just for info:

    12-09 19:31:54.442 17172-17341/my.app D/XXX: Start init mapView
    12-09 19:31:54.525 17172-17341/my.app I/zzy: Making Creator dynamically
    12-09 19:31:55.007 17172-17341/my.app D/ChimeraCfgMgr: Reading stored module config
    12-09 19:31:55.153 17172-17341/my.app D/ChimeraCfgMgr: Loading module com.google.android.gms.maps from APK /data/user/0/com.google.android.gms/app_chimera/chimera-module-root/module-71c764a6f3cb92bdc5525a965b589e7c5ed304f3/MapsModule.apk
   
12-09 19:31:55.154 17172-17341/my.app D/ChimeraModuleLdr: Loading module APK /data/user/0/com.google.android.gms/app_chimera/chimera-module-root/module-71c764a6f3cb92bdc5525a965b589e7c5ed304f3/MapsModule.apk
    12-09 19:31:55.262 17172-17341/my.app D/ChimeraFileApk: Primary ABI of requesting process is armeabi-v7a
    12-09 19:31:55.271 17172-17341/my.app D/ChimeraFileApk: Classloading successful. Optimized code found.
    12-09 19:31:55.316 17172-17341/my.app W/System: ClassLoader referenced unknown path: /data/user/0/com.google.android.gms/app_chimera/chimera-module-root/module-71c764a6f3cb92bdc5525a965b589e7c5ed304f3/native-libs/armeabi-v7a
   
12-09 19:31:55.317 17172-17341/my.app W/System: ClassLoader referenced unknown path: /data/user/0/com.google.android.gms/app_chimera/chimera-module-root/module-71c764a6f3cb92bdc5525a965b589e7c5ed304f3/native-libs/armeabi
    12-09 19:31:55.618 17172-17341/my.app I/Google Maps Android API: Google Play services client version: 7571000
    12-09 19:31:55.630 17172-17341/my.app I/Google Maps Android API: Google Play services package version: 8489438
    12-09 19:31:55.969 17172-17341/my.app I/e: Token loaded from file. Expires in: 423267993 ms.
    12-09 19:31:55.969 17172-17341/my.app I/e: Scheduling next attempt in 422967 seconds.
    12-09 19:31:56.338 17172-17341/my.app D/XXX: ... done

As we can see, it realy takes a lot of time...

вторник, декабря 01, 2015

Установить английский язык в Photoshop CC 2015 / How to switch to English in Photoshop 2015

1. Перейти в / go to
C:\Program Files\Adobe\Adobe Photoshop CC 2015

2. Перейти в / go to
Locales\ru_RU\Support Files

3. Переименовать  / rename
tw10428.dat -> tw10428.dat_

4. Перезапустить / restart

четверг, мая 07, 2015

Custom isResumed method crashes application at start with java.lang.LinkageError exception

I have found an interesting case producing the java.lang.LinkageError exception.

In my base activity there is an isResumed field, I set it to true in onResume - and to false in onPause.
It's protected field.

Today I decided to add a method to get that value from a fragment, so I have added isResumed() method

public class BaseActoinBarActivity extends ActionBarActivity {

    protected boolean isResumed;

    @Override
    protected void onResume() {
        super.onResume();
        isResumed = true;
        ...
    }

    ...


    public boolean isResumed() {
        return isResumed;
    }
    
    ...
}

public class StartActivity extends BaseActionBarActivity {
...
}

Ok, after I have started my application I have got an exception:

05-07 18:45:21.020  14466-14466/? I/art﹕ Rejecting re-init on previously-failed class java.lang.Class<ru.xxxxxx.ui.base.BaseActionBarActivity>
05-07 18:45:21.020  14466-14466/? I/art﹕ Rejecting re-init on previously-failed class java.lang.Class<ru.xxxxxx.ui.base.BaseActionBarActivity>
05-07 18:45:21.020  14466-14466/? I/art﹕ Rejecting re-init on previously-failed class java.lang.Class<ru.xxxxxx.ui.StartActivity>
05-07 18:45:21.021  14466-14466/? I/art﹕ Rejecting re-init on previously-failed class java.lang.Class<ru.xxxxxx.ui.StartActivity>
05-07 18:45:21.021  14466-14466/? D/AndroidRuntime﹕ Shutting down VM
05-07 18:45:21.274  14466-14466/? E/AndroidRuntime﹕ FATAL EXCEPTION: main
    Process: ru.xxxxxx.ui, PID: 14466
    java.lang.LinkageError: ru.simpls.mtsbank2.ui.StartActivity
            at dalvik.system.DexFile.defineClassNative(Native Method)
            at dalvik.system.DexFile.defineClass(DexFile.java:226)
            at dalvik.system.DexFile.loadClassBinaryName(DexFile.java:219)
            at dalvik.system.DexPathList.findClass(DexPathList.java:321)
            at dalvik.system.BaseDexClassLoader.findClass(BaseDexClassLoader.java:54)
            at java.lang.ClassLoader.loadClass(ClassLoader.java:511)
            at java.lang.ClassLoader.loadClass(ClassLoader.java:469)
            at ru.simpls.mtsbank2.ui.SplashScreen$1.handleMessage(SplashScreen.java:42)
            at android.os.Handler.dispatchMessage(Handler.java:102)
            at android.os.Looper.loop(Looper.java:135)
            at android.app.ActivityThread.main(ActivityThread.java:5254)
            at java.lang.reflect.Method.invoke(Native Method)
            at java.lang.reflect.Method.invoke(Method.java:372)
            at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:903)
            at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:698)

It's really funny :)

(SplashScreen is a splash screen activity, it starts StartActivity)

вторник, марта 10, 2015

Как обработать pdf в WebView / How to handle pdf in WebView

Пусть на отображаемой страничке есть ссылка, которая указывает на PDF-документ. 
Нам нужно эту страничку - отобразить. 
В данном варианте при клике по такой ссылке отображается стандартный браузер, но тут уже всё в наших руках.

Удобно использовать API от Гугла:

private static final String GOOGLE_DOC_VIEW_URL = "https://docs.google.com/viewer?embedded=true&url=";

...

WebViewClient viewClient = new WebViewClient() {
   ...
    @Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
if (url.endsWith("pdf")) {
String googleDocsLink = GOOGLE_DOC_VIEW_URL + url;
Uri uri = Uri.parse(googleDocsLink);
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
startActivity(intent);
return true;
}
return super.shouldOverrideUrlLoading(view, url);
}
}

...

webView.setWebViewClient(viewClient);