android library를 만들고 배포할려면 maven repository가 필요한데, 이게 준비가 되어 있지 않다면 aar파일을 직접 전달해야 하는 번거러움이 있습니다. 그런데 github packages에 apache maven을 지원해주기에 간단하게 aar library를 배포하여 사용할 수 있습니다.

github packages에 대한 설명은. 해당 링크로..

github - personal access tokne 생성

github의 자신의 계정에서 Settings-> Developr settings -> Personal access tokens 으로 가서 access token을 생성 합니다.
write packages, read packages 를 체크 합니다.

properties

project의 root 위칭에 github.properties 를 만들고 그 안에
github에서의 username과 access token생성한 것을 적용해 줍니다.
그리고 해당 파일은 github에 올라가지 않도록 .gitignore 에 추가해 주는 것이 좋습니다. 보안상...

github_username={username}
github_access_token={access_token}

Publish script

library module에 있는 위치에 publish.gradle 를 만듭니다.
그리고 그 안의 내용에는

apply plugin: 'maven-publish'

def githubProperties = new Properties()
githubProperties.load(new FileInputStream(rootProject.file("github.properties")))

def LIB_GROUP_ID = {library_group_id}
def LIB_ARTIFACT_ID = {artifact_id}
def LIB_VERSION = {version_name}

task sourceJar(type: Jar) {
    from android.sourceSets.main.java.srcDirs
    classifier "sources"
}

publishing {
    repositories {
        maven {
            name = 'GithubPackages'
            url = uri("https://maven.pkg.github.com/{user_name}/{project_name}")
            credentials {
                username = githubProperties['github_username'] ?: System.getenv("github_username")
                password = githubProperties['github_access_token'] ?: System.getenv("github_access_token")
            }
        }
        maven {
            name = 'CustomMavenRepo'
            url = "file://${buildDir}/repo"
        }
    }
    publications {
        deploy(MavenPublication) {
            groupId LIB_GROUP_ID
            artifactId LIB_ARTIFACT_ID
            version LIB_VERSION
            artifact("$buildDir/outputs/aar/${aar_library_name}.aar")
            artifact(sourceJar)

            pom.withXml {
                def dependenciesNode = asNode().appendNode('dependencies')

                //Iterate over the compile dependencies (we don't want the test ones), adding a <dependency> node for each
                configurations.api.allDependencies.each {
                    def dependencyNode = dependenciesNode.appendNode('dependency')
                    dependencyNode.appendNode('groupId', it.group)
                    dependencyNode.appendNode('artifactId', it.name)
                    dependencyNode.appendNode('version', it.version)
                }
            }
        }
    }
}

그리고 나서, library project에 있는 build.gradle 에 아래와 같이 추가해 줍니다.

apply from: file('publish.gradle')

이와 같이 추가하고 나면

그림과 같이 gradle에 publishing 에 대한 부분이 생깁니다.

library를 build후 publishDeployPublicationToCustomMavenRepoRepository 하고 나면, local repo에 배포가 되는 부분을 확인할 수 있습니다.

실질적으로 배포를 할려면 publishDeployPublicationToGithubPackagesRepository 로 하면 github의 지정된 username/project의github packages에 배포가 되게 됩니다.

library 사용

root에 있는 build.gradle 에 다음과 같이 repositories에 추가해 줍니다.

def githubProperties = new Properties()
githubProperties.load(new FileInputStream(rootProject.file("github.properties")))

repositories {
        google()
        jcenter()

        mavenLocal()
        maven {
            name = {maven_repository_name}
            url = uri("https://maven.pkg.github.com/{user_name}/{project_name}")
            credentials {
                username = githubProperties['github_username'] ?: System.getenv("github_username")
                password = githubProperties['github_access_token'] ?: System.getenv("github_access_token")
            }
        }
    }

그리고 application 에 있는 build.gradle 에 maven repository에 있는 library 사용할 때 처럼.

implementation '{library_group_id}:{artifact_id}:{version}'

으로 해서 사용하면 됩니다.

maven repository를 만들고 관리하는 것 또한 비용인데, 이걸 github packages를 이용한다면 간단히 해결되는 것 같습니다.

하나의 RecylerView에서 다양한 viewholder화면을 보여줄려면, 이전에는 하나의  adapter안에서 type들을 지정하고, 해당 position에 대해서 type에 맞는 viewholder들을 가져와서 보여줬습니다.

하지만, 여러 type들이 존재하거나 할 경우는 adapter안에는 다양한 type과 viewholder내용들이 존재하게 됩니다.

header,. 그리고 item들이 순차적으로 있다고 해도, adapter에 대한 position의 계산도 틀려집니다.

그래서 이번에 concatadapter를 볼려고 합니다.

developer.android.com/reference/androidx/recyclerview/widget/ConcatAdapter

 

ConcatAdapter  |  Android 개발자  |  Android Developers

ConcatAdapter public final class ConcatAdapter extends Adapter An RecyclerView.Adapter implementation that presents the contents of multiple adapters in sequence. MyAdapter adapter1 = ...; AnotherAdapter adapter2 = ...; ConcatAdapter concatenated = new Con

developer.android.com

다양한 adapter들을 결합에서 하나의 recyclerview에서 내용을 보여줄 수 있습니다.

header, item, footer 가 존재한다면,

headeradapter, itemadapter, footeradapter를 만들고, 그걸 concatadapter에 결합해서 사용하면 됩니다.

그리고 notify할 경우도, 각 adapter에서 따로 진행할 수도 있습니다.

간단하게 만든 sample은.

github.com/drcarter/ConcatAdapterTest

 

drcarter/ConcatAdapterTest

Contribute to drcarter/ConcatAdapterTest development by creating an account on GitHub.

github.com

으로 간단히 만들어 봤습니다.

 

Assert   : 9777A9
Debug    : 6A98B9
Error    : FF6B68
Info     : 6A855A
Verbose  : BBBBBB
Warning  : BC7739

이정도 색상으로 변경해서 사용하면,,.

프로젝트의 시간이 길어질 수록 사용하지 않는 resource가 많아지는 문제가 있습니다.

리펙토링 하면서 이전 리소스에 대해서 지울 수도 있지만, 명시적으로 지워준다면 id를 만들지 않기 때문에 더 좋을 수도 있죠.

build.gradle 에서

android {
    buildTypes {
        release {
            shrinkResources true
        }
    }
}

으로 빌드시에 resource정리를 하는 방법도 있지만, 명시적으로 지우는 것을 해볼까 합니다.

android studio 에서 Refactor -> Remove Unused Resources... 라는 메뉴가 있습니다.

해당 기능을 통해서 layout, string, drawable, color, dimen 등등 사용하지 않는 resource를 지울 수 있습니다.

단, 문제가 하나 있는데,

dynamic resource로 접근하는 부분에 대해서는 걸러주지 않습니다.

그 부분을 해결하기 위해서는

resource 유지를 위해서 keep 처리를 해야 하는데, tools:keep 을 적용하면 됩니다.

예를 들어서, resource.getIdentifiericon_position_의 prefix로 되는 resource들을 다 유지하고 싶다면,

<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools" 
    tools:keep="@drawable/icon_position_*">
</resources>

으로 xml resouce파일을 만들어서 관리하면 됩니다. 이 방법은 shrinkResources 로 dynamic resource에 접근하는 것들에 대한 유지 방법과 같습니다.

Firebase Crashlytics에서 custom UnCaughtExceptionHandler를 적용하기 위해선 초기화 하는 방법을 변경해 줘야 합니다.

Fabric에서 하는 방법은 별다른 방법이 없어도 잘 동작했지만, fabric에서 firebase crashlytics로 넘어가면서 초기화 순서가 중요해 졌습니다.

firebase의 초기화는 contentprovider를 통해서 초기화 됩니다.

contentprovider의 속성중 순서를 정할 수 있는 부분이, android:initOrder입니다. 이에 대한 설명은

동일한 프로세스에서 호스팅하는 다른 콘텐츠 제공자에 상대적으로 콘텐츠 제공자를 인스턴스화해야 하는 순서입니다. 콘텐츠 제공자 사이에 종속성이 있는 경우 제공자별로 이 속성을 설정하면 종속성에서 요구하는 순서대로 생성됩니다. 값은 단순 정수이며 숫자가 높을수록 먼저 초기화됩니다.

으로 되어 있습니다.
custom exception handler가 firebase 보다 먼저 초기화 되어야 하는데, 그렇지 않아서 정상적으로 동작하지 않는 부분입니다.

그럼 해결방법은.
custom exception handler를 초기화 하는 conentprovider를 등록하고 android:initOrder 의 값을 firebase보다 크게 지정하면 됩니다. firebase 의 initOrder값은 100으로 되어 있습니다.

override fun onCreate(): Boolean {
    val myUncaughtExceptionHandler = UncaughtExceptionHandler(
        Thread.getDefaultUncaughtExceptionHandler()
    )
    Thread.setDefaultUncaughtExceptionHandler(myUncaughtExceptionHandler)

    return true
}
<provider
    android:name=".UncaughtExceptionHandlerContentProvider"
    android:authorities="${applicationId}"
    android:exported="false"
    android:grantUriPermissions="false"
    android:initOrder="101" />

이와 같이 manifest 에 등록하고 사용하면 됩니다.

val items = listOf(1, 2, 3, 4, 5)
// actual sum of 15

이와 같은 collection에서 전체 합을 구해보기.

 

1. foreach

@Test
fun sumTest_1() {
    var total = 0
    items.forEach {
        total += it
    }
    Assert.assertEquals(total, actual)
}
/**
 * Performs the given [action] on each element.
 */
@kotlin.internal.HidesMembers
public inline fun <T> Iterable<T>.forEach(action: (T) -> Unit): Unit {
    for (element in this) action(element)
}

 

가장 무난하게 많이??이들 사용했을것 같은 부분.

 

 

2. sum()

@Test
fun sumTest_2() {
    val total = items.sum()
    Assert.assertEquals(total, actual)
}
/**
 * Returns the sum of all elements in the collection.
 */
@kotlin.jvm.JvmName("sumOfInt")
public fun Iterable<Int>.sum(): Int {
    var sum: Int = 0
    for (element in this) {
        sum += element
    }
    return sum
}

각 element들의 합계를 바로 구한다. foreach의 내용이 바로 sum의 내용과 같음.

 

 

3. reduce

@Test
fun sumTest_3() {
    val total = items.reduce { acc, i -> acc + i }
    Assert.assertEquals(total, actual)
}
/**
 * Accumulates value starting with the first element and applying [operation] from left to right to current accumulator value and each element.
 * 
 * @sample samples.collections.Collections.Aggregates.reduce
 */
public inline fun <S, T : S> Iterable<T>.reduce(operation: (acc: S, T) -> S): S {
    val iterator = this.iterator()
    if (!iterator.hasNext()) throw UnsupportedOperationException("Empty collection can't be reduced.")
    var accumulator: S = iterator.next()
    while (iterator.hasNext()) {
        accumulator = operation(accumulator, iterator.next())
    }
    return accumulator
}

첫번째 element가 기본값으로 operation의 block내용을 가져와서 합계를 구함. 

 

 

4. fold

@Test
fun sumTest_4() {
    val total = items.fold(0) { acc, i ->
        acc + i
    }
    Assert.assertEquals(total, actual)
}
/**
 * Accumulates value starting with [initial] value and applying [operation] from left to right to current accumulator value and each element.
 */
public inline fun <T, R> Iterable<T>.fold(initial: R, operation: (acc: R, T) -> R): R {
    var accumulator = initial
    for (element in this) accumulator = operation(accumulator, element)
    return accumulator
}

reduce와의 차이는 초기값을 지정할 수 있음.

보통 코드의 실행시간을 측정하기 위해서는.

val start = System.currentTimeMillis()
val result = inputPlus(1, 2)
val measuredTime = System.currentTimeMillis() - start
println("result => $result || measured time ==>$measuredTime")

 

이런식으로 실행시간을 측정하고 싶은 메소드의 시작과, 끝! 그에 대한 시간을 넣고 구할것 같습니다.

 

java로 Android 개발했을 대에는 Jake Wharton의 Hugo를 사용해서 실행시간을 logcat에 출력해서 사용하고 했지만,

https://github.com/JakeWharton/hugo

 

JakeWharton/hugo

Annotation-triggered method call logging for your debug builds. - JakeWharton/hugo

github.com

kotlin으로 넘어오면서, hugo를 사용하지 못하다 보니...

 

그래도 kotlin에는 사용하기 편한게 있습니다.

/**
 * Executes the given function [block] and returns an instance of [TimedValue] class, containing both
 * the result of the function execution and the duration of elapsed time interval.
 *
 * The elapsed time is measured with [TimeSource.Monotonic].
 */
@SinceKotlin("1.3")
@ExperimentalTime
public inline fun <T> measureTimedValue(block: () -> T): TimedValue<T> {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }

    return TimeSource.Monotonic.measureTimedValue(block)
}

/**
 * Executes the given [block] and returns an instance of [TimedValue] class, containing both
 * the result of function execution and the duration of elapsed time interval.
 *
 * The elapsed time is measured with the specified `this` [TimeSource] instance.
 */
@SinceKotlin("1.3")
@ExperimentalTime
public inline fun <T> TimeSource.measureTimedValue(block: () -> T): TimedValue<T> {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }

    val mark = markNow()
    val result = block()
    return TimedValue(result, mark.elapsedNow())
}

https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.time/measure-timed-value.html

 

measureTimedValue - Kotlin Programming Language

 

kotlinlang.org

measureTimeValue를 이용하면 됩니다.

 kotlin 1.3 에 생겼고, 

 

val measuredTime = measureTimedValue {
            inputPlus(2, 3)
        }

println("result => ${measuredTime.value} || measured time ==>${measuredTime.duration}")

와 같이 사용하면, 간단히 사용할 수 있고, 해당 method의 결과도 확인할 수 있습니다.

Collection과 Sequence는 둘다 lambda의 확장함수( map, filter, find... )를 사용하여 원하는 결과를 찾아나갈 수 있습니다.

그런데 Collection의 확장함수를 사용하면, inline function을 통해서 매번 결과가 새롭게 만들어 집니다.

public inline fun <T, R> Iterable<T>.map(transform: (T) -> R): List<R> {
    return mapTo(ArrayList<R>(collectionSizeOrDefault(10)), transform)
}

하지만 Sequence는 inline function이 아닌 chain call 을 통해서 결과를 이어 나갈 수 있습니다.

public fun <T, R> Sequence<T>.map(transform: (T) -> R): Sequence<R> {
    return TransformingSequence(this, transform)
}

 

같은 결과이지만, 중간 과정에서 결과를 보면,

val items = mutableListOf(1, 2, 3, 4, 5)
val result = items.map {
    it * it
}.filter {
    it % 2 == 0
}

map에서 새로운 1,4,9,16,25 가 만들어진 뒤, filter에서 새로운 4, 16의 결과만 만들지게 됩니다.

 

하지만 sequence는,

val items = mutableListOf(1, 2, 3, 4, 5)
val result = items.asSequence().map {
    it * it
}.filter {
    it % 2 == 0
}.toList()

1,1,2,4,3,9,4,16,5,25 의 순서로, 새로운 결과를 만들어지는게 아니라, 하나씩 확인을 해갑니다.

 

 

Collection에서 새로운 결과를 계속 만들다 보면 memory사용도 많아지게 되지만, Sequence로 하게 되면, 메모리 문제는 Collection보다는 덜 하게 됩니다.

+ Recent posts