레이블이 최적화인 게시물을 표시합니다. 모든 게시물 표시
레이블이 최적화인 게시물을 표시합니다. 모든 게시물 표시

2018년 5월 18일 금요일

[안드로이드] ConstraintLayout 1.1 새로운 기능 소개

ConstraintLayout 1.1 새로운 기능 소개

Percents

1.0에서는 뷰의 크기를 퍼센트로 지정하려면 가이드라인 2개를 이용하거나, ConstraintLayout 대신 PecentLayout을 이용해야만 가능했다. 1.1에서 추가된 속성을 이용하면 뷰의 크기를 쉽게 퍼센트로 지정할 수 있다.
퍼센트를 사용하려면 다음의 사항을 따라야 한다.
  • layout_width / layout_height를 0dp(MATCH_CONSTRAINT) 로 해야한다.
  • layout_constraintWidth_percent / layout_constraintHeight_percent를 이용한다.
    • 해당 속성의 값은 0 ~ 1 사이의 값이여야 한다.
<Button
    android:layout_width="0dp"
    android:layout_height="0dp"
    app:layout_constraintWidth_percent="0.7"
    app:layout_constraintHeight_percent="0.2" />

Chains

Chain 제약은 여러개의 뷰를 묶어 어떻게 공간에 배치할지를 정하는 조건이다.
Chain으로 묶으려면, 쌍방에 대한 제약조건을 걸면 된다. 수평 / 수직 방향 모두 가능하며, 배치 방법에는 다음의 3가지가 있다.


  • speard : 기본적인 배치이다. layout_constraintVertical_weight / layout_constraintHorizontal_weight 을 이용하면 LinearLayout의 layout_weight효과를 낼 수 있다.
  • spread_inside : 공간의 가장자리에 첫번째 부와 마지막 뷰가 배치되고, 남은 사이공간에 나머지 뷰들이 배치된다.
  • packed : 묶인 모든 뷰들이 공간의 가운데에 배치된다. bias 속성과 함꼐 사용하면 위치를 조절할 수 있다.

Barriers

Barrier 라는 헬퍼 객체를 이용하여, 뷰들의 크기가 런타임으로 변할때에 적합한 제약조건을 생성할 수 있다.(객체라기보다는 가상의 그룹에 가깝다.) 해당 Barrier에 연관된 뷰들은 크기가 동적으로 변할 수 있고, 크기가 동적으로 변할 때, Barrier에 제약조건을 건 뷰들은 크기 / 위치가 Barrier에 맞게 변경된다. Barrier는 start, top, end, bottom중 하나의 위치로 지정될 수 있다. 해당 객체는 ConstraintLayout에 계층구조로 잡히지 않으며 렌더링되지 않는다.



<android.support.constraint.ConstraintLayout 
  xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:app="http://schemas.android.com/apk/res-auto"
  android:layout_width="match_parent"
  android:layout_height="match_parent">

  <TextView
    android:id="@+id/textView1"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginStart="16dp"
    android:layout_marginTop="16dp"
    android:text="@string/warehouse"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent" />

  <TextView
    android:id="@+id/textView2"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginStart="16dp"
    android:layout_marginTop="8dp"
    android:text="@string/hospital"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toBottomOf="@+id/textView1" />

  <android.support.constraint.Barrier
    android:id="@+id/barrier7"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    app:barrierDirection="end"
    app:constraint_referenced_ids="textView2,textView1" />

  <TextView
    android:id="@+id/textView3"
    android:layout_width="0dp"
    android:layout_height="wrap_content"
    android:layout_marginStart="8dp"
    android:text="@string/lorem_ipsum"
    app:layout_constraintStart_toEndOf="@+id/barrier7"
    app:layout_constraintTop_toTopOf="parent" />

</android.support.constraint.ConstraintLayout>

Group

Group이라는 헬퍼 객체를 이용해, 몇몇 뷰들을 그룹화 할 수 있다. 그룹화 함으로써 얻는 장점은, visibility 등의 동시적인 작업이 필요할때, 해당 그룹을 이용하여 작업을 수행하면 그룹안의 뷰들에게 전체적으로 적용된다. 해당 객체는 ConstraintLayout에 계층구조로 잡히지 않으며 렌더링되지 않는다.
<android.support.constraint.Group
    android:id="@+id/profile"
    app:constraint_referenced_ids="profile_name,profile_image" />
profile.setVisibility(View.VISIBLE);

Circular Constraints

기존의 대부분 레이아웃이 수평 / 수직의 배치만 지원한 것에 비해, 원형의 배치를 지원하게 되었다. 원형 배치를 구현하려면 다음을 수행한다.
  • layout_constraintCircle속성에 구심점이 될 뷰를 지정한다.
  • layout_constraintCircleRadius속성에 구심점으로부터의 거리를 지정한다.(반지름)
  • layout_constraintCircleAngle속성에 0 ~ 360 사이의 각도값을 지정한다. 12시 방향이 0도이고, 시계방향으로 각도가 늘어난다.
<android.support.design.widget.FloatingActionButton
    android:id="@+id/middle_expanded_fab"
    app:layout_constraintCircle="@+id/fab"
    app:layout_constraintCircleRadius="50dp"
    app:layout_constraintCircleAngle="315" />




ConstraintSet을 이용한 애니메이션

ConstraintSet을 이용하면 여러개의 뷰들을 한번에 애니메이셔닝 할 수 있다. ConstraintSet은 ConstraintLayout에서의 제약조건들만 가지고 있고, 해당 제약조건들을 ConstraintLayout에 지정함으로써 제약조건들을 갱신할 수 있다.
애니메이션을 수행하려면, TransitionManager.beginDelayedTransition() API를 이용한다. 이 메서드는 ConstraintSet의 모든 레이아웃의 변경사항을 애니메이션으로 수행한다.
해당 기능에 대해 설명한 비디오
https://www.youtube.com/watch?v=OHcfs6rStRo&feature=youtu.be

New Optimizations

ConstraintLayout안의 불필요한 제약조건들을 줄여서, 레이아웃 속도를 빠르게 해주는 최적화 레벨을 지정할 수 있다. layout_optimizationLevel 속성에 레벨을 지정하여 최적화 레벨을 지정한다. 최적화레벨은 다음의 5가지가 존재한다.
  • barriers : Barrier 제약조건에 관련하여 최적화
  • direct : 고정된 요소(예 : parent, Guideline)에 연결된 제약조건에 관련하여 최적화
  • standard : barriers, direct를 포함한 기본 최적화 레벨
  • dimensions : 뷰 크기에 관련된 부분을 최적화 - 해당 옵션은 현재 테스트중이며 이슈 발생의 소지가 있다.
  • chains : 고정된 크기의 뷰를 체이닝할때의 최적화 - 해당 옵션은 현재 테스트중이며 이슈 발생의ㅏ 소지가 있다.
<android.support.constraint.ConstraintLayout 
    app:layout_optimizationLevel="standard|dimensions|chains"

참고링크

  • http://dktfrmaster.blogspot.kr/2016/09/constraintlayout.html
  • https://developer.android.com/reference/androidx/constraintlayout/widget/ConstraintLayout

2016년 9월 28일 수요일

[안드로이드] 7.0(누가) 백그라운드 최적화

안드로이드에서 7.0버전을 출시하면서 백그라운드를 최적화하기 위해 다음의 2가지 사항을 변경했다.
  • 매니페스트 파일에 등록된 CONNECTIVITY_ACTION(android.net.conn.CONNECTIVITY_CHANGE) 브로드캐스트 수신을 무시한다.(앱이 백그라운드에서 이 브로드캐스트를 더이상 받지 못한다.) 단, Context.registerReceiver()메서드를 이용해 메인스레드에서 등록된 리시버는 앱이 실행중일 때, 똑같이 CONNECTIVITY_ACTION 브로드캐스트를 받을 수 있다.
  • 앱은 더이상 ACTION_NEW_PICTUREACTION_NEW_VIDEO 브로드캐스트를 보내거나 받을 수 없다. 이는 targetSdkVersion에 상관없이 모든 앱에 영향을 끼친다.
안드로이드 7.0을 대응해야 한다면, 반드시 위의 두가지 사항을 확인해서 대응해 주어야 한다. 애응하지 않으면 어떤 부작용이 있을지 알 수 없다.

CONNECTIVITY_ACTION 제한

targetSdkVersion을 24로 설정한 앱의 경우, 매니페스트에 설정한 CONNECTIVITY_ACTION 브로드캐스트를 더 이상 받지 못하게 된다. 이 상황은 백그라운드에서 CONNECTIVITY_ACTION 처리를 하고있는 앱이라면, 반드시 확인해봐야 한다.
Note : 위에서도 언급했지만, 앱이 실행중일 때에는 Context의 registerReceiver() 메서드를 이용해 CONNECTIVITY_ACTION 수신 리시버를 등록하고, 계속 브로드캐스트 받을 수 있다.

네트워크 Job 스케쥴링

안드로이드 5.1 이상

안드로이드 5.1(API 21) 버전에서 새로 추가된 JobScheduler 관련 API를 이용해 처리할 수 있다. JobInfo.builder 로 jobInfo인스턴스를 생성할 때, setRequiredNetworkType() 메서드를 이용해 네트워크 타입을 지정하고 생성한 후에 그 인스턴스를 스케쥴에 등록한다. 그러면, 스케쥴러가 해당 네트워크 타입이 되었을 때, Job을 수행한다. 다음 예제코드는 네트워크 타입이 WIFI 일때의 JobInfo를 등록하는 내용이다.
public static final int MY_BACKGROUND_JOB = 0;
...
public static void scheduleJob(Context context) {
JobScheduler js =
(JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE);
JobInfo job = new JobInfo.Builder(
MY_BACKGROUND_JOB,
new ComponentName(context, MyJobService.class))
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED)
.build();
js.schedule(job);
}
위의 예제에서 네트워크 타입이 UNMETERED(무과금 -> WIFI) 가 되면, JobInfo를 만들때 등록했던 JobService 클래스의 onStartJob()메서드로 콜백된다. (JobService 클래스 참고)

안드로이드 5.1 미만

안드로이드 5.1(API 21)버전 미만에서는 JobScheduler 클래스를 사용할 수 없으므로, 구글플레이 서비스를 이용해 구현된 GcmNetworkManager 클래스를 이용해야 한다. GcmNetworkManager는 Task 추상클래스의 하위 클래스 객체를 스케쥴링하게된다. Task 클래스 하위 인스턴스는 다음과 같이 스케쥴링한다.
     OneoffTask myTask = new OneoffTask.Builder()
.setService(MyGcmTaskService.class)
.setRequiredNetwork(NETWORK_STATE_UNMETERED)
.setTag("test-upload")
.build();
GcmNetworkManager.getInstance(this).schedule(myTask);
해당 태스크의 순서가 되면, GcmTaskService 클래스의 onRunTask() 메서드로 콜백된다. (GcmTaskService 클래스 참고) 단, GcmTaskService 클래스가 콜백을 받으려면 다음과 같이 매니페스트에 등록되어야 한다.
     <service android:name=".MyUploadService"
android:exported="true"
<!-- 다른 코드로부터 이 서비스가 실행되지 않도록 하기 위해서 추가 -->
android:permission="com.google.android.gms.permission.BIND_NETWORK_TASK_SERVICE">
<!-- 이 서비스가 콜백을 받기 위해 추가 -->
<intent-filter>
<action android:name="com.google.android.gms.gcm.ACTION_TASK_READY" />
</intent-filter>
</service>

앱이 실행중일 때, Network Connectivity 모니터링하기

앱 실행중에 브로드캐스트 리시버로 CONNECTIVITY_ACTION을 수신하는 방법 외에도, ConnectivityManager 클래스의 registerNetworkCallback()메서드를 이용해, 해당 상태에서의 콜백을 받을 수 있다. (이 메서드는 API 21에서 추가되었다.)
NetworkRequest.Builder 를 이용하여 NetworkRequest 인스턴스를 생성한 후에, 그 인스턴스를 registerNetworkCallback()메서드의 첫번째 인자로 넘기고, 두번째 인자로는 첫번째 인자에 명시된 상태로 네트워크가 변할 때, 실행될 콜백 메서드를 넘긴다. 앱이 종료될 때, unregisterNetworkCallback()메서드를 호출해 주어야 한다.

NEW_PICTURE / NEW_VIDEO 제한

안드로이드 7.0에서는 ACTION_NEW_PICTUREACTION_NEW_VIDEO 브로드캐스트를 주고받을 수 없도록 변경되었다. 이러한 현상은, 어떤 애플리케이션이 새로운 이미지나 비디오를 프로세싱할 때, 이 액션을 등록한 모든 리시버에게 브로드캐스트 되어 많은 앱들이 깨어나야만 했다.

새로운 JobInfo 메서드

안드로이드 7.0에서는 Content URI의 변화를 연결하기 위해 JobInfo API를 확장하여 다음의 메서드들을 추가하였다.
  • JobInfo.TriggerContentUri() : content URI의 변화를 감지하기 위해 요구되는 파라미터들을 캡슐화한다.
  • JobInfo.Builder.addTriggerContentUri() : TriggerContentUri 객체를 JobInfo로 전달한다. ContentObserver는 캡슐화된 content URI를 모니터링한다. 만약 Job과 관련된 TriggerContentUri가 여러개 있으면, 시스템은 한개의 URI가 변하더라도 콜백을 전달한다. 어떤 URI의 하위 컨텐트의 변화를 감지하고 싶으면, TriggerContentUri.FLAG_NOTIFY_FOR_DESCENDANTS 플래그를 추가하면 된다. 이 플래그는, ContentResolver.registerContentObserver()메서드의 매개변수인 notifyForDescendants와 대응하는 역할을 한다.
Note : TriggerContentUri() 메서드는 setPeriodic()setPersisted() 메서드와 같이 사용할 수 없다. 컨텐트의 변경을 계속 감지하려면, JobService가 콜백을 끝내기 전에 새로운 JobInfo 인스턴스를 만들어 스케쥴링한다.
다음 예제는 MEDIA_URI가 변경될떄를 감지하기 위해 스케쥴링하는 코드이다.
public static final int MY_BACKGROUND_JOB = 0;
...
public static void scheduleJob(Context context) {
JobScheduler js =
(JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE);
JobInfo.Builder builder = new JobInfo.Builder(
MY_BACKGROUND_JOB,
new ComponentName(context, MediaContentJob.class));
builder.addTriggerContentUri(
new JobInfo.TriggerContentUri(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
JobInfo.TriggerContentUri.FLAG_NOTIFY_FOR_DESCENDANTS));
js.schedule(builder.build());
}

새로운 JobParameter 메서드

안드로이드 7.0에서는 JobParameter를 확장하여, Job 콜백이 왔을 때 해당 컨텐트의 권한이나 URI같은 유용한 정보를 볼 수 있도록 확장되었다.
  • Uri[] getTriggeredContentUris() : 이 Job에서 연결된 URI를 배열로 반환한다. 만약 연결된 URI가 없다면 null을 리턴한다.
  • String[] getTriggeredContentAuthorities() : 이 Job에서 연결된 URI들의 권한ㅇ르 배열로 반환한다. 반환된 결과가 null이 아니라면, getTriggeredContentUris() 메서드를 사용하면 된다.
다음 예제는 변경된 URI의 권한과 URI를 JobService.onStartJob() 메서드에서 받아서 기록하는 코드이다.
@Override
public boolean onStartJob(JobParameters params) {
StringBuilder sb = new StringBuilder();
sb.append("Media content has changed:\n");
if (params.getTriggeredContentAuthorities() != null) {
sb.append("Authorities: ");
boolean first = true;
for (String auth :
params.getTriggeredContentAuthorities()) {
if (first) {
first = false;
} else {
sb.append(", ");
}
sb.append(auth);
}
if (params.getTriggeredContentUris() != null) {
for (Uri uri : params.getTriggeredContentUris()) {
sb.append("\n");
sb.append(uri);
}
}
} else {
sb.append("(No content)");
}
Log.i(TAG, sb.toString());
return true;
}