2015. 8. 13.

Android MediaPlayer

Android의 동영상 재생 or 음성재생이 가능한 MediaPlayer API를 사용하였다.

이번 소스는 음성만 재생하는 간단한 소스이다.

MediaPlayer class는 따로 두었다.
아래는 MediaRecord소스이다.


import android.media.MediaPlayer;
import android.os.Environment;
import android.util.Log;

import java.io.File;

/**
 * Created by sona
 */
public class MediaPlayer {

    private final String TAG = "MediaPlayer";
    private final File RECORDED_FILE = Environment.getExternalStorageDirectory();

    private MediaPlayer player;
    private String mFileName;

    public MediaPlayer() {
        mFileName = RECORDED_FILE.getAbsolutePath()+"/audio_milo.mp3";
    }

    /**
     *  Audio Play
     */
    public void play(){
        if (player != null) {
            player.stop();
            player.release();
            player = null;
        }
        Log.d(TAG, "PLAY AUDIO");
        try {
            player = new MediaPlayer();
            player.setDataSource(mFileName);

            player.prepare();
            player.start();
        } catch (Exception e) {
            Log.e(TAG, "Audio play failed.", e);
        }
    }
    /**
     * Audio Play Pause
     */
    public void playPause(){
        if(player.isPlaying()){
            player.pause();
        } else {
            player.start();
        }
    }
    /**
     *  Audio Play Stop
     */
    public void playStop(){
        if (player == null)
            return;
        Log.d(TAG,"PLAY STOP");

        player.stop();
        player.release();
        player = null;
    }

    /**
     *  Mediaplayer Release
     */
    public void release(){
        if (player != null) {
            player.release();
            player = null;
        }
    }

    /**
     * MediaPlayer Seek duration
     * @param frame : video player frame (Refer : MLPlayer.onPlaying)
     */
    public void seekTo(int frame){
        if(player != null){
            player.seekTo(frame * 1000);
        }

    }

아래는 activity에서 test로 사용하는 코드이다.



import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.SeekBar;


public class MediaRecordActivity extends Activity implements View.OnClickListener{

    private Button          audioRecordPlay;
    private Button          audioRecordPlayStop;
    private Button          audioSeekButton;

    private MediaPlayer   mediaPlayer;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.media_activity);
        initialize();
    }

    @Override
    protected void onPause() {
        super.onPause();
        mediaPlayer.release();
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.record_button:
                mediaPlayer.start();
                break;
            case R.id.record_stop_button:
                mediaPlayer.stop();
                break;
            case R.id.play_start_button:
                mediaPlayer.play();
                break;
            case R.id.play_stop_button:
                mediaPlayer.playPause();
                break;
            case R.id.seek_button:
                // seekTo unit = millisecond
                mediaPlayer.seekTo(3000);
                break;
        }
    }

    private void initialize(){

        audioRecordPlay          = (Button) findViewById(R.id.play_start_button);
        audioRecordPlayStop   = (Button) findViewById(R.id.play_stop_button);
        audioSeekButton          = (Button) findViewById(R.id.seek_button);

        audioRecordPlay.setOnClickListener(this);
        audioRecordPlayStop.setOnClickListener(this);
        audioSeekButton.setOnClickListener(this);

        mediaPlayer = new MediaPlayer();
    }
}

Android MediaRecorder

Android의 동영상 녹화 or 음성녹음이 가능한 MediaRecorder API를 사용하였다.

이번 소스는 음성만 녹음하는 간단한 소스이다.

MediaRecorder class는 따로 두었다.
아래는 MediaRecord소스이다.



import android.media.MediaRecorder;
import android.os.Environment;
import android.util.Log;

import java.io.File;

/**
 * Created by sona
 */
public class MediaRecord {

    private final String TAG         = "MediaRecord";
    private final File RECORDED_FILE = Environment.getExternalStorageDirectory();

    private MediaRecorder recorder;

    private String mFileName;


    public MLMediaRecord(){
        // test로 mp3 확장자로 sdcard에 저장해줄 것이다.
        mFileName = RECORDED_FILE.getAbsolutePath()+"/test.mp3";
    }

    /**
     *  Audio Recording Start
     */
    public void start(){
        if (recorder != null) {
            recorder.stop();
            recorder.release();
            recorder = null;
        }
        recorder = new MediaRecorder();
        // Audio Setting 메소드를 콜한다.
        setAudio();
        try {
            Log.d(TAG,"AUDIO RECORD START");
            recorder.prepare();
            recorder.start();
        } catch (Exception e) {
            Log.e(TAG, "Exception : ", e);
        }
    }

    /**
     * Audio Recording Stop
     */
    public void stop(){
        if (recorder == null)
            return;
        recorder.stop();
        recorder.release();
        recorder = null;

        Log.d(TAG, "RECORD STOP");
    }

    /**
     * Audio Recording Release
     */
    public void release(){
        if (recorder != null) {
            recorder.release();
            recorder = null;
        }
    }

    /**
     * Set Audio Parameters (source,output format,encoder,ouputfile name)
     */
    // setting의 순서가 바뀌면 작동하지않는다.
    private void setAudio() {
        recorder.setAudioSource(MediaRecorder.AudioSource.MIC);
        recorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
        recorder.setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT);
        recorder.setOutputFile(mFileName);
    }

}

아래는 activity에서 test로 사용하는 코드이다.

public class MediaRecordActivity extends Activity implements View.OnClickListener{

    private Button          audioRecordStart;
    private Button          audioRecordStop;

    private MediaRecord   mediaRecord;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.media_activity);
        initialize();
    }

    @Override
    protected void onPause() {
        super.onPause();
        mediaRecord.release();
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.record_button:
                mediaRecord.start();
                break;
            case R.id.record_stop_button:
                mediaRecord.stop();
                break;
        }
    }

    private void initialize(){
        audioRecordStart         = (Button) findViewById(R.id.record_button);
        audioRecordStop          = (Button) findViewById(R.id.record_stop_button);
       
        audioRecordStart.setOnClickListener(this);
        audioRecordStop.setOnClickListener(this);
       
        mediaRecord = new MediaRecord();
    }

}

2015. 8. 1.

Improving Layout Performance (#2. Re-using Layouts with include, merge)

좀 더 나은 레이아웃의 성능을 위한
#2. Re-using Layouts with include tag, merge tag

참고문서는 아래 공식문서를 참고하였다.
http://developer.android.com/intl/ja/training/improving-layouts/reusing-layouts.html



Although Android offers a variety of widgets to provide small and re-usable interactive elements, you might also need to re-use larger components that require a special layout. To efficiently re-use complete layouts, you can use the <include/> and <merge/> tags to embed another layout inside the current layout.


안드로이는 작고 재사용가능한 상호작용을하는는 다양한 위젯들을 제공한다.
하지만 특별한 layout이나 큰 component를 재사용하는 것이 필요할지도 모른다.
효율적인 완성된 layout을 재사용하려면, <include/> tag 와 <merge/> tag 를 현재 layout안에 다른 layout을 끼워넣어서서 사용할 수 있다.


위 내용과 같이 include와 merge는 재사용과 좀더 간결한 xml code, 
분할되어 깔끔한 구조를 위해 제공되는 tag이다.


include tag의 사용법을 알아보자.
아래 code는 우리가 재사용을 하고자하는 xml code이다.
titlebar.xml 이라고 name을 지어보자.

The root View should be exactly how you'd like it to appear in each layout to which you add this layout.
root view (위 code에서는 FrameLayout)는  레이아웃을 나타내고 싶어하는 각 레이아웃에 정확하게 추가시켜야한다.


재사용을 하고자하는 레이아웃안에 
<include layout="@layout/titlebar"/>
이런식으로 layout name을 써주면 된다.




You can also override all the layout parameters (any android:layout_* attributes) of the included layout's root view by specifying them in the <include/> tag. 
또한 include된 layout의 root view 의 layout parameters를 override 할 수 있다.
(android:layout_* attributes) 


이번엔 merge tag에 대하여 알아보자.
merge tag는 사실 view hierarchy의 중복 viewgroup을 제거하는데에 도움이된다.


사용은 <merge xmlns:android="http://schemas.android.com/apk/res/android"></merge>
이렇게 사용하되 merge tag는 root view로만 사용이 가능하다.


내가 만약 위의 코드처럼 viewgroup없이 button두개만있는 
레이아웃을  여러곳에서 재사용하고싶을 때
include tag를 사용하게되면 viewgroup으로 꼭 감싸야한다. 

viewgroup은 굳이 필요없고 위젯만 필요한데 include를 사용하게되면
 괜한 view 계층이 생겨버리는 것이다. 

그럴때 그 불필요한 view group을 merge tag로 대체하면 끝!













Improving Layout Performance (#1. Optimizing Layout Hierarchies)

좀 더 나은 레이아웃의 성능을 위한
#1. layout hierarchies

참고문서는 아래 공식문서를 참고하였다.






Use Hierarchy View

1. AndroidStudio menu/ Tools / Android Device Monitor
2. add Menu
3. select “Hierarchy View”

위 순서로 hierarchy view 를 open한다.

android studio 에서 제공해주는 hierarchy view tool을 사용하게되면 아래의 장점들이 있다.
  • Layout Hierarchy List
  • Layout ID
  • UI Structure
  • The timing for rendering(measure,layout,draw) 





밑 그림과 같은 Layout을 LinearLayout으로 구성하게되면


 아래와 같이 layout_weight를 사용하여 코드를 작성할 수 있다.




위 코드에 대한 아래 hiearachy 를 살펴보면
두개의 계층으로 이루어진 것을 볼 수 있다.

이 코드의 rendering time은

Measure: 0.977ms
Layout: 0.167ms
Draw: 2.717ms



공식문서에서는 


LinearLayout that use the layout_weight parameter can be especially expensive as each child needs to be measured twice.

특히 LinearLayout 의 layout_weight parameter를 사용하는 경우 각 자식은 두번의 Measure pass가 필요로하기때문에 더욱 많은 비용이 소모된다

라고 말하고 있다. 




Measure pass는 무엇일까?
저번 포스팅때 "View가 그려지는 순서" 에 대해서 이야기를 해보았는데
그 순서를 안다면 , 그리고 layout_weight의 특성을 잘 알고 있다면 이해하기가 쉽다.

일단 layout_weight는 단순히 비율을 나누어 공간을 차지하는 것이 아니다.
내 부모의 view가 그려지고 나서 남은 공간이 얼마만큼인지, 
다른 View들이 그려지고 나서 다시한번 남은공간도 계산하고 그리고나서 자기자신을 그린다.

View가 그려지는 순서를 보자.
1. view의 수치를 측정해서 저장한다.(쉽게 말하자면 width, height 값을 측정하여 저장)
2. layout의 left,right,top,bottom 즉,그려질 위치를 계산.

그리고

이렇게 자신들의 공간을 먼저 다 계산한 후에 남는 공간에 weight에 맞게 가져가려고 또 계산하게된다.







RelativeLayout을 사용하여 code를 짠다면
계층도 하나를 줄일 수 있을뿐더러 layout_weight을 사용하지않게되어
중첩된 계산을 하지않아도된다.


아래는  RelativeLayout으로 짠 hierarchy 이다.
rendering time은

Measure: 0.598ms
Layout: 0.110ms
Draw: 2.146ms




하나의 계층으로 단순화 되었으며 
속도의 차이는 거의 없지만 
복잡한 레이아웃 코딩을 했을 때는 이야기가 달라진다.


LinearLayout의 무자비한 사용과 layout_weight의 남발은 자제하도록 하자.
특히 ListView / GridView에서는 더더욱.



How Android Draws Views (android 의 view는 어떻게 그려지는 것일까?)

Android view는 대체 어떠한 순서로 그려지는 것일까?

이 순서를 알고 layout을 짜다보면 보다 더 나은 xml의 성능을 기대할 수 있으며,
효율적인 code를 짤 수도있다.


공식문서 http://developer.android.com/intl/ja/guide/topics/ui/how-android-draws.html 에서는


When an Activity receives focus, it will be requested to draw its layout. The Android framework will handle the procedure for drawing, but the Activity must provide the root node of its layout hierarchy.
Drawing begins with the root node of the layout. It is requested to measure and draw the layout tree. Drawing is handled by walking the tree and rendering each View that intersects the invalid region. In turn, eachViewGroup is responsible for requesting each of its children to be drawn (with the draw() method) and eachView is responsible for drawing itself. Because the tree is traversed in-order, this means that parents will be drawn before (i.e., behind) their children, with siblings drawn in the order they appear in the tree.


Activity가 focus를 받았을 때에 , layout을 그리라는 요청이 된다.
요청을 받으면 Android framework는 그려지는 것을 위한 절차를 다룰 것이고,
Layout hierarchy의 root node를 제공해야만한다.

Drawing은 layout의 root node와 함께 시작되며 layout tree를 측정하고 그려서 요청을 받는다.

순차적으로 각 ViewGroup은 각각의 child에 draw method에 그릴 것을 요청하며,
각 View는 자기자신이 그려짐에 책임을 지게된다(즉 , 자기자신을 그린다.)

Drawing은 layout tree를 순차적으로 점검을 하고 유효하지않은 위치를 교차하는 각각의 view를 rendering함으로써 조절이 된다.

부모는 childeren이 그려지기 이전에 그려지며 , 부모 &형제 순으로 tree에 나타난다.
즉 , 부모가 먼저 그려지고 childeren이 그려진다.



Drawing the layout is a two pass process: a measure pass and a layout pass. The measuring pass is implemented inmeasure(int, int) and is a top-down traversal of the Viewtree. Each View pushes dimension specifications down the tree during the recursion. At the end of the measure pass, every Viewhas stored its measurements. The second pass happens inlayout(int, int, int, int) and is also top-down. During this pass each parent is responsible for positioning all of its children using the sizes computed in the measure pass.

layout의 draw는 두가지 pass 의 과정을 거치게된다. 하나는 measure pass , 다른 하나는 layout pass 이다.

Measure Pass :
 the measuring pass 는 measure(int, int) method 에서 시행되며 view tree의  top-down 방식으로 전개되어진다. 각 view 는 반복되는 동안에  공간의 크기를 설계하여 전달한다.
measure pass 마지막에는 모든 view의 measurements를 저장한다.

Layout Pass :
 the layout pass layout(int, int, int, int) 에서 발생되며 이 또한 view tree의 top-down 방식으로 전개된다. 이 pass가 진행되는 동안 각 parent는 measure pass가 계산한 사이즈를 이용하여 자신의 모든 childeren의  positioning해야한다.







두가지 pass를 통하여 view를 그리게 된다는 것은 즉 layout이 어떻게 어느 size로 배치될 것인지를 계산한다는 것이다.

다음 포스팅에는
만약 layout의 계층이 깊어지고 많은 중첩이 이루어지거나 weight 와 같은 속성으로 layout을 배치하였을 경우에 나타나는 performance issue들에 대한 방안에 대해 이야기를 해보려고한다.