본문 바로가기
개발/안드로이드

[안드로이드] UUID 를 만드는 방법에 대하여

by 핸디(Handy) 2020. 7. 4.

UUID

Universally Unique IDentifier, 범용고유식별자입니다.

컴퓨터 시스템 내에서 고유한 객체를 식별하기 위해 사용하는 값으로 8 - 4 - 4 - 4 - 12 구조를 가지고 있습니다.

각 버전에 따라 UUID를 만드는 방식이 점차 변화해 왔으며 현재 버전 4가 가장 많이 쓰인다고 합니다.

버전별 UUID 생성 로직


위의 UUID의 버전별 변화에서 알 수 있듯이

과거에는 디바이스를 판별하기 위해선 IP나 MAC address를 사용했지만 IP, MAC 둘다 영원히 고정되는 값이 아니기에Unique 하다기엔 약간 부족한 값입니다. 따라서 다양한 값의 조합, 확률상으로 나타날 수 없는 크기로 만들게 되었습니다.

따라서 UUID를 사용한다면 웬만해선 고유한 값이라고 판단할 수 있습니다. 위키에 따르면 100경의 UUID가 있어야 한쌍의 UUID가 겹칠 확률이 50%라고 합니다. 한마디로 겹칠 일 없다고 생각하시면 되겠습니다.

하지만 이런 UUID에도 단점이 있습니다.

1. 생각보다 큰 16바이트(128비트)의 크기
2. 아무런 정보를 내재할 수 없음.

1번 단점의 경우 UUID를 DB의 KEY로 사용할 경우 성능이 느리질 수가 있으며, 페이지 순서와 같은 순차적인 식별자가 필요할 경우에 쓸데없이 크기에 부적합합니다.

2번 단점의 경우 16바이트라는 큰 크기에도 불구하고 랜덤한 값이 들어감으로 아무런 정보를 가지고 있지 않습니다. 

따라서 128비트 전부를 랜덤으로 조합하기보다는 특정 값들의 조합으로 표현하거나 비트를 줄이는 방식으로 활용하곤 합니다.

예를 들어 MongoDB의 ObjectID의 경우 96비트의 16진수를 사용하는데
1. 4바이트(32비트)의 타임 스탬프
2. 3바이트(24비트)의 디바이스 식별자
3. 2바이트(16비트)의 프로세스 ID
4. 임의의 값으로 시작하는 3바이트 카운터
를 조합하여 사용합니다.

실제로 저는 DB의 특정 ID를 만들때 128비트를 전부다 사용하지 않고 16비트의 16진수+날짜카운터(20200612)와 같은 값을 사용하여 생성했습니다. 

제가 했던 방식이 UUID의 표준 형식은 아니지만 마지막 1바이트( 날짜 카운터 )를 통해 쿼리를 빠르게 조회할 수 있었기 이러한 방식으로 만들어 사용했습니다. 정렬이나, 셀렉트로 가져올 때 간편하기도 했고요.


제 안드로이드 내에선 UUID를 어떻게 만들 수 있을까에 대한 얘기를 해보겠습니다.

앞에서 말했듯이 가장 범용적인 UUID 버전 4에 맞춰 생성 로직을 구현하여 만드는 방법도 있습니다만,

안드로이드에서 제공하는 UUID의 기능이 있으니 그 기능을 이용해보도록 하겠습니다.

안드로이드의 경우 기기의 ID, 시리얼 넘버, 시큐리티 ID 3가지 조합으로 UUID를 생성합니다.

그렇다면 이제 실제로 UUID를 가져오는 코드를 구현해보도록 하겠습니다.

<uses-permission android:name="android.permission.READ_PHONE_STATE" />
import android.Manifest;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.Signature;
import android.os.Build;
import android.os.Bundle;
import android.telephony.TelephonyManager;
import android.util.Base64;
import android.util.Log;


import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.UUID;

public class MainActivity extends AppCompatActivity {

    private static final int REQUEST_PHONE_STATE = 1;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);

        //런타임 권한 체크
        checkForPhoneStatePermission();
        
        //아이디 받아오기
        GetDevicesUUID(MainActivity.this);
        
 
    }

    private String GetDevicesUUID(Context mContext) {
        final TelephonyManager tm = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
        final String tmDevice, tmSerial, androidId;
        if (ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_PHONE_STATE) != PackageManager.PERMISSION_GRANTED) {
            return "Fail to Get UUID";
        }
        tmDevice = "" + tm.getDeviceId();
        tmSerial = "" + tm.getSimSerialNumber();
        androidId = "" + android.provider.Settings.Secure.getString(getContentResolver(), android.provider.Settings.Secure.ANDROID_ID);
        UUID deviceUuid = new UUID(androidId.hashCode(), ((long)tmDevice.hashCode() << 32) | tmSerial.hashCode());
        String deviceId = deviceUuid.toString();
        Log.d("UUID_generater", deviceId);
        return deviceId;
    }

    private void checkForPhoneStatePermission(){

        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {

            if (ContextCompat.checkSelfPermission(MainActivity.this,
                    Manifest.permission.READ_PHONE_STATE)
                    != PackageManager.PERMISSION_GRANTED) {

                // Should we show an explanation?
                if (ActivityCompat.shouldShowRequestPermissionRationale(MainActivity.this,
                        Manifest.permission.READ_PHONE_STATE)) {

                    // Show an explanation to the user *asynchronously* -- don't block
                    // this thread waiting for the user's response! After the user
                    // sees the explanation, try again to request the permission.

                    showPermissionMessage();

                } else {

                    // No explanation needed, we can request the permission.
                    ActivityCompat.requestPermissions(MainActivity.this,
                            new String[]{Manifest.permission.READ_PHONE_STATE},
                            REQUEST_PHONE_STATE);
                }
            }
        }else{
            //... Permission has already been granted, obtain the UUID
            Log.d("UUID_generater", "deviceId");
            GetDevicesUUID(MainActivity.this);
        }
}


    private void showPermissionMessage(){
        new AlertDialog.Builder(this)
                .setTitle("Read phone state")
                .setMessage("This app requires the permission to read phone state to continue")
                .setPositiveButton("Okay", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        ActivityCompat.requestPermissions(MainActivity.this,
                                new String[]{Manifest.permission.READ_PHONE_STATE},
                                REQUEST_PHONE_STATE);
                    }
                }).create().show();
    }


}

이제 앱을 실행시키고 권한을 얻은 다음에 콘솔창을 보면.

 

UUID 표준 형식 8-4-4-4-12에 따라 알맞게 가져옴을 할 수 있습니다. 또한 같은 디바이스에 같은 값을 주는지 확인하기 위해 한번 더 가져왔더니 그것 또한 같은 값임을 확인했습니다.

 

별개로 다른 예시 코드들 보다 구현이 길어졌는데,
원래 23 버전 아래에서는 manifest에만 권한을 넣어주면 바로 UUID를 가져오는 게 가능했지만 최신(28)부터는 런타임으로 권한을 받아와야 하는 것으로 변경되었습니다. 따라서 귀찮지만 권한 체크 다이얼로그를 띄워주고 권한을 받고 나서 가져오는 방식으로 구현을 해보았습니다.

 

댓글