Android Studio에서 아무 설정도 하지 않고 APK를 생성하면 release, debug 이런식으로 생성이 되기 때문에 생성후에 다시 이름을 바꿔줘야한다. 


이것은 매우 귀찮은 일이다..


그러므로 build.gradle에서 설정 해주도록 하자.







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
apply plugin: 'com.android.application'
apply plugin: 'com.google.gms.google-services'
 
android {
    compileSdkVersion 23
    buildToolsVersion '23.0.1'
//    lintOptions {
//        checkReleaseBuilds false
//    }
    dexOptions {
        javaMaxHeapSize '2g'
    }
    defaultConfig {
        applicationId 'com.fullstack'
        minSdkVersion 14
        targetSdkVersion 23
        versionCode 113
        versionName "1.1.3"
        multiDexEnabled true
    }
 
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
        debug {
            minifyEnabled false
        }
 
        applicationVariants.all { variant ->
            variant.outputs.each { output ->
                def apk = output.outputFile;
                def newName = apk.name.replace(".apk""_" + defaultConfig.versionCode + ".apk");
                output.outputFile = new File(apk.parentFile, newName);
            }
        }
 
    }
}
 
 
dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.android.support:appcompat-v7:23.1.1'
    compile 'com.android.support:recyclerview-v7:23.1.1'
}
cs



코드는 뭐 이런식으로 되어있을 테고.. 봐야할 부분은 다음이다. buildTypes안에 넣어주면 된다.


1
2
3
4
5
6
7
8
applicationVariants.all { variant ->
    variant.outputs.each { output ->
        def apk = output.outputFile;
        def newName = apk.name.replace(".apk""_" + defaultConfig.versionCode + ".apk");
        output.outputFile = new File(apk.parentFile, newName);
    }
}
 
cs

apk.name.replace에서 원하는 부분으로 바꿔주도록 한다. 위에 설정은 .apk 부분을 버전 코드를 넣어서 바꾼 부분이다.

이렇게 하면 apk가 생성되었을 때 버전 코드가 같이 붙어나오므로 보기도 편하고 관리도 쉽다. 






계속 업데이트중입니다.


플랫폼 버전API 레벨버전 코드참고
Android 10.029Q플랫폼 하이라이트
Android 928P플랫폼 하이라이트
Android 8.127O_MR1플랫폼 하이라이트
Android 8.026O플랫폼 하이라이트
Android 7.1.1
Android 7.1
25N_MR1플랫폼 하이라이트
Android 7.024N플랫폼 하이라이트
Android 6.023M플랫폼 하이라이트
Android 5.122LOLLIPOP_MR1플랫폼 하이라이트
Android 5.021LOLLIPOP
Android 4.4W20KITKAT_WATCH웨어러블 기기 전용 KitKat
Android 4.419KITKAT플랫폼 하이라이트
Android 4.318JELLY_BEAN_MR2플랫폼 하이라이트
Android 4.2, 4.2.217JELLY_BEAN_MR1플랫폼 하이라이트
Android 4.1, 4.1.116JELLY_BEAN플랫폼 하이라이트
Android 4.0.3, 4.0.415ICE_CREAM_SANDWICH_MR1플랫폼 하이라이트
Android 4.0, 4.0.1, 4.0.214ICE_CREAM_SANDWICH
Android 3.213HONEYCOMB_MR2
Android 3.1.x12HONEYCOMB_MR1플랫폼 하이라이트
Android 3.0.x11HONEYCOMB플랫폼 하이라이트
Android 2.3.4
Android 2.3.3
10GINGERBREAD_MR1플랫폼 하이라이트
Android 2.3.2
Android 2.3.1
Android 2.3
9GINGERBREAD
Android 2.2.x8FROYO플랫폼 하이라이트
Android 2.1.x7ECLAIR_MR1플랫폼 하이라이트
Android 2.0.16ECLAIR_0_1
Android 2.05ECLAIR
Android 1.64DONUT플랫폼 하이라이트
Android 1.53CUPCAKE플랫폼 하이라이트
Android 1.12BASE_1_1
Android 1.01BASE



출처 : http://developer.android.com/intl/ko/guide/topics/manifest/uses-sdk-element.html





HttpURLConnection을 사용하여 Multipart Upload를 수행하고 그 과정을 ProgressBar로 보여주기를 하는 코드이다.


다음 순서대로 작성하고 적용하면 끝.





1. 인터페이스 생성



1
2
3
4
public interface ProgressListener { 
   void onProgressUpdate(int progress);
}
 
cs




2. MultipartUpload 클래스



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
 
public class MultipartUpload {
 
    private final String boundary;
    private final String tail;
    private static final String LINE_END = "\r\n";
    private static final String TWOHYPEN = "--";
    private HttpURLConnection httpConn;
    private String charset;
    private PrintWriter writer;
    private OutputStream outputStream;
    private static final String TAG = "MultipartUtility";
    int maxBufferSize = 1024;
    private ProgressListener progressListener;
    private long startTime;
 
    public MultipartUpload(String requestURL, String charset) throws IOException {
        this.charset = charset;
 
        boundary = "===" + System.currentTimeMillis() + "===";
        tail = LINE_END + TWOHYPEN + boundary + TWOHYPEN + LINE_END;
        URL url = new URL(requestURL);
        httpConn = (HttpURLConnection) url.openConnection();        
        httpConn.setDoOutput(true); 
        httpConn.setDoInput(true);
        httpConn.setRequestProperty("Content-Type""multipart/form-data; boundary=" + boundary);
    }
 
    public void setProgressListener(ProgressListener progressListener) {
        this.progressListener = progressListener;
    }
 
    public JSONObject upload(HashMap<StringString> params, HashMap<StringString> files) throws IOException {
        String paramsPart = "";
        String fileHeader = "";
        String filePart = "";
        long fileLength = 0;
        startTime = System.currentTimeMillis();
 
        ArrayList<String> paramHeaders = new ArrayList<>();
        for (Map.Entry<StringString> entry : params.entrySet()) {
 
            String param = TWOHYPEN + boundary + LINE_END
                    + "Content-Disposition: form-data; name=\"" + entry.getKey() + "\"" + LINE_END
                    + "Content-Type: text/plain; charset=" + charset + LINE_END
                    + LINE_END
                    + entry.getValue() + LINE_END;
            paramsPart += param;
            paramHeaders.add(param);
        }
 
        ArrayList<File> filesAL = new ArrayList<>();
        ArrayList<String> fileHeaders = new ArrayList<>();
 
        for (Map.Entry<StringString> entry : files.entrySet()) {
            
            File file = new File(entry.getValue());
            fileHeader = TWOHYPEN + boundary + LINE_END
                    + "Content-Disposition: form-data; name=\"" + entry.getKey() + "\"; filename=\"" + file.getName() + "\"" + LINE_END
                    + "Content-Type: " + URLConnection.guessContentTypeFromName(file.getAbsolutePath()) + LINE_END
                    + "Content-Transfer-Encoding: binary" + LINE_END
                    + LINE_END;
            fileLength += file.length() + LINE_END.getBytes(charset).length;
            filePart += fileHeader;
 
            fileHeaders.add(fileHeader);
            filesAL.add(file);
        }
        String partData = paramsPart + filePart;
 
        long requestLength = partData.getBytes(charset).length + fileLength + tail.getBytes(charset).length;
        httpConn.setRequestProperty("Content-length""" + requestLength);
        httpConn.setFixedLengthStreamingMode((int) requestLength);
        httpConn.connect();
 
        outputStream = new BufferedOutputStream(httpConn.getOutputStream());
        writer = new PrintWriter(new OutputStreamWriter(outputStream, charset), true);
 
        for (int i = 0; i < paramHeaders.size(); i++) {
            writer.append(paramHeaders.get(i));
            writer.flush();
        }
 
        int totalRead = 0;
        int bytesRead;
        byte buf[] = new byte[maxBufferSize];
        for (int i = 0; i < filesAL.size(); i++) {
            writer.append(fileHeaders.get(i));
            writer.flush();
            BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream(filesAL.get(i)));
            while ((bytesRead = bufferedInputStream.read(buf)) != -1) {
 
                outputStream.write(buf, 0, bytesRead);
                writer.flush();
                totalRead += bytesRead;
                if (progressListener != null) {
                    float progress = (totalRead / (float) requestLength) * 100;
                    progressListener.onProgressUpdate((int) progress);
                }
            }
            outputStream.write(LINE_END.getBytes());
            outputStream.flush();
            bufferedInputStream.close();
        }
        writer.append(tail);
        writer.flush();
        writer.close();
 
        JSONObject jObj = null;
        StringBuilder sb = new StringBuilder();
        // checks server's status code first
        int status = httpConn.getResponseCode();
        if (status == HttpURLConnection.HTTP_OK) {
            BufferedReader reader = new BufferedReader(new InputStreamReader(httpConn.getInputStream(), "UTF-8"), 8);
            String line = null;
            while ((line = reader.readLine()) != null) {
                sb.append(line).append("\n");
            }
            reader.close();
            httpConn.disconnect();            
        } else {
            throw new IOException("Server returned non-OK status: " + status + " " + httpConn.getResponseMessage());
        }
        try {
            jObj = new JSONObject(sb.toString());
        } catch (JSONException | NullPointerException e) {
            e.printStackTrace();
        }
        return jObj;
 
    }
 
}
cs





3. 사용법



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
class UploadAsync extends AsyncTask<Object, Integer, JSONObject> implements ProgressListener {
 
    private ProgressDialog progressDialog;
    private Context mContext;    
    private HashMap<StringString> param;
    private HashMap<StringString> files;
 
    public UploadAsync(Context context, HashMap<StringString> param, HashMap<StringString> files) {
        mContext = context;
        this.param = param;
        this.files = files;
    }
 
    @Override
    protected void onPreExecute() {
        super.onPreExecute();
        startTime = System.currentTimeMillis();
        progressDialog = new ProgressDialog(mContext);
        progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
        progressDialog.setMessage(mContext.getString(R.string.wait));
        progressDialog.setMax(100);
        progressDialog.setCancelable(false);
        progressDialog.show();
    }
 
    @Override
    protected JSONObject doInBackground(Object... params) {
        JSONObject json = null;
        try {
        
            String url = "http://요청할 URL";
            MultipartUpload multipartUpload = new MultipartUpload(url, "UTF-8");
            multipartUpload.setProgressListener(this);
            json = multipartUpload.upload(param, files);            
 
        } catch (IOException e) {
            e.printStackTrace();
        }
        return json;
 
    }
 
    @Override
    protected void onProgressUpdate(Integer... values) {
        super.onProgressUpdate(values);
        if (progressDialog != null && progressDialog.isShowing()) {
            if (values[1== 1) {
                progressDialog.setProgress(values[0]);
            } else {
                progressDialog.setProgress(values[0]);
            }
        }
    }
 
    @Override
    protected void onPostExecute(JSONObject result) {
        super.onPostExecute(result);
        if (progressDialog.isShowing()) {
            progressDialog.dismiss();
        }
 
        if (result != null) {
            try {
                if (result.getInt("success"== 1) {                            
                    Toast.makeText(mContext, R.string.success, Toast.LENGTH_SHORT).show();
                }
            } catch (JSONException e) {
                e.printStackTrace();
            }
        } else {
            Toast.makeText(mContext, R.string.conntection_error, Toast.LENGTH_SHORT).show();
        }
    }
 
    @Override
    public void onProgressUpdate(int progress) {
        publishProgress(progress, 0);
    }
}
 
cs


  네트워크 작업이므로 반드시 스레드 안에서 실행해야 한다.


  생성자로 접속할 url과 캐릭터셋을 넣고,  프로그레스를 받을 인터페이스 리스너를 등록 한 후 파라미터 파트와 파일 파트를 HashMap으로 구성하여 인자로 넘겨준다. 


  한가지 문제점은 실제로 접속하여 파일을 전송하는 과정이 progress되지 않는다는 점이다. MultipartUpload 클래스에 보면 outputstream에 쓰는 과정을 progress로 보여주는데 사실 이것이 파일이 전송됨을 의미하지는 않는다는 점이다. 단지 버퍼에서 읽는 부분일뿐.. 실제 예제를 만들어 progress가 100%가 후에 전송을 시작하여 마치 앱이 멈춘것 처럼 동작한다. 파일 크기가 작으면 전송이 빨리되어 약간의 딜레이가 생기겠지만 파일이 큰 경우 오류난것 처럼 보인다. 


  버퍼에 쓴다음 바로 flush를 해줘도 똑같다. setFixedLenghStreamingMode를 해주면 된다고는 하는데 안된다! 이거 실제 전송되는 부분 아시는분은 알려주시면 감사하겠습니다. 







안드로이드 RecyclerView에서 drag&drop과 swipe-to-dismiss를 구현한 예제는 많지만 View.OnDragListener를 이용한 경우가 많다. 이것은 예전 버전을 사용한 경우이고

새로운 API를 사용하거나 GestureDetector나 onIterceptTouchEvent를 이용하여 복잡아게 구현한 경우가 많다. 이번 포스트 에서는 Android Support Library를 이용하여

간단히 drag&drop과 swipe-to-dismiss를 구현한 방법을 알아보고자 한다.





ItemTouchHelper

ItemTouchHelper는 drag&drop과 swipe-to-dismiss를 구현하기에 매우 적합한 class이다. RecyclerView.ItemDecoration의 subclass이기 때문에 대부분의 LaoutManager와 Adapter에서 

쉽게 사용이 가능하다.


RecyclerView를 사용하려면 다음과 같이 build.gradle에 dependency를 추가해야 한다.


compile 'com.android.support:recyclerview-v7:22.2.0'





ItemTouchHelper와 ItemTouchHelper.Callback 사용


ItemTouchHelper를 사용하기 위해서는 ItemTouchHelper.Callback을 생성해야 한다.

이것은 "move"와 "swipe" 이벤트를 받을 수 있는 interface이다. 또한 기본 animation과 view의 선택된 상태를 제어할 수 있다.

기본적인 API로 이미 구현이 되어있지만 학습하는 과정이므로 새로운 SimpleCallback class를 만들어 보도록 한다.


callback class는 반드시 다음 3가지 메소드를 override해야 한다.


getMovementFlags(RecyclerView, ViewHolder)

onMove(RecyclerView, ViewHolder, ViewHolder)

onSwiped(ViewHolder, int)


다음 2가지도 사용한다.


isLongPressDragEnabled()

isItemViewSwipeEnabled()



이제 각각 메소드에 대해 하나씩 살펴보자.


1
2
3
4
5
6
7
@Override
public int getMovementFlags(RecyclerView recyclerView, 
        RecyclerView.ViewHolder viewHolder) {
    int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN;
    int swipeFlags = ItemTouchHelper.START | ItemTouchHelper.END;
    return makeMovementFlags(dragFlags, swipeFlags);
}
cs


ItemTouchHelper는 이벤트의 방향(direction)을 쉽게 찾을 수 있게 한다. 어떠한 방향으로 drag 혹은 swipe 되었는지 알려면 getMovementFlags() 를 반드시 override 해야 한다.

return 값으로 ItemTouchHelper.makeMovementFlags(int, int)를 사용하여 dragging 과 swiping를 양쪽 방향으로 사용할 수 있다.




1
2
3
4
5
6
public interface ItemTouchHelperAdapter {
 
    void onItemMove(int fromPosition, int toPosition);
 
    void onItemDismiss(int position);
}
cs


onMove() 와 onSwiped()는 데이터 변화에 따라 호출된다. 이러한 interface를 구성하여 각각 class를 연결시킨다.




1
2
3
4
@Override
public boolean isLongPressDragEnabled() {
    return true;
}
cs

ItemTouchHelper drag 없이 swipe 용도로만 사용가능하다(반대도 가능). RecyclerView item 에서 long press를 통해 drag 시점을 알기 위해서는 isLongPressDragEnabled()에서 true를 return 해야만 한다.

대신에 ItemTouchHelper.startDrag(RecyclerView.ViewHolder)를 사용할 수도 있지만 추후에 다루도록 한다.



1
2
3
4
@Override
public boolean isItemViewSwipeEnabled() {
    return true;
}
cs

View 안에서 touch 이벤트로 부터 swiping을 입력받게 하려면 isItemViewSwipeEnabled()에서 위와 마찬가지로 true를 반환해야 한다.

ItemTouchHelper.startSwipe(RecyclerView.ViewHolder)로 drag 이벤드를 시작할 수 있다.




위의 interface를 adapter에 적용시키면 다음과 같다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class RecyclerListAdapter extends 
        RecyclerView.Adapter<ItemViewHolder> 
        implements ItemTouchHelperAdapter {
...
 
@Override
public void onItemDismiss(int position) {
    mItems.remove(position);
    notifyItemRemoved(position);
}
 
@Override
public boolean onItemMove(int fromPosition, int toPosition) {
    if (fromPosition < toPosition) {
        for (int i = fromPosition; i < toPosition; i++) {
            Collections.swap(mItems, i, i + 1);
        }
    } else {
        for (int i = fromPosition; i > toPosition; i--) {
            Collections.swap(mItems, i, i - 1);
        }
    }
    notifyItemMoved(fromPosition, toPosition);
    return true;
}
cs


adapter 안에서 데이터의 변화는 notifyItemRemoved()와 notifyItemMoved()를 통하여 알 수 있다. 한가지 중요한 것은  데이터의 변화는 drag를 시작하고 drop 될 때 변화 하는것이 아니라 item이 움직일 때 마다 변화한다는 것이다.

그러므로 item이 움직일때 마다 adapter안에서 데이터의 변화가 일어나게 된다는 것을 명심하고 있어야 한다. 



다음으로 SimpleItemTouchHelperCallback에 onMove()와 onSwiped()를 orverride 해 줘야 한다. 생성자에 다음과 같이 adapter field를 추가한다.


1
2
3
4
5
6
private final ItemTouchHelperAdapter mAdapter;
 
public SimpleItemTouchHelperCallback(
        ItemTouchHelperAdapter adapter) {
    mAdapter = adapter;
}
cs



다음으로 onMove()와 onSwiped()를 추가한다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Override
public boolean onMove(RecyclerView recyclerView, 
        RecyclerView.ViewHolder viewHolder, 
        RecyclerView.ViewHolder target) {
    mAdapter.onItemMove(viewHolder.getAdapterPosition(), 
            target.getAdapterPosition());
    return true;
}
 
@Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, 
        int direction) {
    mAdapter.onItemDismiss(viewHolder.getAdapterPosition());
}
cs



Callback class의 최종 코드는 다음과 같다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public class SimpleItemTouchHelperCallback extends ItemTouchHelper.Callback {
 
    private final ItemTouchHelperAdapter mAdapter;
 
    public SimpleItemTouchHelperCallback(ItemTouchHelperAdapter adapter) {
        mAdapter = adapter;
    }
    
    @Override
    public boolean isLongPressDragEnabled() {
        return true;
    }
 
    @Override
    public boolean isItemViewSwipeEnabled() {
        return true;
    }
 
    @Override
    public int getMovementFlags(RecyclerView recyclerView, ViewHolder viewHolder) {
        int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN;
        int swipeFlags = ItemTouchHelper.START | ItemTouchHelper.END;
        return makeMovementFlags(dragFlags, swipeFlags);
    }
 
    @Override
    public boolean onMove(RecyclerView recyclerView, ViewHolder viewHolder, 
            ViewHolder target) {
        mAdapter.onItemMove(viewHolder.getAdapterPosition(), target.getAdapterPosition());
        return true;
    }
 
    @Override
    public void onSwiped(ViewHolder viewHolder, int direction) {
        mAdapter.onItemDismiss(viewHolder.getAdapterPosition());
    }
 
}
cs



이렇게 생성된 class들은 Activity나 Fragment에서 다음과 같이 사용한다. 


1
2
3
ItemTouchHelper.Callback callback =  new SimpleItemTouchHelperCallback(adapter);
ItemTouchHelper touchHelper = new ItemTouchHelper(callback);
touchHelper.attachToRecyclerView(recyclerView);
cs










1. 언제쓰이나?


· Service : UI없이 실행될 수 있지만 매우 길지 않아야 한다. 만약 오래걸리는 작업을 Service에서 실행하고자 한다면 Service 안에서 스레드를 사용해야 한다.


· IntentService : 오래걸리지만 메인스레드와 관련이 없는 작업을할 때 주로 이용한다. 만약 메인 스레드와 관련된 작업을 해야 한다면 메인스레드 Handler나 Boradcast intent를 이용해야 한다.




2. 어떻게 실행시키나?


· Service : startService() 메소드에 의해 실행된다.


· IntentService : Intent사용에 의해 실행된다. 새로운 스레드가 생성되며 onHandleIntent()가 불린다.





3. 불리는 위치


· ServiceIntentService 모두 아무 스레드에서 생성되고 , 액티비티 뿐만 아니라 다른곳에서도 실행가능하다.





4. 실행중인 위치


· Service : 백드라운드에서 동작하지만 메인스레드에 포함된다.


· IntentService : 새로운 스레드에서 동작한다.





5. 어떻게 멈추나


· Service : 순전히 사용자의 몫이다. stopSelf()나 stopService()에 의해 동작이 멈춘다. Service Binding의 경우 필요없음.


· IntentService : onHandleIntent() 내의 모든 동작이 수행되면 멈춘다. 멈추기 위한 다른 메소드 호출이 불필요한다.





6. 단점?


· Service : 메인스레드에 포함되므로 무거운 작업일때 메인스레드에 영향을주어 느려지거나 할 수 있다.


· IntentService : 병렬적으로 수행될 수 없으므로 연속적인 Intent 호출에 관해서 순차적으로 처리된다.





 

# install openjdk

sudo apt-get install openjdk-7-jdk


# download android sdk

wget http://dl.google.com/android/android-sdk_r24.2-linux.tgz


tar -xvf android-sdk_r24.2-linux.tgz

cd android-sdk-linux/tools


# install all sdk packages

./android update sdk --no-ui


# set path

vi ~/.zshrc << EOT


export PATH=${PATH}:$HOME/sdk/android-sdk-linux/platform-tools:$HOME/sdk/android-sdk-linux/tools:$HOME/sdk/android-sdk-linux/build-tools/22.0.1/


EOT


source ~/.zshrc


# adb

sudo apt-get install libc6:i386 libstdc++6:i386

# aapt

sudo apt-get install zlib1g:i386

+ Recent posts