레이블이 어플제작인 게시물을 표시합니다. 모든 게시물 표시
레이블이 어플제작인 게시물을 표시합니다. 모든 게시물 표시

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

2018년 1월 14일 일요일

apk extractor(추출기)

안드로이드 사용자라면 많이 사용하게 되는 apk 추출기 소스입니다.
apk는 안드로이드에 있어서 실행 파일로 생각하면 됩니다. 마켓에 이미 apk를 추출하는 많은 어플들이 존재하지만 대부분 광고가 달려있어서 설치하면 왠지 마음만큼이나 어플이 무겁습니다.
광고 없고 검색이 편리한 추출기를 만들어 보겠습니다.
특징은 검색시 패키지 명이나 화면에 보이는 어플 이름으로 검색이 가능합니다.

작업한 내용

작업은 custom list view를 기반으로 어플 목록을 나열한뒤 클릭하면 자세하게 보는 activity가 연결되고 거기에 Extract 버튼이 존재하는 방식입니다.

첫화면은 아래와 같은 형태로 전체 설치된 앱목록이 주르륵 나옵니다.


원하는 앱을 선택하면 아래와 같이 자세한 내용이 보이며 Extract 버튼을 누르면 /sdcard 폴더에 파일이 생성되는 방식입니다.

소스

자세한 설명은 나중에 하고 이번에는 소스와 스크린샷만 올리겠습니다. 관심있는 분은 소스 분석해보시기 바랍니다.


2015년 9월 26일 토요일

안드로이드 계산기 만들기 javacc 이용 소스


앞에서 만든 카톡 List와 JavaCC로 구현한 계산기를 두개 섞어서 카톡형 계산기를 만들어 보았습니다.
소스는 아래 링크 참고 하시기 바랍니다.
혹시 소스 설명이 필요한게 있을지 모르겠지만 그건 나중에 남기도록 하겠습니다. ~~~



https://drive.google.com/file/d/0B9vAKDzHthQIMnJ1bEtkcU4xWVk/view?usp=sharing


2014년 10월 25일 토요일

디아블로 전정실 안드로이드앱으로 html 파싱해서 보자(영웅들 목록)어플제작

영웅들 목록과 영웅들 얼굴들을 나타낼 차례입니다.
해야 할 것들을 정리해 보자면 다음과 같습니다.

1. Network으로 접속해서 영웅 목록 html 가져오기
2. Network으로 접속할때 캐쉬 사용하기(캐쉬에 있다면 접속하지 않기)
3. 영웅들을 파싱해서 List 에 보여주기 (Custom list 사용)
4. 영웅들 이미지를 List에 추가하기(영웅 이미지도 캐쉬 사용하기)
5. refresh 구현(캐쉬 지우는 조건)(이건 이번에 작업하지 않았습니다.)

이전 내용보다는 많지 않습니다.
완성된 모습입니다.


thread를 이용하여 network 접속하는 소스 입니다.

Thread nthread = new Thread(new Runnable() {
     public void run() {
      try {
       heroInfo = obj_itemDetails.getFromNetwork(context);
       if( heroInfo != null ) {
        dialog.dismiss();
        startActivity(intent);
       }else{
        dialog.dismiss();
           AlertDialog.Builder alt_bld = new AlertDialog.Builder(context);
           alt_bld.setMessage("Net work error").setCancelable(false).setNeutralButton("OK",
               new DialogInterface.OnClickListener() {
               public void onClick(DialogInterface dialog, int id) {
                   dialog.cancel();
               }
               });
           AlertDialog alert = alt_bld.create();
           // Title for AlertDialog
           //alert.setTitle("Title");
           // Icon for AlertDialog
           //alert.setIcon(R.drawable.icon);
           alert.show();
       }
      } catch (Throwable ex) {
       ex.printStackTrace();
       Log.d(TAG,"ERROR"+ex);
      }
     }
    });


핵심 소스는 getFromNetwork(context); 입니다. 저장하는 위치는 context.getCacheDir()함수로 정합니다. 이렇게 하면 /data/data/패키지명/cache 폴더 위치를 얻을 수 있습니다.
모든 처리가 제대로 된다면 영웅들 데이터가 들어있는 arraylist를 반환합니다.
career로 정보가 있는 html을 읽어 heroInfo.executeGetHeros()함수를 호출하면 영웅들의 목록이 올라오게되고 영웅 목록으로 부터 heros.get(i).executeGetHero(hdata); 함수를 호출하여 영웅의 이미지 및 하드코어 여부 레벨등을 가져오게 됩니다.

 public HeroInfo getFromNetwork(Context context) {
  // career 붙어있는 경로는 전체 목록을 획득할때 사용한다.
  String url = getProfileURL()+"/career";
  // 캐쉬경로를 저장 경로로 설정한다.
  String career = context.getCacheDir()+"/"+getProfileStr()+Utils.getMD5String(url)+"career.html";
  // 캐쉬에 저장된 파일이 있는지 검사 없으면 읽어옴
  Log.d(TAG,"savename:"+career);
  if( !Utils.checkFileExists(career) ){
   
   GetHttp html = new GetHttp();
   // 네트워크 에러 처리
   if( html.execute(url) ){
    Utils.fileWriteByte(career, html.getByte());
   } else {
    return null;
   }
  }
  String data = Utils.readFile(career);
  if( data == null ) return null;
  HeroInfo heroInfo;
  heroInfo = new HeroInfo(data);
  heroInfo.executeGetHeros();
  
  ArrayList<Hero> heros = heroInfo.getHeros();
  if( heros.size() == 0 ) {
   return null;
  }

  for(int i=0 ; i < heros.size() ; i++ ) {
   ///d3/ko/profile/donarts-3994/hero/30559318
   String hurl = getProfileURL()+"/hero/"+heros.get(i).getId();
   String hinfo = context.getCacheDir()+"/"+getProfileStr()+Utils.getMD5String(hurl)+"hero.html";
   Log.d(TAG,"strt:"+hurl);
   if( !Utils.checkFileExists(hinfo) ){
    GetHttp html = new GetHttp();
    // 네트워크 에러 처리
    if( html.execute(hurl) ){
     Utils.fileWriteByte(hinfo, html.getByte());
    } else {
     return null;
    }
   }
   String hdata = Utils.readFile(hinfo);
   if( hdata == null ) return null;
   // 하나의 hero 정보만을 가져온다. png 위치 및 정확한 class 등등
   heros.get(i).executeGetHero(hdata);
   
   // 이후 구한 png 경로로 부터 다시 네트워크 처리를 한다
   String furl = heros.get(i).getFaceURL();
   String finfo = context.getCacheDir()+"/"+"common_"+Utils.getMD5String(furl)+".png";
   heros.get(i).setFaceLocalPath(finfo);
   
   if( !Utils.checkFileExists(finfo) ){
    GetHttp html = new GetHttp();
    // 네트워크 에러 처리
    if( html.execute(furl) ){
     Utils.fileWriteByte(finfo, html.getByte());
    } else {
     return null;
    }
   }
  }
  return heroInfo;
 }

heros.get(i).executeGetHero(hdata);의 특징은 StringTokenizer의 두번째 인자가 조금씩 바뀐다는 것입니다. 이것은 html 데이터를 보고 어떤값을 가져올지 봐서 적당한 값으로 설정합니다.

public void executeGetHero(String data) {
  // TODO Auto-generated method stub
  StringTokenizer st = new StringTokenizer(data," \t\r\n,'=[{}];()<>\"");
  boolean getd = false;
  while(st.hasMoreTokens()){
   String temp = st.nextToken();
   if(temp!=null && !temp.isEmpty() && temp.equals("class")){
    temp = st.nextToken();
    //System.out.println("["+temp+"]");
    if(temp!=null && !temp.isEmpty() && temp.equals("paragon-level")){
     // Get paragon-level
     temp = st.nextToken();
     this.setParagonLevel(Integer.parseInt(temp)); 
     //System.out.println(" "+temp);
     temp = st.nextToken();
     if(temp!=null && !temp.isEmpty() && temp.equals("/span")){
      temp = st.nextToken();
      if(temp!=null && !temp.isEmpty() && temp.equals("/strong")){
       // Get local class
       temp = st.nextToken();
       setLocalCls(temp);
       //System.out.println(" "+temp);
       while(st.hasMoreTokens()){
        temp = st.nextToken();
        if(temp!=null && !temp.isEmpty() && temp.equals("/a")){
         break;
        } else if(temp!=null && !temp.isEmpty() && temp.equals("d3-color-hardcore")){
         setHardCore(true);
         break;
        }
       }
      }
     }
    }
   }else if(temp!=null && !temp.isEmpty() && temp.equals("og:image") && !getd ){
    temp = st.nextToken();
    //System.out.println("1 "+temp);
    if(temp!=null && !temp.isEmpty() && temp.equals("content")){
     // Get Image
     temp = st.nextToken();
     //System.out.println("2 "+temp);
     getd = true;
     setFaceURL(temp);
    }
   }
  }
 }

작업소스

2014년 10월 8일 수요일

디아블로 전정실 안드로이드앱으로 html 파싱해서 보자(StringTokenizer 이용)



처음 파싱해야 하는 내용은 영웅들의 목록(영웅들의 레벨과 type(hard core,normal 등등)이다.

파싱하는 방법은 jericho 라이브러리가 있긴 한데.... 공부도 할겸 여기에서는 간단하게 만들도록 하겠다.

html 읽어오기

html을 읽어 오기 위해서는 소켓을 열고 connection을 한 뒤 http header에 데이터를 보내서 읽어 오면 되겠지만 이미 java에는 html connection 관련 라이브러리(HttpURLConnection)가 준비되어 있다.
따라서 검색해보니 예제들을 참고 해서 구현해 보았다.
( http://markan82.tistory.com/32 )

GetHttp.java


public class GetHttp {
 static final int ERROR_CODE_HTTP_NOT_FOUND = -404;
 static final int ERROR_CODE_HTTP_UNAUTHORIZED = -401;
 static final int ERROR_CODE_HTTP_ELSE = -1;
 static final int ERROR_CODE_HTTP_EXCEPTION = -1000;
 static final int ERROR_CODE_NOERROR = 0;
 StringBuilder html = null;
 int errorCode = 0;
 public String get() {
  return html.toString();  
 }
 public int getErrorCode() {
  return errorCode;
 }
 public boolean execute(String addr) {
  return execute(addr, 5, 10);
 }
 public boolean execute(String addr, int connTimeOutSec, int readTimeOutSec) {
  html = new StringBuilder();
  try {
   //인터넷상의 자원이나 서비스 주소값을 URL 객체로 생성합니다.
   URL url = new URL(addr);
   
   //접속에 성공하면 양방향 통신이 가능한 연결 객체(HttpURLConnection)가 리턴됩니다.
   HttpURLConnection conn = (HttpURLConnection) url.openConnection();
   if( conn != null ) {
    conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded;charset=utf-8");
    //연결 제한 시간을 1/1000 초 단위로 지정합니다. 
    //0이면 무한 대기입니다.
    conn.setConnectTimeout(1000*connTimeOutSec);
    
    //읽기 제한 시간을 지정합니다. 0이면 무한 대기합니다.
    conn.setReadTimeout(1000*readTimeOutSec);
    
    //캐쉬 사용여부를 지정합니다.
    conn.setUseCaches(false);
    
    //http 연결의 경우 요청방식을 지정할수 있습니다. 
    //지정하지 않으면 디폴트인 GET 방식이 적용됩니다.
    //conn.setRequestMethod("GET" | "POST");
    
    //서버에 요청을 보내가 응답 결과를 받아옵니다.
    int resCode = conn.getResponseCode();
    
    //요청이 정상적으로 전달되엇으면 HTTP_OK(200)이 리턴됩니다.
    //URL이 발견되지 않으면 HTTP_NOT_FOUND(404)가 리턴됩니다.
    //인증에 실패하면 HTTP_UNAUTHORIZED(401)가 리턴됩니다.
    if( resCode == HttpURLConnection.HTTP_OK ) {
     
     //요청에 성공했으면 getInputStream 메서드로 입력 스트림을 얻어 서버로부터 전송된 결과를 읽습니다.
     InputStreamReader isr = new InputStreamReader(conn.getInputStream(),"utf-8");
     
     //스트림을 직접읽으면 느리고 비효율 적이므로 버퍼를 지원하는 BufferedReader 객체를 사용합니다.
     BufferedReader br = new BufferedReader(isr);
     for(;;) {
      String line  = br.readLine();
      if( line == null ) break;
      html.append(line + "\n");
     }
     br.close();
    } else if( resCode == HttpURLConnection.HTTP_NOT_FOUND ) {
     errorCode = ERROR_CODE_HTTP_NOT_FOUND;
     return false;
    } else if( resCode == HttpURLConnection.HTTP_UNAUTHORIZED ) {
     errorCode = ERROR_CODE_HTTP_UNAUTHORIZED;
     return false;
    } else {
     errorCode = ERROR_CODE_HTTP_ELSE;
     return false;
    }
    conn.disconnect();
   } else {
    errorCode = ERROR_CODE_HTTP_ELSE;
    return false;
   }
  } catch(Exception e) {
   errorCode = ERROR_CODE_HTTP_EXCEPTION;
   return false;
  }
  errorCode = ERROR_CODE_NOERROR;
  return true;
 } 
}

위 클래스를 테스트 해볼 수 있는 테스트 코드이다.


GetHttp html = new GetHttp();
html.execute("http://kr.battle.net/d3/ko/profile/donarts-3994/career");
System.out.println( "bText = " + html.get());
Util.FileWrite("D:\\test.html",html.get());


저장된 html 파일을 열어서 어떤걸 파싱 해야 하는지 저장하고 html 내용을 봤다. (크롬이나,ie를 이용해서 html을 저장해도 되고 위에서 사용한 예제를 이용한 파일을 봐도 동일하다.)

html 중간쯤 보면 아래와 같은 부분이 나온다.



html 내에 javascript를 처리하는 부분인데 정리가 아주 잘 되어있다.
해당 정보를 이용해서 추출하는 코드를 만들어보자.
StringTokenizer class를 이용하였다. 해당 class는 문자열에서 token 단위로 건너뛰면서 문자열을 검사 할 수 있는 함수이다. 마지막임의 판단을 '>' 문자열로 하였다. 그 이유는 토큰이 안되는 문자를 미리 정의할 수 있는데(" \t\r\n,':=[{}];/()":이 문자로 정의함)'>'의 경우를 정의하지 않아서 html에서 토큰으로 인식하게 된다. 


String data = Util.FileRead("D:\\test.html");
//기본적으로 공백 문자가 구획문자로 사용됩니다 
//(space character , tab character  ,newline character , carriage-return, form-feed)
StringTokenizer st = new StringTokenizer(data," \t\r\n,':=[{}];/()");
while(st.hasMoreTokens()){
 String temp = st.nextToken();
 //System.out.println(" " +temp+"\n");
 if(temp.equals("Profile.heroes")){
  while(st.hasMoreTokens()){
   //{ id: 30559319, name: 'donartsA', level: 70, 'class': 'barbarian', historyType: 'hero' },
   temp = st.nextToken(); // id
   if( temp.equals(">") ) break;
   System.out.println(" " +st.nextToken());
   st.nextToken();// name
   System.out.println(" " +st.nextToken());
   st.nextToken();// level
   System.out.println(" " +st.nextToken());
   st.nextToken();// class
   System.out.println(" " +st.nextToken());
   st.nextToken();// historyType
   System.out.println(" " +st.nextToken());
  }
 }
}


여기에서 FileRead 함수는 아래와 같이 구현하였다.

public static String FileRead(String path)
{
 String lines = null;
 try {
  ////////////////////////////////////////////////////////////////
  BufferedReader in = new BufferedReader(new FileReader(path));
  String line =null;
  while((line = in.readLine()) != null){
   lines += (line + "\n"); 
  }
  ////////////////////////////////////////////////////////////////
 } catch (IOException e) {
  return null;
 }
 return lines;
}


위 함수의 결과는 아래와 같다.


 30559319
 donartsA
 70
 barbarian
 hero
 30559318
 donartsB
 70
 witch-doctor
 hero
 30559317
 donartsC
 70
 demon-hunter
 hero
 33559916
 donarts
 70
이하 생략...







2014년 10월 5일 일요일

안드로이드-분노의배(소스)




2011년에 문서만 작성하다가 그만 소스를 안올리고 말았네요.
지금이라도 정리하는 차원에서 소스 올립니다.

완성모습


소스

만약 빌드시 오류가 발생한다면, lib order 설정 항목을 참고하시기 바랍니다.

그리고 해당 소스는 2d엔진을 사용하는데 emulator에서는 실행이 되지 않습니다. 반듯이 안드로이드기기를 연결해서 실행하도록 하세요. 엔진에 대한 자세한 설명은 www.andengine.org 여기를 참고 하세요.






디아블로 전정실 안드로이드앱으로 html 파싱해서 보자(LIST로 뼈대 만들기)


들어가기에 앞서

날씨가 쌀쌀해 진다. 쿨럭
안드로이드 앱으로 프로필이 나오면 장비 정보가 나오는 앱을 만들어 보자.

계획

전체적인 동작 구성은 다음과 같이 종이에 그려보았다.


배틀 태그를 미리 넣어 놓으면 개인 프로필 정보를 읽어오도록 구현해 보자.
계획을 세웠으면, 인터넷에서 소스 검색해서 이것 저것 붙여서 만들면 된다. 파싱하는 부분만 빼고 만들어 보자.

안드로이드에서 리스트 구현

이미지도 나와야 함으로 아래 코드 참고하면 되겠다.
http://www.javacodegeeks.com/2012/10/android-listview-example-with-image-and.html
ProfileListActivity 만들기
각각의 변수를 적당한 이름으로 변경해 보자
ListViewImagesActivity -> ProfileListActivity
ItemDetails -> BattleTagItem
뚝딱뚝딱 ... 붙여넣기 붙여넣기 ...

생각해보니 배틀 태그에는 이미지가 필요없네요...
해당 예제로 3개 activity를 모두 구현합니다. 2개는 이미지가 필요하고 한개는 이미지가 필요 없습니다. 버튼을 눌러주면 다른 activity를 띄워주는건 intent로 합니다.

다른 Activity 호출 하는 방법
Intent intent = new Intent(this, nextActivity.class);
startActivity(intent);

다른 Activity를 호출할때 인자 전달하는 방법
보내는 곳에서: putExtra 사용합니다.
intent.putExtra("param1", tv.getText().toString());
받는 곳에서: getStringExtra 와 같은것으로 사용합니다. string외에도 다른 형태도 전달 가능합니다. intent를 참조하시기 바랍니다.
Intent intent = getIntent(); // 값을 받아온다.
String s = intent.getStringExtra("param1");     

다음과 같이 완성 되었다.

여기까지 소스 다운로드

2014년 10월 3일 금요일

디아블로 전정실 URL

인터넷 페이지에는 주소가 존재한다.
하지만 그런 페이지 주소가 보이지 않는 경우도 보일때가 있다.

디아블로3 전투정보실을 참고해보자

금일 기준 야만용사 일반 대균열 랭크 1위 달리고 있는 분의 주소이다.

http://kr.battle.net/d3/ko/profile/Nokia-3756/ <= 마지막에 반듯이 "/"를 넣도록 한다.

http://kr.battle.net/d3/ko/profile/배틀 태그 ID-배틀 태그 숫자/

그러면 아래와 같이 나온다. ~~ 대단하군.

웹에서 보려면 여기까지만 하면된다.



마우스를 어깨 방어구위쪽에 가져가면 능력치가 나오게 된다.
하지만 해당 정보를 가져오는 URL은 어디에 있는걸까?

해당 URL을 언제 사용할까? APP을 만들거나 할때 정보를 유용하게 사용할 수 있다.



크롬 브라우저 F12번키를 누른 후 Network 항목을 선택후 마우스를 아이템에 올려보면 아래와 같은 화면을 볼 수 있다.

여기에서 html 소스를 살펴보도록 하자.

html 소스를 얻는 방법은 IE 에서 파일 > 다른이름으로 저장해도 되고 크롬브라우저에서 F12 소스 탭을 이용해도 된다.

소스에서 data-d3tooltip으로 검색하면 뒤쪽에 있는 주소가 url이 되며 Network 항목에 있는 값을 참고하여 url을 구성해보면 아래와 같은 형식이 된다.

http://kr.battle.net/d3/ko/tooltip/item/CkUIwPmtyAISBwgEFY9EW9Idco4dIR0BYC2xHWpJaeodmwYAyx04neQEHYGBxsQwiwI4jANAAFASWARgjAOAAUa1AewLY0QY4e2l2gU

브라우저로 요청하였더니 아래와 같은 형식으로 나왔다.
이걸 개인 app을 이용해서 파싱을 하는 app을 만들면 스마트폰에서도 능력치를 볼 수 있는 app을 만들 수 있을것이다.

This XML file does not appear to have any style information associated with it. The document tree is shown below.
<div class="d3-tooltip d3-tooltip-item">

<div class="tooltip-head tooltip-head-orange">

<h3 class="d3-color-orange">마력 깃든 호의</h3>
</div>

<div class="tooltip-body effect-bg effect-bg-armor effect-bg-armor-square">

<span class="d3-icon d3-icon-item d3-icon-item-large d3-icon-item-orange">

<span class="icon-item-gradient">

<span class="icon-item-inner icon-item-square" style="background-image: url(http://media.blizzard.com/d3/icons/items/large/x1_followeritem_templar_legendary_01_demonhunter_male.png);"></span>
</span>
</span>

<div class="d3-item-properties">

<ul class="item-type-right">

<li class="item-slot">추종자</li>

<li class="item-class-specific d3-color-white">기사단원</li>
</ul>

<ul class="item-type">

<li>

<span class="d3-color-orange">전설 기사단원 유물</span>
</li>
</ul>

<ul class="item-armor-weapon item-armor-armor">

<li class="big">
<p>
<span class="value">566</span>
</p>
</li>

<li>방어도</li>
</ul>

<div class="item-before-effects"/>

<ul class="item-effects">

<p class="item-property-category">주요 속성</p>

<li class="d3-color-blue d3-item-property-default">
<p>

<span class="value">+690</span>
</p>
</li>

<li class="d3-color-blue d3-item-property-default">
<p>
활력
<span class="value">+726</span>
</p>
</li>

<li class="d3-color-blue d3-item-property-default">
<p>
방어도
<span class="value">+566</span>
</p>
</li>

<li class="d3-color-blue d3-item-property-default">
<p>
적중 시 생명력
<span class="value">+13049</span>
</p>
</li>

<p class="item-property-category">보조 속성</p>

<li class="d3-color-blue d3-item-property-default">
<p>
화염 저항
<span class="value">+</span>
<span class="value">147</span>
</p>
</li>

<li class="d3-color-blue d3-item-property-utility">
<p>
적을 처치하고 얻는 경험치
<span class="value">+185</span>
</p>
</li>

<li class="d3-color-orange d3-item-property-default">
<p>추종자에게 장비: 추종자가 죽지 않음</p>
</li>
</ul>

<ul class="item-extras">

<li class="item-reqlevel">
<span class="d3-color-gold">요구 레벨:</span>
<span class="value">70</span>
</li>

<li>계정 귀속</li>
</ul>

<span class="item-unique-equipped">고유 장착</span>

<span class="clear">
<!-- -->
</span>
</div>
</div>

<div class="tooltip-extension ">

<div class="flavor">절세 미녀가 애정의 증표로 준 속이 비치는 목도리입니다.</div>
</div>
</div>


2011년 11월 16일 수요일

안드로이드-분노의배(스크립트 엔진)

이것만 만들고 버리는게 아니라 코드의 재사용을 위해 스크립드 엔진을 만들었다.

스크립트의 규칙에 대해 정리하면 아래와 같다.

스크립트에는 4개의 정보로 나뉜다.

pathInfo : 객체 이동 대한 정보 ( 움직이거나 크기를 변경...)

actionInfo : 사용자나 이벤트에 의한 명령을 수행하기 위한 명령의 집합


timerInfo : 시간의 순서대로 처리 해야 하는 명령의 집합 
imageInfo : 이미지를 로드하여 텍스처에 저장하기 위한 정보

각 대 분류는 xml 처럼 <tag></tag> 로 묶인다.

pathInfo
pathInfo에서 사용할수 있는 명령은 다음과 같다.

imgAngle(angle) : 이미지를 각도만큼 회전시킨다.
scale(%) : 크기를 변경한다.(%)
goAngle(angle,speed) : 정해진 각도를 속도로 움직이게 한다.
dSleep(pixel) : 거리만큼 이동될때까지 쉰다.
sleep(time) : 시간만큼 쉰다.
alpha(%) : alpha 값을 변경한다.
destroy() : 현재 이 객체를 소멸 시킨다.
moveTo(x,y) : 정해진 좌표로 순간 이동한다.
rgb(%,%,%) : r,g,b 의 %를 변화한다.
goto(index) : 위치를 이동하는데 인자는 상대적인 값이 된다.
visible(0 or 1) : 현재의 객체를 보일지 안보일지를 결정한다.
action(number) : 액션을 수행한다.


startProject(시작할파일) : 다음 스크립트를 실행한다.

예)

<pathInfo>
<1>
rgb(100,100,100)
scale(100)
alpha(0)
sleep(100)
alpha(10)
sleep(100)
alpha(20)
sleep(100)
alpha(30)
sleep(100)
alpha(40)
sleep(100)
alpha(50)
sleep(100)
alpha(60)
sleep(100)
alpha(70)
sleep(100)
alpha(70)
sleep(100)
alpha(80)
sleep(100)
alpha(90)
sleep(100)
alpha(100)
sleep(2000)
action(1)
</1>
<2>
alpha(100)
sleep(100)
alpha(90)
sleep(100)
alpha(80)
sleep(100)
alpha(70)
sleep(100)
alpha(60)
sleep(100)
alpha(50)
sleep(100)
alpha(40)
sleep(100)
alpha(30)
sleep(100)
alpha(20)
sleep(100)
alpha(10)
sleep(100)
alpha(0)
action(2)
</2>
</pathInfo>


actionInfo
터치 또는 이벤트가 일어나면 동작을 한꺼번에 한다.
timerInfo 와 동일한 동작가능 단,sleep,goto 동작만 안됨

예)
<actionInfo>
<1>
setPathInfo(99,2)
</1>
<2>
startProject(mainmenu.txt)
</2>
</actionInfo>


timerInfo
시간에 의해 발생되는 기능들
trackAction(0 or 1) : 사용자에 의한 액션이 일어나지 않도록 한다.
sleep(time) : 정해진 시간 동안 쉰다.
goto(index) : 스크립트의 인덱스를 이동한다.(상대값)
setVisibleLayer(layer,on/off) : layer를 on/off (0 or 1) 한다. (보이거나 보이지 않게)
setPathInfo(type,path number) : type객체의 path number를 변경한다.
setActionInfo(type,action number) : type객체의 action number를 변경한다.
setBackColor(r%,g%,b%) : 배경의 r,g,b %를 변경한다.
cImg(  x,  y,img,path,action,type,hit,layer,    hp,ref1,ref2) : 이미지 객체를 생성한다.
cBox(  x,  y,  0,path,action,type,hit,layer,    hp,ref1,ref2,width,height) : box 객체를 생성한다.
cTxt(  x,  y,  0,path,action,type,hit,layer,    hp,ref1,ref2,length,fontref,text) : text 객체를 생성한다.

예)
<timerInfo>
setBackColor(100,100,100)
trackAction(0)
cTxt(350,240,  0,   1,     0,  99,  1,    2,   100,   0,   0,    21,      5,Powered by AndEngine)
cImg(570,240,  1,   1,     0,  99,  1,    2,   100,   0,   0)
</timerInfo>


imageInfo
이미지의 정보를 담고있다.
id는 1 부터 증가하여야 하며 빠지면 안된다.
repeat count:-1 인 경우 무한,
texture좌표:texturex,texturey
imageInfo(id,image name,frame rate,repeat count ,texturex,texturey,가로개수,세로개수)

예)
<imageInfo>
//64*64
imageInfo(1 ,gfx/badge.png ,0 ,0 , 0, 0,     1,1)
</imageInfo>



스크립트 엔진의 동작 이해

동작은 timerInfo 부터 시작된다.

setBackColor(100,100,100) : 배경색을 흰색으로 변경한다.
trackAction(0) : 사용자에 의한 trigger를 끈다.
cTxt(350,240,  0,   1,     0,  99,  1,    2,   100,   0,   0,    21,      5,Powered by AndEngine)
: 텍스트를 생성해서 특정좌표에 표시하는데 path,action,type가 (1,0,99) 가 된다.
cImg(570,240,  1,   1,     0,  99,  1,    2,   100,   0,   0)
: 이미지를 생성해서 좌표에 표시하는데 이미지 1번(gfx/badge.png)을 이용하고 path,action,type가 (1,0,99) 가 된다.
이미지와 텍스트 모두 pathInfo <1>을 사용하게 된다.

<1>
rgb(100,100,100)
scale(100)
alpha(0)
sleep(100)
alpha(10)
sleep(100)
alpha(20)
sleep(100)
alpha(30)
sleep(100)
alpha(40)
sleep(100)
alpha(50)
sleep(100)
alpha(60)
sleep(100)
alpha(70)
sleep(100)
alpha(70)
sleep(100)
alpha(80)
sleep(100)
alpha(90)
sleep(100)
alpha(100)
sleep(2000)
action(1)
</1>

pathInfo <1>의 동작은 점차 alpha 값을 증가시키는 fade in 동작이며, 마지막에 action(1)을 둠으로서 actionInfo <1>을 수행한다.

<1>
setPathInfo(99,2)
</1>
actionInfo <1>은 type이 99인 객체의 pathInfo를 2로 변경한다.
그러면 이미지와 text의 type이 99이므로 pathInfo가 2로 변경되어 다음 동작을 수행한다.

<2>
alpha(100)
sleep(100)
alpha(90)
sleep(100)
alpha(80)
sleep(100)
alpha(70)
sleep(100)
alpha(60)
sleep(100)
alpha(50)
sleep(100)
alpha(40)
sleep(100)
alpha(30)
sleep(100)
alpha(20)
sleep(100)
alpha(10)
sleep(100)
alpha(0)
action(2)
</2>
이 동작은 fade out 동작이며 마지막에 action <2>를 수행한다.

<2>
startProject(mainmenu.txt)
</2>
다른 프로젝트를 로드하게 되므로서 이 페이지의 동작은 끝이 난다.



끝마치며

timerInfo는 전체적인 흐름을 관리하는 도구이며, 생성된 객체(텍스트,이미지,박스)들의 동작은 각각의 pathInfo로 움직이게 된다.
사용자의 터치나, 어떤 이벤트가 일어나게 되면 적절히 actionInfo를 이용하여 변경이 되도록 작업을 해야한다.