2018년 1월 21일 일요일

apk extractor(추출기) 소스 설명

앞에서 apk extractor를 만들었습니다.

https://swlock.blogspot.com/2018/01/apk-extractor.html

소스 설명이 없어서 준비하였습니다.

1. List는 기본

첫화면은 list로 구성되어 있습니다.

일반적인 list는 앞에 아이콘을 배치하기 어렵기 때문에 Custom list를 사용하게 됩니다. list 구성에 대한 이해는 아래 링크를 참고 하시기 바랍니다.
https://swlock.blogspot.com/2017/03/custom-list-which-has-check-box-in.html

MainActivity의 onCreate 메소드부터 살펴보겠습니다.
크게 4개의 함수 호출을 하게됩니다.

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_applist);

        setupList();
        setupAdapter();
        setupFilter();
        setupPermission();
    }

setupList는 list에서 버튼을 눌렀을때 PackageSummary Activity를 띄워주기 위한 처리를 합니다.
    private void setupList() {
        AdapterView.OnItemClickListener listener= new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                MyPackageInfo info = mAdapter.itemForPosition(position);
                if (info != null) {
                    Intent intent = new Intent(
                            null, Uri.fromParts("package", info.info.packageName, null));
                    intent.setClass(MainActivity.super.getBaseContext(), PackageSummary.class);
                    startActivity(intent);
                }
            }
        };
        listView=(ListView)findViewById(R.id.list);
        listView.setOnItemClickListener(listener);
    }
setupAdapter()는 AdapterAsyncTask class를 실행합니다.
코드는 간단하지만, 비동기적으로 AdapterAsyncTask 가 실행이 됩니다.
    private void setupAdapter() {
        AdapterAsyncTask adaterAsyncTask = new AdapterAsyncTask(MainActivity.this);
        adaterAsyncTask.execute();
    }
AsyncTask로 부터 상속 받아서 동시 작업이 필요한 경우 사용하게 됩니다, 여기에서는 list를 구성하는데 시간이 소요되기 때문에 사용하였습니다. 해당 class를 사용하지 않고 구현하게되면 목록을 구성하는데 시간이 오래걸린다면, 단말이 응답없는 상태에 빠질 수 있습니다. 이렇게 되면 ANR이 발생하게 됩니다. AdapterAsyncTask 를 보는 방법은 onPreExcute()가 먼저 실행되고, doInBackground(), onPostExcute()가 수행됩니다. 보통 Thread로 만드는 경우도 있지만, android에서 ui 처리가 필요한경우 많이 사용하게 됩니다.



    public class AdapterAsyncTask extends AsyncTask<String,Void,String> {
        private ProgressDialog mDlg;
        Context mContext;

        public AdapterAsyncTask(Context context) {
            mContext = context;
        }
        @Override
        protected void onPreExecute() {
            super.onPreExecute();
            mDlg = new ProgressDialog(mContext);
            mDlg.setProgressStyle(ProgressDialog.STYLE_SPINNER);
            mDlg.setMessage( mContext.getString(R.string.loading));
            mDlg.show();
        }
        @Override
        protected String doInBackground(String... strings) {
            List<PackageInfo> pkgs = mContext.getPackageManager().getInstalledPackages(0);
            for (int i=0; i<pkgs.size(); i++) {
                MyPackageInfo info = new MyPackageInfo();
                info.info = pkgs.get(i);
                info.label = info.info.applicationInfo.loadLabel(getPackageManager()).toString();
                mPackageInfoList.add(info);
            }
            if (mPackageInfoList != null) {
                Collections.sort(mPackageInfoList, sDisplayNameComparator);
            }
            mdisplayPackageInfoList.addAll(mPackageInfoList);
            packageCount = mPackageInfoList.size();
            return null;
        }
        @Override
        protected void onPostExecute(String s) {
            super.onPostExecute(s);
            mDlg.dismiss();
            mAdapter = new PackageListAdapter(mContext);
            listView.setAdapter(mAdapter);
        }
    }

다음으로 setupFilter 인데요. 이건 list에서 검색을 할때 사용합니다. 돋보기 있는 칸에서 글씨를 쓰게되면 실시간으로 list에 필터를 걸어서 list의 adapter내용을 변경시킵니다. 그러면 list가 변경되게 됩니다.

    private void setupFilter() {
        editView=(EditText)findViewById(R.id.editText_appfilter);
        editView.addTextChangedListener(new TextWatcher() {
            @Override
            public void beforeTextChanged(CharSequence s, int start, int count, int after) {
            }

            @Override
            public void onTextChanged(CharSequence s, int start, int before, int count) {
            }
            @Override
            public void afterTextChanged(Editable s) {
                String searchText = editView.getText().toString();
                if( mAdapter!= null ) mAdapter.fillter(searchText);
            }
        });
    }

setupPermission는 안드로이드 23 부터는 내장 sdcard영역에 write권한을 획득하려면 runtime에 획득 하여야 합니다. 권한을 획득 하는 코드 입니다.

    private void setupPermission() {
        if (Build.VERSION.SDK_INT >= 23) {
            if (ContextCompat.checkSelfPermission(this, android.Manifest.permission.WRITE_EXTERNAL_STORAGE)
                    != PackageManager.PERMISSION_GRANTED || ContextCompat.checkSelfPermission(this, android.Manifest.permission.READ_EXTERNAL_STORAGE)
                    != PackageManager.PERMISSION_GRANTED) {
                ActivityCompat.requestPermissions(this,
                        new String[]{android.Manifest.permission.WRITE_EXTERNAL_STORAGE, android.Manifest.permission.READ_EXTERNAL_STORAGE},
                        1);
            } else {
                //do something
            }
        } else {
            //do something
        }
    }

2. Package 정보 얻어오기

패키지 list를 얻어오려면 기본적으로 휴대폰에 설치된 어플들의 목록을 얻어와야 합니다.
여기에서는 AdapterAsyncTask class의 doInBackground 메소드 아래 코드에 의해서 이루어 집니다.
List<PackageInfo> pkgs = mContext.getPackageManager().getInstalledPackages(0);

3. PackageSummary Activity

PackageSummary는 구글 소스에서 가져왔습니다.
Package의 자세한 정보들은 List에서 버튼을 눌렀을때 보여주도록 하였습니다. 이때 activity가 바뀌게 됩니다. 이때 패키지명을 intent로 전달하게 됩니다.
Intent intent = new Intent(
        null, Uri.fromParts("package", info.info.packageName, null));
intent.setClass(MainActivity.super.getBaseContext(), PackageSummary.class);
startActivity(intent);
보낼때 Uri.fromParts로 보내면서 scheme을 "package"로 하였지만, 받을때는 scheme 부분이 중요하지는 않고 뒤쪽에 전달되는 packageName부분만 중요하기 때문에 getSchemeSpecificPart 메소드를 이용해서 얻어옵니다.
mPackageName = getIntent().getData().getSchemeSpecificPart();
이것을 getPackageInfo 메소드를 이용하면 좀더 많은 정보를 획득가능합니다.
            info = pm.getPackageInfo(mPackageName,
                        //PackageManager.GET_ACTIVITIES |
                        //PackageManager.GET_RECEIVERS |
                        //PackageManager.GET_SERVICES |
                        //PackageManager.GET_PROVIDERS |
                        PackageManager.GET_INSTRUMENTATION |
                        PackageManager.GET_DISABLED_COMPONENTS |
                        0
            );


4. APK 추출하기

Apk를 추출하려면 apk가 설치된 위치를 알아야합니다. 이것은 getPackageInfo 결과의 info.applicationInfo.sourceDir 값을 이용하면 알 수 있습니다.

            info = pm.getPackageInfo(mPackageName,
                        //PackageManager.GET_ACTIVITIES |
                        //PackageManager.GET_RECEIVERS |
                        //PackageManager.GET_SERVICES |
                        //PackageManager.GET_PROVIDERS |
                        PackageManager.GET_INSTRUMENTATION |
                        PackageManager.GET_DISABLED_COMPONENTS |
                        0
            );

info.applicationInfo.sourceDir

해당 정보를 이용해서 미리 정해놓은 위치로 복사하면 됩니다.
copy(new File(info.applicationInfo.sourceDir), new File(sdcardPath+apkname+".apk"));


5. 전체 소스

소스는 이전에도 공유한 소스와 동일합니다.
https://drive.google.com/file/d/1HPWLyD_SzdgsOmKcUBIXQ5eMnJ-Lcrot/view?usp=sharing

댓글 없음:

댓글 쓰기