[Android / Glide] GlideApp 생성절차

아래 방식은 4.x.x 버전에서 유효하다. 3.x.x 버전의 Glide에서는 Manifest.xml에 meta-data를 추가하는 방식으로 진행된다.
참고) 3.x.x는 아래 링크가 더 도움이 될 것 같다. 3.x.x에서는 AppGlideModule이 아닌 GlideModule을 상속받는 클래스를 만들어야 한다.
https://medium.com/@PaulinaSadowska/adding-headers-to-image-request-in-glide-dc9640ca9b12

1. app/build.gradle에 dependencies 추가

1
2
3
4
5
6
7
8
9
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-kapt'
...
dependencies {
...
    implementation 'com.github.bumptech.glide:glide:4.11.0'
    implementation "com.github.bumptech.glide:okhttp3-integration:4.11.0"
    kapt 'com.github.bumptech.glide:compiler:4.11.0'
}

코틀린을 쓰고 있다면, glide:compiler dependencies를 kapt로 추가해줘야한다.
이를 위해 선행작업으로 gradle.build의 최상단에 kotlin-kapt 도 필요하다.

  • glide:glide는 기본적으로 Glide 라이브러리를 사용하기 위해 추가됨.
  • okhttp3-integration는 CustomGlideModule에서 사용하기 위함.
  • glide:compiler는 GeneratedAppGlideModuleImpl를 생성하기 위함.
    이후 proguard-rules.pro에서 GeneratedAppGlideModuleImpl를 keep 해주는데 이는 GlideApp을 사용하기 위해 CustomGlideModule과 같은 패키지(net.common.utils.GlideApp)에 만들어진다.(4번 절차 참조)

2. UserAgentInterceptor 생성

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import java.io.IOException;
import okhttp3.Interceptor;
import okhttp3.Request;
import okhttp3.Response;

public class UserAgentInterceptor implements Interceptor {

private Context mContext;
public UserAgentInterceptor(Context context) {
mContext = context;
}

@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request()
.newBuilder()
.header("User-Agent", getUserAgent(mContext))
.build();
return chain.proceed(request);
}
}

3. CustomGlideModule 생성

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
package net.common.utils;

import com.bumptech.glide.Glide;
import com.bumptech.glide.GlideBuilder;
import com.bumptech.glide.Registry;
import com.bumptech.glide.annotation.GlideModule;
import com.bumptech.glide.load.model.GlideUrl;
import com.bumptech.glide.module.AppGlideModule;
import com.bumptech.glide.integration.okhttp3.OkHttpUrlLoader.Factory;
import java.io.InputStream;
import okhttp3.OkHttpClient;

@GlideModule
public final class CustomGlideModule extends AppGlideModule {

@Override
public void applyOptions(@NonNull Context context,
    @NonNull GlideBuilder builder) {
// 아무 것도 수정하지 않지만 오버라이드 해줘야함
super.applyOptions(context, builder);
}

@Override
public void registerComponents(@NonNull Context context,
    @NonNull Glide glide, @NonNull Registry registry) {
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(new UserAgentInterceptor(context))
.build();
registry.replace(GlideUrl.class, InputStream.class, new Factory(client));
}
}
  • @GlideModule 어노테이션 선언 꼭 잊지 말 것.
  • 3.x.x와는 다르게 AppGlideModule을 상속받아 커스텀 클래스를 만들어준다.
  • registerComponents에서 OkHttpClient를 통해 커스텀 인터셉터를 추가해준다.

AppGlideModule과 LibraryGlideModule

AppGlideModule(을 상속받은 CustomAppGM)은 애플리케이션 단에, LibraryGlideModule(을 상속받은 ~ 생략)은 공통 라이브러리 단에 구현하면 된다.

이때 GlideModule을 커스텀할 경우, CustomAppGM의 구현은 필수적이나 CustomLibraryGM의 구현은 선택적이어서, CustomLibraryGM만 구현해선 안 된다.

그리고 공통 라이브러리에 정의한 모든 CustomLibraryGM은 CustomAppGM에서 통합적으로 추가된다.

4. 재빌드

3번까지 진행 후 재빌드(Build>Make Project) 하자.

  • app/build/generated/source/kapt/appDebug/com.bumptech.glide.GeneratedAppGlideModuleImpl
  • app/build/generated/source/kapt/appDebug/net.common.utils.GlideApp

위와 같은 로케이션에 GeneratedAppGlideModuleImplGlideApp이 생성되는 것을 확인할 수 있다. 이렇게 되면 이제 GlideApp을 사용할 준비가 된 것.

  • GlideApp은 CustomGlideModule을 정의한 패키지 내에 만들어진다.

  • 위의 두 파일이 만약 생성되지 않는다면

    • build.gradle의 kapt를 확인할 것
    • @GlideModule 어노테이션 넣었는지 확인할 것

5. app/proguard-rules.pro 편집

1
2
-keep public class * extends com.bumptech.glide.module.AppGlideModule
-keep class com.bumptech.glide.GeneratedAppGlideModuleImpl

위와 같은 keep을 추가해줘야한다.

첫번째 라인(* extends AppGlideModule)은 CustomAppGlideModule을 위해서 쓰임.

두번째 라인(GeneratedAppGlideModuleImpl)은 빨간줄로 오류표시가 날 수 있는데, 4번에서 말한 것처럼 kapt로 추가된 의존성에 의해 빌드 후 생성되는 파일이라서이다. 빌드 후 generated에 생성되는 게 GeneratedAppGM과 GlideApp인 것으로 보아 GlideApp을 사용하기 위해서 쓰이는 걸로 보인다.

(참고로 app/proguard-rules.pro 파일에서 #으로 시작되는 라인은 주석에 해당함.)

6. GlideApp 사용예

1
2
3
4
GlideApp.with(context)
.load("url")
.apply(options)
.into(imageView);

GlideApp을 통해 이미지를 로드해야지만이 CustomGlideModule에서 정의한 설정을 따른다.

기본적으로 Glide와 사용에 지장이 있을 정도로 방식이 다르지 않다.

단, GlideApp은 Glide와는 다르게 일부 RequestOptions를 통해서만 호출할 수 있었던 속성들을 다이렉트로 컨트롤할 수 있게 된다. 이는 공식문서에서 일부 예제들을 통해 비교할 수 있다.

[Android / Glide] Glide v4 적용

1. 시작하기

안드로이드 SDK 요구사항

  • 최소 SDK 버전 - Ice Cream Sandwich, 14 이상
  • 컴파일 SDK 버전 - Oreo MR1, 27 이상

권한

1
2
3
4
5
<uses-permission android:name="android.permission.INTERNET" />
// Glide가 연결 상태를 감시하고 실패한 요청을 재시작하는 것을 허용
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
// ExternalPreferredCacheDiskCacheFactory를 사용하여 Glide의 캐시를 공개 sdcard에 저장하기 위해
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

의존성

1
2
3
dependencies {
kapt 'com.github.bumptech.glide:glide:4.11.0' // 코틀린을 쓸 경우 kapt여야함.
}

2. 기본 형태

1
2
3
4
5
6
7
8
9
RequestOptions options = new RequestOptions()
.bitmapTransform(new RoundedCorners(20)) //radius
.placeholder(R.drawable.loading) // 이미지 로딩 중
.error(R.drawable.loading); // 이미지를 불러오지 못할 경우

Glide.with(context) // RequestManager 형태로 반환
.load("url"// RequestBuilder<Drawable> 형태로 반환 (이하 apply, into 동일하게 반환)
.apply(options)
.into(imageView);

ImageView의 setDrawableResource(resource)를 부르는 것으로 보임.

  • apply에 if문을 적용한 사례
    1
    2
    3
    4
    5
    6
    7
    8
    Glide.with(this)
    .load(url)
    .transition(DrawableTransitionOptions.withCrossFade(factory))
    .diskCacheStrategy(DiskCacheStrategy.ALL)
    .apply {
    if (corner = 0) transfoem(CenterInside(), RoundedCorners(corner.fromDpToPx()))
    }
    .into(this)

3. Glide 커스텀을 위한(=GlideApp 사용을 위한) AppGlideModule

Header에 User-Agent property 삽입

GlideApp 생성절차 링크를 따를 것.

4. 비트맵 관련 설정 - RequestOptions

(1) 주요 옵션

  • placeholder(resourceId): 이미지 로딩하는 중에 보여지는 대체 이미지.
  • error(resourceId): 이미지를 불러오지 못할 경우 대체 이미지.
  • skipMemoryCache(boolean): true 설정 시 캐시를 통하지 않고 이미지 로드.
  • override(int width, int height): target의 너비, 높이를 세팅. 이미지 리사이징.
  • centerCrop: default type. 외에 FitCenter, CircleCrop도 있음.

Glide에서 RequestOptions를 Glide.apply()했을 때와 안 했을 때의 이미지 transformation default 값이 다르다. (아래 예시: v4 공식문서 발췌)

→ RequestOptions을 적용하지 않았을 경우 default 값
Glide.with(context)
.load(url)
.fitCenter() // default
.into(imageView);

→ RequestOptions을 적용 시 default 값
Glide.with(context)
.load(url)
.apply(new RequestOptions().centerCrop())
.into(imageView);

→ xml에서 scaleType지정하면 그걸로 적용될 것 같은데, 만약 ImageView인데 scaleType을 지정하지 않았을 경우 AUIL와 Glide(RequestOptions적용한 것), Glide(RO 적용 안 한 것) 세 개의 작동이 다를 수 있음.

(2) 참고 옵션

  • diskCacheStrategy: 디스크 캐시 전략. 이미지 로드에 사용할 캐시 설정 ALL - DATA와 RESOURCE를 사용하여 원격 데이터를 캐시하고 RESOURCE만 사용하여 로컬 데이터를 캐싱 AUTO - defualt value. NONE - 디스크캐시 사용안함.
  • priority: 로드되는 이미지의 우선순위 결정 HIGH, IMMEDIATE, LOW, NORMAL
  • fallback: load할 url이 null일 경우 보여줄 대체 이미지. 이게 정의되어있지 않으면 error 이미지를, error 이미지도 없다면 placeholder 이미지로 대체됨.

(3) 예제. DisplayImageOptions와 대응되는 RequestOptions

1
2
3
4
5
6
7
8
9
private DisplayImageOptions mPlaceHolderOption = new DisplayImageOptions.Builder()
.resetViewBeforeLoading(true) // default
.cacheInMemory(true) // default
.cacheOnDisk(true) // default
.imageScaleType(ImageScaleType.EXACTLY) // default
.bitmapConfig(Bitmap.Config.RGB_565) // default
.showImageOnLoading(R.drawable.ic_loading) // loading place holder resource
.showImageOnFail(R.drawable.ic_loading) // failed place holder resource
.build();
  • resetViewBeforeLoading(true)
  • cacheInMemory(true) → RequestOptions.skipMemoryCache(false)
  • cacheOnDisk → RequestOptions.diskCacheStrategy
  • imageScaleType.EXACTLY: 뷰 사이즈에 맞춰서 이미지가 작아짐.
  • bitmapConfig → RequestOptions.format(DecodeFormat.PREFER_RGB_565)
      RequestOptions.format(DecodeFormat.PREFER_ARGB_8888)
    
    Glide’s default: RGB_565
  • showImageOnLoading / showImageOnFail → RequestOptions.placeholder(drawable) / RequestOptions.error(drawable)

5. Clear

(1) memory 캐시 삭제

1
2
// 이 메소드는 메인 스레드에서 호출되어야 한다.
Glide.get(context).clearMemory();

Glide의 캐시 메모리 영역과 BitmapPool을 정리한다.

단, 모든 메모리를 삭제하는 것은 특히 효율적이지 않으며 버벅거림과 로드 시간 증가를 방지하기 위해 가능한 한 피해야 한다.

(2) disk 캐시 삭제

1
2
3
4
5
6
7
8
val isMainThread = Looper.myLooper() == Looper.getMainLooper()
just<String>("")
  .subscribeOn(if (isMainThread) Schedulers.io() else Schedulers.immediate())
  .subscribe {
    // 이는 background 스레드에서 실행되어야 한다.
    Glide.get(this@SomethingActivity).clearDiskCache()
    Log.d("Deleted image memory cache and disk cache.")
}

디스크 캐시의 모든 항목을 지운다.

앱에서 실제로 테스트 해보니 캐시메모리의 디폴트 사이즈인 250MB를 채운 후 clearDiskCache()를 호출했을 때 변화는 다음과 같았다.

(Glide 외의 다른 캐시 데이터로 인해 왼쪽 before 사진에서는 250MB보다 약간 오버된 상태이다.)

6. 전환 - Transitions

1
2
3
4
5
6
import static com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions.withCrossFade;

Glide.with(context)
.load(url)
.transition(withCrossFade()) // optional
.into(view);

Glide v4 does NOT apply a cross fade or any other transition by default.

Glide v4 부터는 어떤 transitions 이벤트도 기본값으로 들어가있지 않으므로 필요할 떄 추가할 수 있다. (v3에서는 기본값이 cross fade였음)

7. 리스너 - RequestListener

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
Glide.with(context)
.load(url)
.listener(new RequestListener<Drawable>() { // load() 다음에 추가할 것
        @Override
        boolean onLoadFailed(@Nullable GlideException e, Object model,
        Target<Drawable> target, boolean isFirstResource) {
        // 여기서 GlideException 로그 출력
        Log.e(TAG, "Load failed: " + e.printBlrBlr());
        return false; // Target에서 onLoadFailed가 호출되는 것을 허용한다.
        }
       
        /*
          resource: 로드된 이미지.
          model: 이미지를 로드하는 데 사용된 모델.
          dataSource: 이미지 출처. DATA_DISK_CACHE, LOCAL, MEMORY_CACHE, REMOTE와 같은 값.
        */
        @Override
        boolean onResourceReady(
        Drawable resource,
        Object model,
        Target<Drawable> target,
        DataSource dataSource,
        boolean isFirstResource) {
        // 성공 로그를 출력하거나 DataSource를 사용해 캐시적중을 추적할 수 있다.
        return false; // Target에서 onResourceReady가 호출되는 것을 허용한다.
        }
    })
    .into(imageView);

만약 Bitmap으로 로딩된 이미지를 얻고자 한다면,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Bitmap loadedImage = Glide.with(context)
.asBitmap()
.load(url)
.listener(new RequestListener<Bitmap>() {
@Override
public boolean onLoadFailed(@Nullable GlideException e, Object o,
Target<Bitmap> target, boolean b) {
return false;
}

@Override
public boolean onResourceReady(**Bitmap bitmap**, Object o,
Target<Bitmap> target, DataSource dataSource, boolean b) {
image.setImage(ImageSource.bitmap(bitmap));
return false;
}
})
.submit(100100) // width, height: RequestBuilder API 참조
.get();

관련링크

기술 블로그 - 구글 검색엔진 최적화(SEO;Search Engine Optimization) 적용

  • 이카루스에서 세팅하는 방법 - 링크

구 이카루스에서 ejs를 사용하다가 jsx로 바뀌어 위의 방법이 통하지 않았음.(링크)

링크에 따르면 구버전에 비해 React를 사용해 만든 기존 이카루스에서는 표준링크를 자동으로 생성하기 때문에 hexo-auto-canonical를 따로 설치하지 않아도 된다.(그런데 설치해버려서 npm uninstall 로 삭제.)

1
2
3
4
npm i hexo-autonofollow
npm i hexo-generator-feed
npm i hexo-generator-seo-friendly-sitemap
npm i hexo-generator-robotstxt

결국 설치하는 건 위의 4개.

[1] 검색 엔진 등록: 구글 서치 콘솔

이 단락에서는 [사이트맵 생성]과 [색인 생성]방법을 알아볼 건데, 우선 규모가 작은 웹사이트의 경우 아래와 같이 구글서치콘솔 가이드에서 사이트맵이 필요하지 않을 수도 있다고 나와있다.

나는 아래에도 나와있지만 사이트맵 생성에 오류가 있어 일단 사이트맵 대신 페이지마다 [색인 생성]을 하는 방법으로 각 페이지를 구글검색에 걸리도록 하였다. (현재 페이지가 4개 정도밖에 없음)

그렇다고 사이트맵을 생성하지 말아야하는 것은 아니다.

(1) 사이트맵 생성: 아직도 안됨

Google Search Console에서 URL 접두어를 이용하여 도메인을 등록한다.

[계속]을 눌렀을 때 나오는 파일을 다운로드하여 [프로젝트]/public/ 에 해당 파일을 옮겨 넣는다. 그리고 배포 후 조금 기다리면 아래와 같이 초록색이 뜬다.

소유권이 확인되면 [속성으로 이동] 또는 왼쪽 위 URL 드롭다운을 클릭하여 Search Console을 이용할 수 있다.

지금은 검색 엔진을 사용하기 위해 사이트맵을 rss2.xml과 sitemap.xml을 등록해준다.

  • “가져올 수 없음”에 관한 포스팅

그런데 “가져올 수 없음”이라고 실패/성공도 아닌 것이 뜬다. 사이트맵 입력 시 슬래시를 붙여서 “/sitemap.xml”으로 하면 잘 된다는 말이 있어서 해보았으나 다르지 않음. 아마 현재 설연휴라서 이게 처리가 늦어지는 것으로 보고 일단 보류하고 다른 방법을 찾아보았다.

(2) 색인 생성: 1-2일 소요

주의 > 색인 생성은 일일 할당량(10개 아래, 정확하지 않음)이 있어 이를 초과하면 더이상 색인 생성을 요청할 수 없다.

색인 생성 방법은 다음과 같다.

  • 참고: 구글 서치 콘솔에서 색인 생성하는 방법 링크

상단 URL 검사란에 포스팅 주소(https://dl137584.github.io/2022/01/31/JCenter-지원종료/)를 붙여넣고 아래 [색인 생성 요청]을 누른다.

요청이 완료되면 아래와 같은 문구가 뜬다.

기본적으로 1-2일 소요되긴 하나, 1. 구글검색창에 나와도 아래 [색인생성범위]에는 뜨지 않거나 2. 이틀이 지나도 구글검색창이나 [색인생성범위]에 리스팅되지 않아서 다시 URL 검사를 해보면 등록되어있지 않다고 나오는 등의 상황이 있었다.

1번의 경우 7일 이하로 기다리면 나타나거나 하는데 2번의 경우 나는 다시 색인생성을 요청하였다.(sitemap.xml이 계속 “가져올 수 없음”이 떴기 때문에 어쩔 수 없이…)

정상적으로 색인생성요청이 처리가 된 후에는 사이드바 [색인생성범위]에서 아래 [유효]를 클릭하면 초록색으로 변하면서 그래프에도 유효 카운트가 표시되고 그 아래에 상세정보가 뜬다.

유효 색인 상세정보

색인을 생성한 url을 확인할 수 있으나 여기서 삭제는 할 수 없다.(해당 페이지는 내가 md 파일명을 변경하여 주소가 변경되었는데도 그대로 남아있다. 단, 이것도 7일 이하로 기다리면 [제외됨]으로 옮겨지면서 자동으로 갱신된다. [제외됨]으로 자동으로 옮겨질 경우 검색창에서도 검색되지 않는지는 해보지 않아 알 수 없음.)

(3) 색인 생성된 url 삭제: 6시간 내로 됨

url이 바뀌어 올렸던 것을 삭제해야 한다면 사이드바의 [삭제]로 이동한다.

그리고[임시 삭제 항목]>[새 요청]에서 삭제하고자하는 url을 기입하여 요청할 수 있다.

1

2

3

요청 처리가 완료되어 삭제되면 더이상 구글 검색창에서 관련 키워드를 입력해도 검색되지 않는다.

기술 블로그 만들기 - Github-pages, Hexo

1. 노션을 사용하는 것에 대한 불편함

(1) 노션의 단점

  • 읽기 어려운 URL + 제목을 수정하면 같이 변경되는 URL

    → 해보니 Hexo도 비슷한 이슈가 있음. 하지만 노션이 공유하기 어려운 것도 사실임.. 블로그로서 만들기가 어려움.

  • 페이지 로딩이 느림

    → Hexo는 기본적으로 정적사이트를 생성해주기 때문에 이 부분은 해결이 됨.

    정적 웹사이트란, 서버에서 배포하기 전까지는 이미 올려진 데이터만을 보여주기 때문에 페이지 내에서 글 수정이 불가능하며 그렇기 때문에 가볍다는 장점이 있다. 페이지를 로드한 후에는 서버와 통신할 필요가 없으니까.

덧붙여,

(2) 대체 플랫폼

  • Notion + Cloudflare

  • Tistory

  • Velog

  • Github-pages

    → 내가 지금까지 해본 건 Tistory/Notion(오직 노션)/Github(레포지토리를 DB 삼아 md 파일 만듦. 사이트를 따로 생성하지 않음.)/Jekyll(페이지 껍데기만 만듦)/OneNote/EverNote 였다.

    그 중 고유한 Url을 생성할 수 있고 디자인을 코드단으로 만질 수 있는 Jekyll이 제일 재밌었는데 그래서 Github-pages를 사용하기로 함.

    단, Jekyll은 제외하고.(왠지 한 번 만들고 실패해서 손이 안 감.)

(3) Github-pages 적용에 필요한 것

  • 정적 사이트 생성기(Static Site Generator): Jekyll(ruby), Hugo(Go), Hexo(Node.js), Gatsby 등
    • 정적 웹 사이트 생성기: DB 통신 등을 해서 서버에서 HTML 페이지를 실시간으로 만들던 동적 사이트 방식이 아닌, 완성된 HTML 파일을 바로 전달.

고민은 길지 않았다. 서버 문외한인 내가 가장 많이 들어본 Node.js 기반 Hexo를 사용하기로 결정했다.

(4) Hexo 적용 후 불편했던 점

하는 김에 hexo를 이용하면서 불편했던 점도 적어봄. 위에 나열된 노션의 단점은 모두 커버되었으나 일부 사용하기 어려운 점이 있었다.

  • 페이지를 업데이트하려면 코드를 수정해서 배포해야하기 때문에 바로 수정된 걸 볼 수 없다.(hexo에서 제공하는 draft 기능을 사용하는 데에도 동일한 문제있음)

    → 노션의 이용을 병행하여 draft(초안)은 노션에 작성하기로 함. (일단 나는 편해짐.)

  • 여러 PC에서 배포를 할 수 있는 수월한 방법을 찾지 못함.

    • 방법(링크)은 찾았는데 기존 .deploy_git을 다른 PC에 동기화해줘야(동일하게 옮겨줘야) 하는 번거로움때문에 시도 안 하는 중..
    • Github에 올린 코드를 받아서 hexo-cli만 설치하여 올리려고 보니 hexo server 명령어가 기능을 잘 안 하고(기존 작업하던 PC OS는 윈도우, 새로 시도한 PC는 맥), 커밋 안 한 다른 PC에서 수정하던 게 마음에 걸려서 골치아파지더라.
  • 목차 생성 시 h1 아래에 h2가 아닌 h3으로 하위헤더를 바로 생성할 경우, 아래 이미지와 같이 일부가 생략된다.

(5) 노션의 장점

노션의 이용을 병행하려고 보니 장점을 빼놓을 수가 없다.

  • 접근성이 좋다. 웹, 앱 어디서든 편집할 수 있다.

  • 동기화가 잘 된다. 엄청 잘 된다! 처음 노션을 고른 이유가 동기화였다.

  • 편리한 단축키. ctrl + x로 한 행을 삭제하거나 슬래시(/)로 페이지를 만들 수 있게 하거나 여러가지로 잘 사용중.

  • 편집이 편하고 예쁘다! (잘 쓰진 않지만 수정된 내용은 히스토리로 남아서 혹시모를 상황에 대비가 가능하다.)

    • 드래그앤드랍으로 행 옮기거나 블록처리 가능한 점.
    • 어디서든 특정 부분을 하이라이트 하기 좋음. md는 코드 내에서는 볼드체가 불가능하거나 불편한 점이 있음. 이것도 따로 방법을 찾아봐야 할듯.
    • 폰트가 읽기 좋음. md는 바꾸기 귀찮아서 디폴트로 사용중인데 영어는 거의 잘 나오는데 PC에서 한국어가 픽셀 깨지듯이(예민) 나옴.
    • 헤더를 만들었을 때 딱 읽기 좋은 형태로(상하단 여백, 텍스트 강조) 노출됨. md는 가끔 h1 쓰는데도 읽으면서 가독성 떨어져서 일일이 bold 넣어줘야함.

2. Hexo 환경설정

(1) node.js 및 npm 버전 확인

내 경우 React.js 시작하기 에서 node.js와 npm이 설치된 상태여서 Hexo에 필요한 버전만 확인했다.

  • Mac에서 node.js 및 npm설치 homebrew는 Mac에서 패키지 관리를 용이하게 하기 위한 툴이다. 이걸 먼저 설치.
    1
    /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
    그리고 node.js를 설치하면 npm도 함께 설치되기 때문에 아래 명령어로 node.js를 설치.
    1
    brew install node
1
2
3
4
> node -v
v14.15.5
> npm -v
6.14.11

Hexo를 사용하기 위해서는 node.js는 v12이상을 권하고 있다.(최소 v10.13)

(2) Hexo 설치

1
> npm install -g hexo-cli

(3) Hexo 초기화

1
> hexo init testBlog

위 명령어로 초기화하면 아래와 같은 파일구조로 기본 설정파일들이 생성된다.

(4) 파일구조

1
2
3
4
5
6
7
8
.
├── _config.yml
├── package.json
├── scaffolds
├── source
| ├── _drafts
| └── _posts
└── themes
  • _config.yml: 환경설정(사이트 설정;웹사이트 제목, URL 설정, include 설정 등 가능)
  • package.json: 데이터 파일. EJS, Stylus, Markdown 렌더러들은 기본으로 설치됨.
  • scaffolds: 새 포스트/페이지를 생성했을 때 기본으로 만들어질 레이아웃을 정의.

(5) 명령어

  • 서버 실행: hexo s (server)
  • 정적파일 생성: hexo g (generate, public/이 생성된다.)
  • 배포: hexo d (deploy)
  • 생성 및 배포: hexo d -g (hexo deploy -generate)
  • 클린: hexo clean (public/이 삭제된다.)

커맨드라인에서 hexo clean을 하게되면 마지막 INFO에 아래와 같이 뜨는 것을 볼 수 있다. 즉, DB와 public 폴더가 초기화된다.

1
2
3
INFO  === Registering Hexo extensions ===
INFO Deleted database.
INFO Deleted public folder.

3. 커스텀

(1) 사이드바

_config.icarus.yml 편집

4. 포스팅 - 이카루스 테마(icarus theme) 사용

(1) 새 포스트 생성

흔히 알고 있는 하나의 포스팅을 쓸 때 사용한다.

1
hexo new post "title"

(2) 이미지 폴더 생성

주의
1. 파일명은 대소구분을 한다. “png”와 “PNG”는 다르다.
2. 아래 방법으로 하게되면 hexo clean 명령어 사용 시 public/아래의 파일은 날아가므로 여기에 파일을 넣지 않도록 할 것.
  1. _config.yml에서 post_asset_folder 속성을 true로 바꾼다.

  2. 포스트 생성 시(hexo new post) 포스트의 제목과 같은 폴더가 자동으로 생성될텐데 여기에 이미지 파일을 넣는다.

  3. 이렇게 되면 ![](/2022/02/04/006-create-personal-blog-with-githubpages-and-hexo/006-02.png)와 같이 접근할 수 있다.
    내 경우 _config.yml에서 permalink를 “:year/:month/:day/:title/“로 해두어 저렇게 나온다.

주의
![](./image01.png)처럼 상대경로를 사용하게되면 포스팅을 클릭하여 나타난 페이지에서는 정상적으로 이미지가 노출되나 사이드메뉴의 카테고리 중 하나를 클릭했을 때 페이지에서 상대경로가 맞지 않아 이미지가 깨지게 된다.

(3) 새 페이지 생성

페이지는 생성할 경우 해당 페이지명으로 된 폴더와 하위에 index.html 파일이 생성되며 포스트와 다르게 상단 탭에 연결할 수 있는 것으로 보임. 따라서 category와 tag는 사용할 수 없고 about처럼 상단에 추가할 수 있다.

1
hexo new page "page"

(4) Scaffolds(머리말) 속성

  • title 대괄호가 잘 안 들어가서 이스케이프문자를 넣어야하나했는데 그냥 큰따옴표로 묶어서 해결해버림.
  • date 내가 지정해주지 않아도 자동으로 DB에 발행일이 들어가는가 보더라. 딱히 그럴 일이 없어서 이게 수정이 되는지는 확인 안해봄.
  • categories, tags categories, tags는 여러개 지정할 수 있는데, 다음과 같이 두 가지 방법이 있다.
    1
    2
    3
    4
    categories:
    - ctg1
    - ctg2
    tags: [tag1, tag2]

(5) toc 띄우기

이카루스 default 테마에서는 toc 속성이 false로 되어있는지 처음에는 나타나지 않으나 아래와 같이 true로 명시해주면 바로 [카탈로그]라고 뜬다. 사이드에 위치하는데 position 옵션에서 left/right를 지정할 수 있다.

창의 너비가 작아지면 우선적으로 보이는게 left라서 나는 left로 해두고 left sidebar의 sticky 옵션을 true로 바꾸었다.

1
2
3
4
5
6
7
8
toc: true
widgets:
-
position: left
type: toc
index: true
collapsed: true
depth: 3

(6) 로컬 테스트

http://localhost:4000/에서 확인한다.

로컬 테스트는 배포 전 로컬의 파일 변경만으로 확인할 수 있는데, 아래 케이스에 해당한다.

  • configuration이 변경된 경우
    • 서버를 내렸다가(ctrl + C) 다시 올려야(hexo s) 적용이 된다.

로컬에서 테스트가 끝나면 아래 명령어로 배포 후 1-2분 후 적용되었는지 https://dl137584.github.io/에서 확인할 수 있다.

1
hexo d -g

(7) about 페이지 생성

1
hexo new page about

(public에 about 폴더를 만들고 별짓을 다했는데 그냥 new page하면 되는 거였어..)

page 명이 “about”인 이유는 _config.icarus.yml에서 navbar:menu:About에 설정한 이름을 따라간 것.

1
2
3
4
5
6
7
8
9
# Page top navigation bar configurations
navbar:
# Navigation menu items
menu:
Home: /
Archives: /archives
# Categories: /categories
# Tags: /tags
About: /about

(8) 초안 작성

1
hexo new draft title

이렇게 생성된 포스트는 _posts/가 아닌 _draft/에 위치하게 된다. 작성은 했는데 바로 배포하고 싶을 때 md 파일을 _draft 폴더로 옮기면 배포해도 포스팅이 노출되지 않기 때문에 편하게 쓸 수 있다.

만약 테스트 시 draft 파일도 보이도록 하고자한다면 서버를 올릴 때 –draft 옵션을 붙여주면 된다.

1
hexo server --draft

5. 마크다운 활용

노션의 callout(설명) 구현

인용과 차이를 두면서 노션의 callout UI와 같이 일부영역 강조를 편하게 사용하고 싶어서 마크다운의 테이블 구조를 활용하여 아래와 같이 만들었다. 인용 외에 참고사항 등에 사용할 예정이다.

Title
Contents1
Contents2

좌우 여백은 위의 경우 제법 넓게 나타나는 편인데, 가운데 영역(Title, Content1, Content2)의 총 너비가 짧아서 그렇다. Title이나 Content의 길이가 길어질 경우 여기에서 사용한 것처럼 여백이 줄어들게 된다.

마크다운 코드는 아래와 같이 사용하였다.

1
2
3
4
5
  | Title |  
--- | --- | ---
| Contents1 |
| Contents2 |
| |

[번역: Async Basics with Rust] 동시성 VS 병렬성(Concurrent vs Parallel)

이 포스팅은 Async Basics with Rust의 글을 번역한 것입니다. 오역, 의역, 생략이 있으니 감안하여 읽어주십시오.

동시성과 병렬성의 차이는 무엇인가?

이 주제에 대해 곧바로 동시성이 무엇인지 정의함으로써 파헤쳐보자. 병렬 상의 동시성과 쉽게 헷갈릴 수 있어서 시작부터 두 가지를 명확히 구분해둘 것이다.

동시 실행(Concurrency)이란 동시에 많은 것을 **처리하는** 걸 말한다.

병행(Parallelism)이란 동시에 많은 일을 **수행하는** 걸 말한다.

우리는 멀티태스킹을 동시에 여러 개의 작업을 진행한다는 개념으로 쓴다. 이러한 다중 작업에는 두 가지 방법이 있다.

하나는 작업을 동시에(;함께; 겸임) 진행하지만, 실제 같은 시간에 하지는 않는 것이며,

그림1 - Concurrent

또 다른 방법은 병렬적으로 실제 같은 시간에 여러 작업을 진행하는 것이다.

그림2 - Parallel

몇 가지를 정의해보자.

  • 리소스(Resource)

    작업을 진행하는 데에 필요한 것. 리소스는 제한되어있다.

    한 예로 CPU의 시간이나 메모리를 들 수 있다.

  • 작업(Task)

    진행하면서 어떤 종류의 리소스를 필요로하는 기능 집합(A set of operations)이다.

    하나의 작업은 몇 개의 sub-operations로 구성된다.

  • 병렬성(Parallel)

    정확히 같은 시간에 독립적으로 일어나는 일.

  • 동시성(Concurrent)

    동시에 진행중(in progress)인 작업들을 말하지만, 반드시 같은 시간에 진행되는 것은 아니다.

    이는 중요한 차이점이다. 만약 두 작업이 동시에 실행됐지만 병렬적이지는 않을 때, 그 작업들은 stop(멈춤)하거나 resume(재시작)할 수 있어야 한다.

주석 > 왜냐하면 병렬적이지 않은 것은 위의 [그림2 - Parallel]과 같이 한 작업이 계속해서 CPU를 붙잡고 항상 진행중 상태에 있지 않기 때문이다. 멈추고 재시작하기를 반복한다.

따라서 동시 실행 속성을 가지고 있다면 interruptable(끼어들 수 있는)하다고 말한다.

내가 사용하는 심상모형(mental model)

나는 우리가 병렬성이나 동시성을 가진 프로그램을 만들 떄 어려워하는 이유가 일상에서 일어나는 사건들을 모델링하는 방법에서 기인한다고 생각한다. 우리는 대게 잘못된 직관으로 대략적인 정의를 내리는 경향이 있기 때문이다.

concurrent의 사전적 정의는 parallel과의 차이를 인지하는 데에 도움을 주지 않는다.

나로 말할 것 같으면, 병렬성과 동시성의 차이점이 필요한지를 떠올린 게 시작이었다.

이들이 필요한 이유는 리소스의 활용도와 효율성과 관련된 모든 것에 있다.

효율성이란 어떤 일을 하면서, 또는 바라는 결과를 내는 데에 쓰이는 자원, 에너지, 노력, 돈, 그리고 시간을 낭비하지 않는 (대게 측정할 수 있는) 능력을 말한다.

  • 병행(Parallelism)

    작업을 수행하면서 리소스를 계속해서 늘린다. 이는 효율성을 고려하지 않는다.

  • 동시 실행(Concurrency)

    효율성과 리소스 활용도 모두를 고려한다. 동시 진행은 절대 단 하나의 작업을 더 빠르게 만들 수는 없다. 대신 리소스를 더 효율적으로 운용하고, 그럼으로써 작업들의 집합(a set of tasks)이 더 빠르게 끝나도록 한다.

경제학에서 몇 가지 유사점을 찾아보자.

상품 제조 사업에서는 린(LEAN)이 대표적이다.

린 이라는 기술을 사용함에 있어 가장 이점은 기다리는 시간과 가치없는 작업을 제거하는 것이다.

프로그래밍에서 말할 것 같으면, blocking과 polling을 피한다고 말할 수 있다.

동시성 및 I/O와의 관계

지금까지 말한 걸 보면, 비동기 코드를 작성하는 데 리소스를 최적으로 사용할 때야 비로소 의미가 있다.

프로그램을 짤 때 동시성이 도움이 되지 않는 경우도 있다. 병렬로 작업할 파트들로 나눌 수 있다면 더 많은 리소스를 할당하는 식으로 문제를 해결할 수 있다.

동시성에 관한 두 가지 주요 이용 사례가 있다:

  1. 입출력이 수행되는 중에 일부 외부 이벤트가 발생할 때까지 기다리는 상황.
  2. 여러 가지에 집중해야할 때 한 가지에만 너무 오래 기다리는 걸 방지해야하는 상황.

첫 번째는 기본적인 입출력 예제이다: 당신이 한 가지 작업을 진행하기 전에 네트워크 호출이나 DB 쿼리 등이 발생할 때까지 기다려야하는 상황이다. 그러나 지금 다른 할 일도 많기 때문에 다른 작업을 계속하다가 작업(네트워크 호출 등)이 준비가 됐는지 정기적으로 확인하거나 준비가 됐을 떄 알림을 받아야 한다.

두 번째는 UI 단에서 자주 일어나는 일이다. 당신이 한 개의 코어만 가지고 있다고 하자. 그럼 CPU에서 집중적으로 작업을 수행하고 있는데 대체 어떤 UI가 무반응을 피할 수 있을까?

음, 당신이 지금 하고 있는 작업이 뭐든간에 멈추고, “UI 갱신”을 하고, 그 후에 하려고 했던 일을 재시작할 수도 있다. 이렇게하면 작업을 1초에 60번 중지/재시작 해야한다. 그러면 당신은 결국 대략 60Hz의 새로고침 빈도를 반응하는 UI를 가지게 될 것이다.

OS에서 제공하는 스레드에 관하여

I/O 처리 전략에 관해 이야기하면서 스레드에 대해 좀 더 다룰 거지만 여기서도 언급하겠다. OS 스레드를 사용할 때 한 가지 문제는 코어에 매핑되는 것처럼 보인다는 것이다. 대부분의 운영 체제가 스레드 수가 코어 수와 같을 때까지 하나의 스레드를 하나의 코어에 매핑하려고 시도하더라도 이게 반드시 올바른 심상 모델은 아니다.

코어보다 많은 스레드를 생성하면, OS는 스레드간에 스위치를 수행하고 각 스레드에 실행시간을 제공하는 스케줄러를 사용하여 각 스레드를 동시에 진행하도록 할 것이다. 그리고 시스템에서 프로그램은 겨우 하나만 실행되지 않는다는 걸 명심해야한다. 다른 프로그램도 여러 개의 스레드를 생성할 수 있고, 이는 CPU에 있는 코어보다 더 많은 스레드가 있음을 의미한다.

그러므로, 스레드는 병렬적으로 작업을 수행하도록 하는 수단이 된다. 이는 동시성을 달성하는 수단이기도 하다.

이건 동시성에 관한 마지막 파트로 이어진다. 이제 일종의 참조 프레임을 정의해야 한다.

참조 프레임 바꾸기

당신의 관점에서 봤을 때 완벽하게 동기적인 코드를 짰다고 하자. 잠시 멈춰서 운영 체제 관점에서 이게 어떻게 보일지 생각해봐라.

운영 체제는 당신의 코드를 처음부터 끝까지 실행하지 않을 수 있다. 매순간 프로세스를 멈추고 다시 시작하길 반복할 것이다. CPU는 당신이 보기에 이 작업에만 집중하고 있다고 생각하는 동안에도 멈추고 일부 입력을 처리하고 있을지도 모른다.

그러니 동기적 실행은 그저 환상이다. 하지만 프로그래머로서 당신의 관점에서, 그렇지만도 않다. 이게 요점이다:

다른 맥락 없이 동시성을 말할 때, 당신은 프로그래머이며 당신의 코드는 참조 프레임이 된다. 만약 이를 염두에 두지 않고 동시성에 대해 이해하려한다면 혼란스러울 수 있다.

즉, 참조 프레임을 염두에 둬야 한다.

아직 복잡하게 들릴 수 있다. 이후 비동기 코드와 함께 작업하면서 이를 계속 상기해낸다면 복잡함은 점점 덜게 될 것이라 약속한다.