반응형

오늘은 삽질을 하다가 찾아낸 방법을 공유하려고 씁니다

 

진짜 ㅋㅋㅋ 내용들때문에 진짜 삽질을 어마어마하게 했네요 ㅋㅋㅋㅋ 

(인터넷도 잘 서핑을 해야됨 진짜... ㅋㅋ )

 

 

자 간단하게 먼저 설명 시작!!

 

일단 안드로이드 API29 즉 안드로이드 10버전 부터는 저장소 접근 방식이 변경되어 이전 방식처럼 바로 접근은 불가함

 

 

파일 Read/Write

Android 9 이하 (API 28 이하) Permission 획득 후 접근 가능
- WRITE_EXTERNAL_STORAGE
- READ_EXTERNAL_STORAGE
Android 10 이상 (API 29 이상) Scoped storage 정책 적용

Android 10 에서는 예외사항으로 속성값 적용 시 9버전처럼 사용가능(Target Build SDK가 29일 경우, 30일 경우는 해당 속성값 무시)
-> <application
      ....
      // 아래부분 추가
      android:requestLegacyExternalStorage="true"
    >

Private 영역은 권한 요청없이 생성, 삭제 가능
Public 영역은 MediaStore 함수를 이용하여 생성, 삭제 등 가능
반응형

 

일단 Storage Scope 에 대한 내용은 아래 링크 참조바랍니다.

[구글 공식내용]

 

Android 11의 저장소 업데이트  |  Android 개발자  |  Android Developers

Android 11의 저장소 업데이트 Android 11(API 수준 30)에서는 플랫폼을 한층 더 강화하여 외부 저장소의 앱 및 사용자 데이터를 더욱 안전하게 보호합니다. 이 버전에는 미디어의 원시 파일 경로 액세

developer.android.com

 

 

자 그러면 Private 영역, Public 영역은 뭐가 있는지 봅시다

 

이때까진 

// 루트경로
String path = Environment.getExternalStorageDirectory().getPath();

위 처럼 루트 경로에서 "패키지명" 폴더 를 만들어서 작업을 많이 했다면.... 이젠 불가....

 

공용 폴더 Path 정리

//공용 DCIM 폴더
String DCIM_Path = Environment.getExternalStoragePublicDirectory(DIRECTORY_DCIM).getPath();

//공용 다운로드 폴더
String Download_Path = Environment.getExternalStoragePublicDirectory(DIRECTORY_DOWNLOADS).getPath();

//공용 음악 폴더
String Music_Path = Environment.getExternalStoragePublicDirectory(DIRECTORY_MUSIC).getPath();

//공용 팟캐스트 폴더
String Podcast_Path = Environment.getExternalStoragePublicDirectory(DIRECTORY_PODCASTS).getPath();

//공용 벨소리 폴더
String Ring_Path = Environment.getExternalStoragePublicDirectory(DIRECTORY_RINGTONES).getPath();

//공용 알람 폴더
String Alarm_Path = Environment.getExternalStoragePublicDirectory(DIRECTORY_ALARMS).getPath();

//공용 알림음 폴더
String Noti_Path = Environment.getExternalStoragePublicDirectory(DIRECTORY_NOTIFICATIONS).getPath();

//공용 사진 폴더
String Picture_Path = Environment.getExternalStoragePublicDirectory(DIRECTORY_PICTURES).getPath();

//공용 영상 폴더
String Movie_Path = Environment.getExternalStoragePublicDirectory(DIRECTORY_MOVIES).getPath();

//공용 문서 폴더
String Document_Path = Environment.getExternalStoragePublicDirectory(DIRECTORY_DOCUMENTS).getPath();

//공용 스크린샷 폴더
String Screenshot_Path = Environment.getExternalStoragePublicDirectory(DIRECTORY_SCREENSHOTS).getPath();

//공용 오디오북 폴더
String Audio_Path = Environment.getExternalStoragePublicDirectory(DIRECTORY_AUDIOBOOKS).getPath();

 

Private 영역 Path 정리

// 내부폴더 Path : /Android/data/패키지명/files/

//내부 DCIM 폴더
String DCIM_Path = context.getExternalFilesDir(DIRECTORY_DCIM).getPath();

//내부 다운로드 폴더
String Download_Path = context.getExternalFilesDir(DIRECTORY_DOWNLOADS).getPath();

//내부 음악 폴더
String Music_Path = context.getExternalFilesDir(DIRECTORY_MUSIC).getPath();

//내부 팟캐스트 폴더
String Podcast_Path = context.getExternalFilesDir(DIRECTORY_PODCASTS).getPath();

//내부 벨소리 폴더
String Ring_Path = context.getExternalFilesDir(DIRECTORY_RINGTONES).getPath();

//내부 알람 폴더
String Alarm_Path = context.getExternalFilesDir(DIRECTORY_ALARMS).getPath();

//내부 알림음 폴더
String Noti_Path = context.getExternalFilesDir(DIRECTORY_NOTIFICATIONS).getPath();

//내부 사진 폴더
String Picture_Path = context.getExternalFilesDir(DIRECTORY_PICTURES).getPath();

//내부 영상 폴더
String Movie_Path = context.getExternalFilesDir(DIRECTORY_MOVIES).getPath();

//내부 문서 폴더
String Document_Path = context.getExternalFilesDir(DIRECTORY_DOCUMENTS).getPath();

//내부 스크린샷 폴더
String Screenshot_Path = context.getExternalFilesDir(DIRECTORY_SCREENSHOTS).getPath();

//내부 오디오북 폴더
String Audio_Path = context.getExternalFilesDir(DIRECTORY_AUDIOBOOKS).getPath();

 

내부폴더를 생성하고 사진, 영상 파일 생성 후 갤러리에 표출하기위해 

// 특정 사진을 갤러리에 동기화
sendBroadcast(new Intent( Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.fromFile(file)) );

동기화를 실행.... 그러나 실패....

 

왜????  Private 영역은 애시당초 Android 폴더 내에 .nomedia 라는 파일이 자동생성 되어있어 갤러리에 동기화가 되질 않는다....하아.... 

 

그래서 DCIM 폴더에 파일을 옮겨야 하는 일이 생겼는데... 

 

그런데... 여기서 공용 폴더에 파일을 옮기려고 했더니 문제가 생겼다.

 

 

 

그냥 옮기면 안되고 MediaStore 함수를 이용하여 보내야 하는 상황이 발생!!!

 

ㅋㅋㅋㅋㅋ 그럼 MediaStore 를 또 알아봐야함 ㅋㅋ 끝이없네 ㅋㅋ 

 

간단하게 함수먼저 똭!!

 

사진파일 이동

   /**
     * @param context : Context
     * @param file : 원본 파일 정보
     * @param SavePath : 옮길 폴더 디렉토리  (ex : DCIM/TEST/사진저장)
     */
    public static void ImageFileMove(Context context, File file, String SavePath){
        try {
            Log.e("ImageFileMove", file.getName());
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
                ContentValues values = new ContentValues();
                values.put(MediaStore.Images.Media.TITLE, file.getName());
                values.put(MediaStore.Images.Media.DISPLAY_NAME, file.getName());
                values.put(MediaStore.Images.Media.DATE_TAKEN, System.currentTimeMillis());
                values.put(MediaStore.Images.Media.RELATIVE_PATH, SavePath);
                values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg");

                Uri uri = context.getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
                OutputStream fos = context.getContentResolver().openOutputStream(uri);

                Bitmap bmp = BitmapFactory.decodeFile(file.getPath() );
                bmp.compress(Bitmap.CompressFormat.JPEG, 100, fos);

                fos.flush();
                fos.close();
            } else {
                String dir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM).toString() + File.separator + SavePath;
                File dir_file = new File(dir);
                if(!dir_file.exists()){
                    dir_file.mkdirs();
                }

                File imgfile = new File(dir_file, file.getName());
                FileOutputStream os = new FileOutputStream(imgfile);
                Bitmap bmp = BitmapFactory.decodeFile(file.getPath() );
                bmp.compress(Bitmap.CompressFormat.JPEG, 100, os);
                os.flush();
                os.close();

                ContentValues values = new ContentValues();
                values.put(MediaStore.Images.Media.TITLE, file.getName());
                values.put(MediaStore.Images.Media.DISPLAY_NAME, file.getName());
                values.put(MediaStore.Images.Media.DATE_TAKEN, System.currentTimeMillis());
                values.put(MediaStore.Images.Media.BUCKET_ID, file.getName());
                values.put(MediaStore.Images.Media.DATA, imgfile.getAbsolutePath());
                values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg");

                context.getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }

 

이거면 로컬에 생성한 사진파일을 DCIM 에 특정폴더로 이동하여 갤러리에 갱신시킬 수 있다 ㅎㅎ 

설명은 편하게 툭툭 쓰지만 삽질을 쫌 했음.... 후우.... 

 

속성값 중에 특히 중요한건 아래내용이다.

속성값 설명
DISPLAY_NAME 저장할 파일 이름
DATE_TAKEN 파일생성 시간
DATA 원본파일 정보
MIME_TYPE 파일 저장 형식
RELATIVE_PATH 저장할 폴더 경로

 

위에 함수를 사용하면 사진파일 이동은 별 문제 없이 가능할 것이다.

 

그럼....영상파일 이동은?!!

 

함수 또 나옵니다~~~~

 

영상파일 이동

    /**
     * @param context : Context
     * @param file : 원본 파일 정보
     * @param SavePath : 옮길 폴더 디렉토리  (ex : DCIM/TEST/영상저장)
     */
    public static void VideoFileMove(Context context, File file, String SavePath){
        try {
            Log.e("VideoFileMove", file.getName());
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
                ContentValues values = new ContentValues();
                values.put(MediaStore.Video.Media.TITLE, file.getName());
                values.put(MediaStore.Video.Media.DISPLAY_NAME, file.getName());
                values.put(MediaStore.Video.Media.DATE_TAKEN, System.currentTimeMillis());
                values.put(MediaStore.Video.Media.RELATIVE_PATH, SavePath);
                values.put(MediaStore.Video.Media.MIME_TYPE, "video/mp4");

                Uri uri = context.getContentResolver().insert(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, values);
                ParcelFileDescriptor pdf = context.getContentResolver().openFileDescriptor(uri, "w", null);
                if(pdf == null){
                    Log.e("asdf", "null");
                }
                else{
                    FileOutputStream fos = new FileOutputStream(pdf.getFileDescriptor());
                    fos.write(FileUtils.readFileToByteArray(file));
                    fos.close();
                    pdf.close();
                    context.getContentResolver().update(uri, values, null, null);
                }
            } else {
                MOVE_FILE(context, MP4_Path, file.getName(), MP4_Path_orig);
            }
            Sub_FileDelete(file, 81);
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    public static void MOVE_FILE(Context context, String inputPath, String inputFile, String outputPath){
        InputStream in = null;
        OutputStream out = null;
        try{
            File dir = new File(outputPath);
            Log.e("dir", dir.getPath());
            if(!dir.exists()){
                dir.mkdirs();
            }

            Log.e("MOVE_FILE", outputPath + "/" + inputFile + "______" + dir.getPath());

            in = new FileInputStream(inputPath + inputFile);
            out = new FileOutputStream(outputPath + "/" + inputFile);

            byte[] buffer = new byte[1024];
            int read;
            while((read = in.read(buffer)) != -1){
                out.write(buffer, 0, read);
            }
            in.close();
            in = null;

            out.flush();
            out.close();
            out = null;

            new File(inputPath + inputFile).delete();

            File tmp_file = new File(outputPath + "/" + inputFile);
            context.sendBroadcast(new Intent( Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.fromFile(tmp_file)) );
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
SMALL

 

이러면 영상파일 이동도 완료 ㅎㅎㅎ 

 

기본적인 내용은 사진 이동하고 비슷함 ㅎㅎ 

 

안드로이드 8, 9, 10, 11 까지 시험 진행 완료된 함수이니 사용해도 무방할 것임 ㅎㅎ 

 

 

 

여기까지 하고 나니 DCIM 폴더에 저장한 사진 리스트를 받아와서 삭제해야하는 경우가 생김!!!

(산넘어 산.... 역시 가볍게 끝내주질 않음....)

 

그래서 DCIM 폴더에 접근해서 전체 파일 정보를 읽어오는

File dir = new File(path);
File[] fileList = dir.listFiles();

했지만... 결과는 Null !!!!!!! 왜?!!! 

 

그럼 아래처럼 파일 존재 확인은???

File file = new File(path);
Log.e("file", "Result : " + file.exists());

가능하다.... 

 

음.... 그래서 다시 검색 시작.....

근데 거의 대다수 사이트에서 똑같이 말하는 내용이 있다.....

아래처럼 WRITE_EXTERNAL_STORAGE Permission 에서 maxSdkVersion 을 28로 설정하라고.....

<uses-permission
    android:name="android.permission.WRITE_EXTERNAL_STORAGE"
    android:maxSdkVersion="28"/>

 

아래처럼 maxSdkVersion 을 29로 설정하면 10버전에서 파일 리스트 호출이 정상적으로 동작함.....

<uses-permission
    android:name="android.permission.WRITE_EXTERNAL_STORAGE"
    android:maxSdkVersion="29"/>

 

누구냐 도대체.... 28로 설정하라해서 삽질하게 만든 사람들은.....하아.....

 

오늘의 삽질은 여기까지.... ㅎㅎ 

 

다 필요한 함수는 아니겠지만 누군가에겐 도움이 되길 바라면서 오늘도 이만 ㅎㅎ 

반응형
반응형

JAVA 는 기본적으로 C 에서 사용하는 Struct 라는 구조체는 존재하지 않음...

 

근데 구조체처럼 관리가 필요한 경우가 있음

 

그때 사용하는 방법을 요약해서 공유드립니다.

 

일단 Java 니깐 클래스를 하나 생성!! 

 

[구조체 클래스 : ST_LIST_File]

public class ST_LIST_File {
    // 사용 인자 선언
    private boolean f_exist;
    private final String filetype;
    private final String orig_filename;
    private final String date;
    private final String time;
    private final String mode;
    private final String type;
    private final String frame;
    private final String size;
    private final String makeTime;

    // 내용 삽입을 위한 Public 클래스 선언
    public ST_LIST_File(String filetype, String orig_filename, String date, String time, String mode, String type, String frame, String size, String makeTime, boolean f_exist){
        this.filetype = filetype;
        this.orig_filename = orig_filename;
        this.date = date;
        this.time = time;
        this.mode = mode;
        this.type = type;
        this.frame = frame;
        this.size = size;
        this.makeTime = makeTime;
        this.f_exist = f_exist;
    }

    // 값 호출
    public String get_filetype(){
        return this.filetype;
    }
    public String get_orig_filename(){
        return this.orig_filename;
    }
    public String get_date(){
        return this.date;
    }
    public String get_time(){
        return this.time;
    }
    public String get_mode(){
        return this.mode;
    }
    public String get_type(){
        return this.type;
    }
    public String get_frame(){
        return this.frame;
    }
    public String get_size(){
        return this.size;
    }
    public String get_makeTime(){
        return this.makeTime;
    }
    public boolean get_f_exist(){
        return this.f_exist;
    }
    public void set_f_exist(boolean flag){
        this.f_exist = flag;
    }
}
반응형

클래스 명은 원하는데로 지정 

 

 

해당 클래스로는 일단 1개 정보밖에 저장이 안됨...

 

그래서!!!

 

// 구조체 ArrayList 선언
ArrayList<ST_LIST_File> ST_TEST = new ArrayList<>();

 

위 처럼 ArrayList 를 사용하여 해당 구조체 클래스를 ArrayList로 선언!!

 

이제 데이터를 삽입해야 함

 

ArrayList 삽입하는 방식과 똑같음

SMALL
// 데이터 구조에 맞춰 선언
ST_LIST_File tmp = new ST_LIST_File(
        "데이터1",
        "데이터2",
        "데이터3",
        "데이터4",
        "데이터5",
        "데이터6",
        "데이터7",
        "데이터8",
        "데이터9",
        "데이터10");

// ArrayList 에 자료 삽입
ST_TEST.add(tmp);

이렇게 선언 후 데이터 삽입을 하면 저장은 완료 ㅎㅎ 

 

 

사용은 어떻게???

 

for(int i = 0 ; i < ST_TEST.size() ; i++){
	// 구조체 클래스에 선언한 호출함수로 해당 값을 호출
	Log.d("Result", ST_TEST.get(i).get_date());
}

 

이렇게 사용하면 심플하게 사용 ㅎㅎ 

 

너무 어렵게 생각하지 말고 모두들 즐코딩 ㅎㅎ 

반응형
반응형

네트워크를 통해서 HTTP 통신 후 Request 로 응답처리까지 처음하는 사람의 경우 어려울 수도 있어서 정리함

 

코드는 간단하게 정리해서 공유드리겠음

 

클래스는 2개를 추가해야한다.

RequestHttpConnection.java 와 NetworkTask.java 클래스 2개를 생성한다.

 

SMALL

 

1. RequestHttpConnection.java

   HTTP 연결 및 데이터 전달을 위한 클래스, 방식은 POST

public class RequestHttpConnection {
    public String request(String _url, ContentValues _params){

        // HttpURLConnection 참조 변수.
        HttpURLConnection urlConn = null;
        // URL 뒤에 붙여서 보낼 파라미터.
        StringBuilder sbParams = new StringBuilder();

        /**
         * 1. StringBuffer에 파라미터 연결
         * */
        // 보낼 데이터가 없으면 파라미터를 비운다.
        if (_params == null)
            sbParams.append("");
            // 보낼 데이터가 있으면 파라미터를 채운다.
        else {
            // 파라미터가 2개 이상이면 파라미터 연결에 &가 필요하므로 스위칭할 변수 생성.
            boolean isAnd = false;
            // 파라미터 키와 값.
            String key;
            String value;

            for(Map.Entry<String, Object> parameter : _params.valueSet()){
                key = parameter.getKey();
                value = parameter.getValue().toString();

                // 파라미터가 두개 이상일때, 파라미터 사이에 &를 붙인다.
                if (isAnd)
                    sbParams.append("&");

                sbParams.append(key).append("=").append(value);

                // 파라미터가 2개 이상이면 isAnd를 true로 바꾸고 다음 루프부터 &를 붙인다.
                if (!isAnd)
                    if (_params.size() >= 2)
                        isAnd = true;
            }
        }

        /**
         * 2. HttpURLConnection을 통해 web의 데이터를 가져온다.
         * */
        try{
            URL url = new URL(_url);
            urlConn = (HttpURLConnection) url.openConnection();
            urlConn.setConnectTimeout(HTTP_TIMEOUT_m);
            urlConn.setReadTimeout(HTTP_TIMEOUT_m);

            // [2-1]. urlConn 설정.
            urlConn.setRequestMethod("POST"); // URL 요청에 대한 메소드 설정 : POST.
            urlConn.setRequestProperty("Accept-Charset", "UTF-8"); // Accept-Charset 설정.
            urlConn.setRequestProperty("Context_Type", "application/x-www-form-urlencoded;cahrset=UTF-8");

            // [2-2]. parameter 전달 및 데이터 읽어오기.
            String strParams = sbParams.toString(); //sbParams에 정리한 파라미터들을 스트링으로 저장. 예)id=id1&pw=123;
            OutputStream os = urlConn.getOutputStream();
            os.write(strParams.getBytes(StandardCharsets.UTF_8)); // 출력 스트림에 출력.
            os.flush(); // 출력 스트림을 플러시(비운다)하고 버퍼링 된 모든 출력 바이트를 강제 실행.
            os.close(); // 출력 스트림을 닫고 모든 시스템 자원을 해제.

            // [2-3]. 연결 요청 확인.
            // 실패 시 null을 리턴하고 메서드를 종료.
            if (urlConn.getResponseCode() != HttpURLConnection.HTTP_OK) {
                Log.e("RequestHttpConnection : Err[2-3] " + urlConn.getResponseCode());
                return null;
            }

            // [2-4]. 읽어온 결과물 리턴.
            // 요청한 URL의 출력물을 BufferedReader로 받는다.
            BufferedReader reader = new BufferedReader(new InputStreamReader(urlConn.getInputStream(), StandardCharsets.UTF_8));

            // 출력물의 라인과 그 합에 대한 변수.
            String line;
            StringBuilder page = new StringBuilder();

            // 라인을 받아와 합친다.
            while ((line = reader.readLine()) != null){
                page.append(line);
            }

            return page.toString();

        } catch (IOException e) { // for URL.
            e.printStackTrace();
        }// for openConnection().
        finally {
            if (urlConn != null)
                urlConn.disconnect();
        }

        Log.e("RequestHttpConnection :  Err[END]");

        return null;

    }
}
반응형

2. NetworkTask.java

   AsyncTask 로 네트워크 Request 를 호출하여 처리할 수 있도록 함

public class NetworkTask extends AsyncTask<Void, Void, String> {

    private final String url;
    private final ContentValues values;
    private final Handler receiveHandler;  // 리턴 메세지를 반환할 Handler

    public NetworkTask(String url, ContentValues values, Handler receiveHandler) {
        this.url = url;
        this.values = values;
        this.receiveHandler = receiveHandler;
    }

    @Override
    protected String doInBackground(Void... params) {

        Log.d("URL : " + url );

        String result; // 요청 결과를 저장할 변수.
        RequestHttpConnection requestHttpURLConnection = new RequestHttpConnection();
        result = requestHttpURLConnection.request(url, values); // 해당 URL로 부터 결과물을 얻어온다.

        return result;
    }

    @Override
    protected void onPostExecute(String s) {
        super.onPostExecute(s);

        //doInBackground()로 부터 리턴된 값이 onPostExecute()의 매개변수로 넘어오므로 s를 메세지에 담아 리턴한다.
        try {
            Log.d("onPostExecute : " + s );
            Message msg = receiveHandler.obtainMessage(SEND_WEB_RETURN);
            Bundle data = new Bundle();
            data.putString("WEB_RETURN", s);
            msg.setData(data);
            receiveHandler.sendMessage(msg);
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

 

위 클래스를 작성하였다면 이젠 사용하는 방법만 익히면 가능함

 

3. 호출하여 사용

    메인에서 호출을 진행한다고 하는 경우

    아래처럼
    1) POST 데이터를 ContentValues 클래스로 삽입 

    2) 리턴 값을 처리 할 Handler 를 생성

    3) 변수들을 삽입 후 실행~!!

    4) 리턴으로 돌아온 값을 처리하면 끝~!!

//POST 데이터를 삽입
ContentValues value = new ContentValues();
value.put("DATA1", data1);
value.put("DATA2", data2);

// 데이터 리턴값을 처리할 Handler
Handler receive_handler = new Handler(msg -> {
    if(msg.what == SEND_WEB_RETURN){
        String data = msg.getData().getString("WEB_RETURN");
        if(!data.equals("null")){
            try {
                Log.d("RecvData", data);
                JSONObject object = new JSONObject(data);  //JSON 파싱
                ... //그 이후 응답 데이터 처리 진행
            } catch (JSONException e) {
                e.printStackTrace();
            }
        }
    }
    return true;
});

NetworkTask HTTP_REQ = new NetworkTask("URL 주소", value, receive_handler);
HTTP_REQ.execute();

 

복잡해 보이지만 RequestHttpConnection.java 와 NetworkTask.java 는 한번 만들어 두면 변경할 필요없이 계속 사용 가능하다.

 

그럼 오늘도 모두들 화이팅 하세요 ㅎㅎ 

반응형
반응형

Android 11 버전부터 바뀐점이 확인됨....

 

바로 폰에 설치되어 있는 다른 앱들의 패키지 정보를 가져오는 기능이 문제...

 

List<ApplicationInfo> pack = context.getPackageManager().getInstalledApplications(PackageManager.GET_META_DATA);

 

해당 함수는 폰에 설치된 다른 패키지 정보를 가져오는 함수입니다.

 

그런데!! 

 

반응형

 

 

타겟 Build 버전을 안드로이드 11 API30 으로 적용할 경우 시스템 어플 리스트 및 일부 앱만 출력 될 뿐

더이상 설치된 모든 앱 패키지 정보를 가져오지 못합니다.

 

안드로이드 10 버전까진 정상동작....

 

그럼 어떻게 변경이 되나.... 

 

일단 공식문서상 내용은 다음과 같습니다. [링크]

 

패키지 공개 상태 관리  |  Android 개발자  |  Android Developers

패키지 공개 상태 관리 앱을 만들 때는 기기에 설치된 다른 앱, 즉 내 앱에서 액세스하려는 앱을 표시하는 패키지 집합을 고려하는 것이 중요합니다. 앱이 Android 11(API 수준 30) 이상을 타겟팅한다

developer.android.com

 

간단하게 설명하자면 아에 불가능한게 아니라 특정 패키지명을 명시한다면 검색 할 수 있다는 내용입니다.

 

<manifest>
  <queries>
      <package android:name="패키지명1" />
      <package android:name="패키지명2" />
  </queries>
</manifest>

 

위 처럼 manifest 속성파일 내에 queries 값으로 검색할 패키지명을 명시해 주면 가능합니다.

 

SMALL

 

List<ApplicationInfo> pack = getPackageManager().getInstalledApplications(PackageManager.GET_META_DATA);
for(ApplicationInfo packageInfo : pack){
	Drawable App_icon = getPackageManager().getApplicationIcon(packageInfo.packageName);
    String App_name = getPackageManager().getApplicationLabel(context.getPackageManager().getApplicationInfo(packageInfo.packageName, PackageManager.GET_UNINSTALLED_PACKAGES)).toString();
}

그러면 해당 코드를 이용하여 해당 패키지의 기본 아이콘 및 앱 이름을 가져올 수 있습니다.

 

이걸로 또 문제가 발생했지만 뭐... 해결방법은 찾았으니 모두들 즐코딩하세요 ㅎㅎ 이만

반응형
반응형

안드로이드 개발을 하다보면 단말기의 Bluetooth Log 를 확인해야하는 경우가 발생

 

해당 내용을 간략하게 공유함

 

아래 스샷처럼 [개발자 옵션] -> [블루투스 HCI 스누프 로그 사용] 을 사용으로 변경해주면 된다.

참고. 개발자 모드 스크린샷

로그 사용을 켰다면 블루투스를 한번 껐다 켜주는게 좋다

 

그리고 시험하려는 단말기와 블루투스 통신을 진행한 뒤 .....

 

이제 로그를 추출해야함

SMALL

 

1. Android Studio 를 실행!!

   [Android Studio 다운로드]

 

Download Android Studio and SDK tools  |  Android Developers

<!-- hide description -->

developer.android.com

2. 폰을 USB 연결!!

3. CMD 창에서 ADB.exe 가 있는 곳으로 이동

cd C:\Users\USER\AppData\Local\Android\Sdk\platform-tools
C:\Users\USER\AppData\Local\Android\Sdk\platform-tools>adb bugreport test.zip
/data/user_de/0/com.android.shell/files/bugreports/bugreport-2021-11-18-14-27-54.zip: 1 file pulled, 0 skipped. 24.9 MB/s (9181748 bytes in 0.351s)
Bug report copied to test.zip

4. 3번에서 adb bugreport test.zip 을 입력하면 전체 버그 리포트가 추출된다

5. Android Studio 에서 File Explorer 를 클릭한다 보통 화면 우측 하단에 존재함

참고. 화면 우측 하단 File Explorer 위치

반응형

6. 버그리포트는 bugreports 라는 폴더에 존재함

참고. bugreport 파일 위치

7. 해당 파일 우클릭 -> Save As 로 내 PC로 저장하면 됨. 그리고 압축 해제!! 

8. FS/data/log/bt 에 btsnoop_hci.log 파일이 존재 확인

9. Wireshark 라는 프로그램으로 해당 파일을 열면 완료!!

  [Wireshark 다운로드]

 

Wireshark · Download

Download Wireshark The current stable release of Wireshark is 3.4.10. It supersedes all previous releases. You can also download the latest development release (3.6.0rc3) and documentation.

www.wireshark.org

참고. Wireshark 로 btsnoop_hci.log 검색

10. 로그 분석 시작 ㅎㅎ 수고하세요 ㅎㅎ 

반응형

+ Recent posts