잡다한팁들2018. 1. 24. 02:55

1. 빠른 시작 끄기


빠른 시작은 종료시에 다음 부팅에 메모리 내용을 로딩하는 임시 저장 파일을 만드는 것으로 오히려 버그를 만든다. 

특히 사운드와 비디오 드라이버 쪽에서 문제가 발생. 

게다가 기능을 켠다고 해도 딱히 크게 윈도우즈 로딩의 성능 향상이 없다. 

불편한 버그가 있는 기능으로 끄는게 좋다.


전원 옵션 - 전원 단추 동작 설정 - 현재 사용할 수 없는 설정 변경 - 빠른 시작 켜기 체크 해제.


-

-




2. HPET 끄기


HPET는 고정밀 타이머로 8코어 이상에서는 오히려 문제를 일으키는 기능으로 다음과 같은 증상을 만든다.

- 화면 티어링

- 1000 폴링 레이트로 사용하는 마우스 포인터의 튀는 현상

- 화면과 소리의 싱크 안맞음


윈10에서의 기능을 끄는 방법은 cmd를 관리자모드로 열고 다음과 같이 입력.


bcdedit /set useplatformclock false


이런 문제로 최근 윈도우즈의 정책도 HPET를 쓰지 않는 다른 타이머 방식으로 가는 쪽을 선택하고 있다고 한다.

HPET 타이머의 값을 읽어오는 동안의 지연 시간이 오히려 타이밍을 망가뜨리는 결과를 만들고 있기 때문.


HPET를 끄면 게임의 성능도 오히려 소폭 상승한다.


Posted by 홍규홍규
안드로이드2017. 12. 22. 23:59

웹뷰와 자바스크립트 간 통신을 할 때 다음과 같은 오류가 발생할 때가 있다.


java exception was raised during method invocation


이는 js에서 안드로이드 메서드를 호출할 때 발생하는데, 스레드가 필요한 작업인데 그냥 수행하는 경우 발생한다.

따라서 스레드 또는 핸들러로 처리해주면 해결된다.

@Override @JavascriptInterface
public void methodName() {
  new Handler().post(new Runnable() {
    @Override
    public void run() {
      // 작업 처리
    }
  });
}
Posted by 홍규홍규
JavaScript2017. 12. 20. 13:54

모바일에서는 ajax나 getJSON으로 웹 페이지를 불러올 때 이미지가 있는 경우 뚝뚝 끊길 때가 많다.

네트워크 상태가 안 좋거나 이미지 크기가 크거나 그럴 때.


그래서 이미지가 완전히 로드된 후에 나머지 페이지 처리를 하고 싶었다.

구글링을 하다가 좋은 예제가 있어 담아왔다. 출처는 아래에 ..


먼저 이미지로드를 처리할 확장형 객체를 구현한다.

$.fn.imagesLoaded = function() {
  var $imgs = this.find('img[src!=""]');
  if(!$imgs.length) 
    return $.Deferred().resolve().promise();

  var dfds = [];
  $imgs.each(function() {
    var dfd = $.Deferred();
    dfds.push(dfd);
    var img = new Image();
    img.onload = function() {
      dfd.resolve();
    }
    img.onerror = function() {
      drd.resolve();
    }
    img.src = this.src;
  });
  
  return $.whdn.apply($, dfds);
}

이제 이미지를 로드하는 곳에 붙여넣기만 하면 끝.

$(selector).css('display', 'none');
$(selector).empty();
$.getJSON('/url.do', function(result) {
  ...
  $(selector).append(result.date.imageSrc).imagesLoaded().then(function() {
    $(selector).css('display', 'block');
    // window.android.hideProgressBar();
    // 그 외 나머지 처리할 작업
  });
}

출처 : https://stackoverflow.com/questions/4774746/jquery-ajax-wait-until-all-images-are-loaded


참고로 같은 이름의 플러그인도 있으니 다운받아서 사용하면 더 간단하지 싶다. 

https://github.com/desandro/imagesloaded

Posted by 홍규홍규
Java & Spring2017. 12. 19. 00:06

application-context.xml 에 다음을 밑줄 친 부분을 추가한다.

<beans xmlns="http://www.springframework.org/schema/beans"
    ...
    xmlns:task="http://www.springframework.org/schema/task"  
    xsi:schemaLocation="http://www.springframework.org/schema/beans
                        http://www.springframework.org/schema/beans/spring-beans.xsd
                        http://www.springframework.org/schema/context
                        http://www.springframework.org/schema/context/spring-context.xsd
                         ...
                        http://www.springframework.org/schema/task 
                        http://www.springframework.org/schema/task/spring-task-3.0.xsd"> 

그리고 아랫 부분도 추가해준다.

<context:component-scan base-package="패키지명" />
<bean id="scheduleJob" class="패키지명.클래스이름" />
<task:scheduler id="scheduler" pool-size="10" />
<task:annotation-driven scheduler="scheduler" /> 

xml 설정은 이걸로 끝난다. 매우 간단.

다음은 구현부인데 구현부도 간단하다.

@Component
public class Shceduler {
  
  @Scheduled(cron="0 0 1 * * *")
  public void sheduler() {
    Calendar calendar = Calendar.getInstance();
    Date date = calendar.getTime();
    String today = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(date);
    System.out.println("스케쥴러 작동 시간 : " + today);
  }

}

클래스 위에 @Component 어노테이션을 붙여주고, 구현 메서드에도 @Scheduled 어노테이션을 붙여주면 된다.

cron 표현식은 스케쥴러가 작동하는 주기.

아래 블로그에 cron 표현식에 대해 자세하게 정리되어 있다. 


http://blog.naver.com/PostView.nhn?blogId=lovemema&logNo=140200056062


내 경우 하루에 한 번씩 주기적으로 쿼리문을 돌려야 해서 위 스케쥴러를 구현하게 되었다.


- 2019-02-19 추가

위 처럼 하면 스케쥴러가 중복실행되는 이슈가 발생한다. 

이것은 바보처럼 Scheduler 클래스에 @Component 어노테이션을 붙였기 때문인데 

xml에서 스케쥴러 세팅을 했는데도 클래스를 빈으로 등록하는 어노테이션을 선언했기 때문이다.


따라서 @Component 어노테이션은 삭제해주자...^^;;

Posted by 홍규홍규
MySQL2017. 12. 13. 17:50

테이블에 특정 값들을 Insert하고 성공 시 Insert된 데이터의 Primary Key가 필요한 경우가 있다.

이때는 <selectKey>를 사용하면 된다.

<insert id="insert" parameterType="Member">
    INSERT INTO MEMBER(name, age) VALUES(#{name}, #{age})
    <selectKey resultType="int" keyProperty="no" order="AFTER">
        SELECT LAST_INSERT_ID()
    </selectKey>
</insert>

keyProperty는 VO에 정의한 테이블에 들어가는 컬럼 ID를 넣으면 된다.

따라서 Primary Key 뿐만 아니라 다른 데이터도 가져올 수 있다는 뜻.


order는 순서다. 

특정 값을 이용해 INSERT하려면 당연하게도 INSERT 앞에 위치시키고 BEFORE 명령어를 쓰면 된다.


그러면 insert가 성공하면 MEMBER 객체의 no 변수에 Auto Increment된 값이 저장된다.


member.getNo()로 가져오면 끝.



'MySQL' 카테고리의 다른 글

동적 쿼리문 작성해보기  (0) 2018.04.24
Posted by 홍규홍규
안드로이드2017. 12. 5. 14:47

이클립스에서 기기에서 테스트를 하려고 할 때 누가(Nougat) 이상 버전의 기기는 아래와 같이 

target이 unknown으로 뜨는 현상이 발생한다.



이를 해결하기 위해서는 커스터마이징된 ADT를 다운받아서 설치해야 한다.


https://github.com/khaledev/ADT/releases/tag/adt20160729


이곳에서 ADT-24.2.0-20160729.zip을 받아서 적당한 곳에 넣어둔다.

(압축 풀 필요 없음)


이클립스에서 Help - Install New Software를 선택한다.



Add 버튼을 클릭하고 Archive를 클릭한 다음에 다운받은 zip 파일을 선택하고 설치하면 된다.



Posted by 홍규홍규
JavaScript2017. 12. 2. 01:03

jquery datepicker를 사용하다가 날짜를 선택할 때 선택 범위를 제한해야 할 때가 있다.

$(selector).datepicker({
  dateFormat: 'yy-mm-dd',
  minDate: 0
});

일반적으로는 minDate에 0을 주면 오늘날짜부터 선택할 수가 있다.


그런데 내가 하고 싶은 것은 오늘날짜가 아닌 특정날짜 이후, 그리고 이전으로 범위를 제한하고 싶었다.

다음과 같이 하면 된다.

$(selector).datepicker({
  dateFormat: 'yy-mm-dd',
  minDate: new Date('2017-12-01'),
  maxDate: new Date('2017-12-31')
});

이렇게 하면 2017년 12월 1일부터 31일까지로 날짜 선택할 수 있는 범위가 제한된다.


그런데 보통 이렇게 하드코딩하지 않고 동적으로 범위가 변할 경우가 많기 때문에 

'2017-12-01' 대신에 변수를 넣는 것이 맞겠지.


아래는 팁.

$(selector).datepicker({
  dateFormat: 'yy-mm-dd',
  minDate: new Date('2017-12-01'),
  maxDate: new Date('2017-12-31'),
  onClose: function() {
    $(selector2).datepicker({
      dateFormat: 'yy-mm-dd',
      minDate: new Date(selector의 시작날),
      maxDate: new Date('2017-12-31')
    });
  });
});

이렇게 하면 datepicker를 두 개 쓸 때 

두 번째 datepicker에서 첫 번째 selector에서 선택한 날짜 이후부터 날짜를 선택할 수 있다.


Posted by 홍규홍규
안드로이드2017. 11. 7. 14:39


안드로이드 스튜디오 3.0으로 업데이트 하면서 

기존 이클립스에서 안드로이드 프로젝트 빌드를 하려고 하면 아래와 같은 오류가 발생한다.




build-tools/26.0.2/dx.jar was not loaded from the SDK folder!


dx.jar를 새로운 버전으로 바꿔줘야 하는지 

아니면 이클립스에서는 26버전부터는 빌드를 지원하지 않는 건지 아무튼

이래저래 시도해봤는데 해결이 안 돼서 결국 아래와 같은 방법으로 해결했다.


SDK Manager을 띄워서 

Android SDK Build-tools 26.0.2 를 삭제하고 25.0.3 버전을 사용.



(+) 2017. 11. 16 추가

이렇게 하면 이클립스에서는 문제없이 빌드가 되지만 

안드로이드 스튜디오를 함께 사용한다면 - 특히 3.0으로 업데이트 했다면 -

빌드툴 인스톨이 안 되었다면서 오류를 뿜는다.


두 가지 툴에서 모두 정상적으로 빌드가 되게 하려면 다음과 같은 방법을 사용한다.


이클립스 프로젝트 내 project.properties 파일에 다음 문구를 추가하고 저장하면 끝.

sdk.buildtools=25.0.3

Posted by 홍규홍규
안드로이드2017. 10. 30. 03:34

네이버 개발자 센터의 레퍼런스 코드를 기준으로 작성되었다.

https://developers.naver.com/docs/login/android/


필요한 환경 설정은 위 사이트를 참고하여 세팅하면 된다.


내 경우 외부 클래스를 별도로 만들어 로그인을 처리했다. 로그인 버튼은 커스텀 이미지를 사용했다.


public class NaverLogin {
  private OAuthLogin mOAuthLoginInstance;
  private Context mContext;

  public NaverLogin(Context mContext) {
    this.mContext = mContext;
    initNaverAuthInstance();
  }

  // API 인스턴스를 초기화
  private void initNaverAuthInstance() {
    final Striing OAUTH_CLIENT_ID = "7o6Aqvtqr11ap1MdnH58";
    final String OAUTH_CLIENT_SECRET = "2IbCBMfSLt";
    final String OAUTH_CLIENT_NAME = "네이버 아이디로 로그인";
    mOAuthLoginInstance = OAuthLogin.getInstance();
    mOAuthLoginInstance.init(mContext, OAUTH_CLIENT_ID, OAUTH_CLIENT_SECRET, OAUTH_CLIENT_NAME);
  }
  
  // 로그인 처리
  public void forceLogin() {
    mOAuthLoginInstance.startOauthLoginActivity((Activity)mContext, mOAuthLoginHandler);
  }

  // 로그아웃 처리(토큰도 함께 삭제)
  public forceLogout() {
    // 스레드로 돌려야 한다. 안 그러면 로그아웃 처리가 안되고 false를 반환한다.
    new Thread(new Runnable() {
      @Override
      public void run() {
        mOAuthLoginInstance.logoutAndDeleteToken(mContext);
      }
    }).start();
  }

  // 로그인을 처리할 핸들러
  private OAuthLoginHandler mOAuthLoginHandler = new OAuthLoginHandler() {
    @Override
    public void run(boolean success) {
      if(success) {
        String accessToken = mOAuthLoginInstance.getAccessToken(mContext);
        String refreshToken = mOAuthLoginInstance.getRefreshToken(mContext);
        String tokenType = mOAuthLoginInstance.getTokenType(mContext);
        long expiresAt = mOAuthLoginInstance.getExpiresAt(mContext);
        
        new RequestApiTask(accessToken).execute(); // 로그인이 성공하면 네이버의 계정 정보를 가져온다.
      } else {
        // 로그인 실패 처리
      }
    }
  };

이어서 로그인 성공 시 해당 계정 정보를 가져오는 작업을 한다.

  private class RequestApiTask extends AsyncTask {
    private String token;

    RequestApiTask(String token) {
      this.token = token;
    }

    @Override
    protected void onPostExecute(StringBuffer result) {
      super.onPostExecute(result);
      // 로그인 처리가 완료되면 수행할 로직 작성
      processAuthResult(result);
    }

    @Override
    protected StringBuffer doInBackground(Void... params) {
      String header = "Bearer " + token;
      try {
        final String apiURL = "https://openapi.naver.com/v1/nid/me";
        URL url = new URL(apiURL);
        HttpURLConnection con = (HttpURLConnection)url.openConnection();
        con.setRequestMethod("GET");
        con.setRequestProperty("Authorization", header);
        int responseCode = con.getResponseCode();

        BufferedReader br = new BufferedReader(new InputStreamReader(
                                      responseCode == 200 ? con.getInputStream() : con.getErrorStream()));

        String inputLine;
        StringBuffer response = new StringBuffer();
        while((inputLine = br.readLine()) != null) {
          response.append(inputLine);
        }

        br.close();
        return response;

      } catch(Exception e) {
        e.printStackTrace();
      }

      return null;
    }
  }

로그인의 계정 정보를 가져와서 처리하는 작업을 한다.

9월부터 계정 정보 요구 시 개인 정보 제공을 거부할 수 있기 때문에 이것에 대한 처리가 필요했다.


계정 정보는 JSON 형식으로 반환되는데, 이를 디코드해주면 다음과 같은 데이터를 조회할 수 있다.

{"resultcode":"00","message":"success",
    "response":{
        "nickname":"...","enc_id":"...","profile_image":"...","age":"...",
        "gender":"...","id":"...","name":"...","email":"...","birthday":"..."
    }
}

필요한 정보는 response라는 객체 안에 들어있기 때문에 최초 반환된 JSON 데이터에서는 정보를 가져올 수 없다.

  private void processAuthResult(StringBuffer response) {
    try {
      // response는 json encoded된 상태이기 때문에 json 형식으로 decode 해줘야 한다.
      JSONObject object = new JSONObject(response.toString());
      JSONObject innerJson = new JSONObject(object.get("response").toString());

      // 만약 이메일이 필요한데 사용자가 이메일 제공을 거부하면 
      // JSON 데이터에는 email이라는 키가 없고, 이걸로 제공 여부를 판단한다.
      if(!innerJson.has("email")) {        
        final Dialog dialog = Util.getOneDialog(mContext); // Util.getOneDialog 구현은 생략.
        TextView content = (TextView)dialog.findViewById(R.id.one_dialog_content);
        content.setText("이메일 정보 제공에 동의해주세요.");
        Button dialogBtn = (Button)dialog.findViewById(R.id.one_dialog_btn_ok);
        dialogBtn.setOnClickListener(new View.OnClickListener() {
          @Override
          public void onClick(View v) {
            dialog.dismiss();
            forceLogout();
          }
        });
      } else {        
        String account = innerJson.getString("email");
        String nickname = innerJson.getString("nickname");
        String profileImgUrl = innerJson.getString("profile_image");
        // 원하는 모든 과정이 처리가 되면 해당 멤버 데이터를 가지고 다음 로직을 수행한다.
        // checkMember(account, nickname, profileImgUrl.replaceAll("\\\\", ""));
      }
    } catch(Exception e) {
      e.printStackTrace();
    }
  }
}

필요한 정보 제공을 거부할 경우 네이버 로그인이 정상적으로 처리되어도 강제로 로그아웃 시키고 토큰도 삭제한다.

그리고 다시 로그인 화면으로 보내서 로그인을 요청.

Posted by 홍규홍규