MySQL에서 이모티콘(Emoji)를 저장하려고 하면 Incorrect string value 에러 메세지가 발생하게 됩니다.


MySQL의 utf8은 3byte로 표현되는 범위내의 문자만 입력할 수 있습니다.


하지만 이모티콘(Emoji)는 4byte로 표현되기 때문에 위와같은 에러가 발생합니다.


MySQL 5.5.3부터 4byte를 지원하는 utf8mb4라는 캐릭터셋이 추가되었습니다. 


따라서 이모티콘을 삽입하기 위해서는 테이블의 캐릭터셋을 uft8mb4로 변경해야 합니다.


여러가지 방법이 있지만 저는 mysql 설정파일 my.cnf파일을 수정했습니다.


MySQL 5.5.3이상에서만 가능하기 때문에 이전 버전이라면 업데이트를 해줘야 합니다.



1. /etc/my.cnf에 내용을 추가해줍니다.


[mysqld] 

character-set-server = utf8mb4

collation-server = utf8mb4_unicode_ci



2. MySQL 서비스를 재시작해줍니다.


servicce mysqld restart





출처 : http://jabstorage.tistory.com/23

 * 웹뷰를 이용하여 내 앱으로 웹화면을 띄우려고 합니다.


구글링으로 여러가지를 찾아보다가 제가 사용할 웹사이트에 맞게 제작했습니다.

혹시 같은 방식이라면 참고하시기 바랍니다~!!


1. 먼저 매니페스트 파일을 수정합니다. (AndroidManifest.xml)


* 인터넷을 사용하기 위한 설정입니다.

<uses-permission android:name="android.permission.INTERNET" />

* 어플의 아이콘을 변경해보았습니다.

android:icon="원하는 이미지"

* 예시를 보여드리겠습니다.

android:icon="@drawable/icon"

* 사진은 여기에 저장하고 불러옵니다.



* 원하는 어플의 이름을 등록합니다.

android:label="원하는이름"

* 전체 소스입니다.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.changmin.sellee">

<uses-permission android:name="android.permission.INTERNET" />

<application
android:allowBackup="true"
android:icon="@drawable/icon"
android:label="원하는 어플이름"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.AppCompat.NoActionBar">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>

</manifest>


2. xml소스입니다. (activity_main.xml)


* webview를 webview라는 id로 등록하고 match_parent로 화면전체를 채워줍니다

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:foregroundGravity="center"
android:orientation="vertical"
tools:context="com.example.changmin.sellee.MainActivity">

<WebView
android:id="@+id/webview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:foregroundGravity="center" />
</android.support.constraint.ConstraintLayout>


3. java소스입니다. (MainActivity.java)


* 웹뷰를 선언합니다.

public class MainActivity extends AppCompatActivity {
WebView webView;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);


webView = (WebView) findViewById(R.id.webview);
}

* WebSettings를 사용해서 webview설정을 추가해줍니다.

public class MainActivity extends AppCompatActivity {
WebView webView;
WebSettings webSettings;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

webView = (WebView) findViewById(R.id.webview);

webSettings = webView.getSettings();
webSettings.setJavaScriptEnabled(true); // javascript를 실행할 수 있도록 설정
webSettings.setSupportMultipleWindows(true); // 여러개의 윈도우를 사용할 수 있도록 설정
webSettings.setJavaScriptCanOpenWindowsAutomatically(true); // javascript window.open()을 사용할 수 있도록 설정
}


* setWebChromeClient와 setWebVeiwClinet함수를 추가해줍니다. 그리고 원하는 url로 연결해줍니다.

public class MainActivity extends AppCompatActivity {
WebView webView;
WebSettings webSettings;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

CookieManager cookieManager = CookieManager.getInstance();
cookieManager.setAcceptCookie(true);

webView = (WebView) findViewById(R.id.webview);

webSettings = webView.getSettings();
webSettings.setJavaScriptEnabled(true); // javascript를 실행할 수 있도록 설정
webSettings.setSupportMultipleWindows(true); // 여러개의 윈도우를 사용할 수 있도록 설정
webSettings.setJavaScriptCanOpenWindowsAutomatically(true); // javascript window.open()을 사용할 수 있도록 설정

webView.setWebChromeClient(new MyWebChromeClient());
webView.setWebViewClient(new MyWebViewClient());

webView.loadUrl("첫시작 url (접속하고싶은 url을 입력합니다^^)");

}


★ 여기까지가 웹뷰의 기본입니다. 나머지는 풀소스를 올리겠습니다.

  

public class MainActivity extends AppCompatActivity {
WebView webView;
WebView childView;
WebSettings webSettings;
long lastTimeBackPressed;
ProgressDialog progressDialog;
String myURL = "";
String childURL = "";
int count = 1;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

CookieManager cookieManager = CookieManager.getInstance();
cookieManager.setAcceptCookie(true);

webView = (WebView) findViewById(R.id.webview);

webSettings = webView.getSettings();
webSettings.setJavaScriptEnabled(true);
webSettings.setSupportMultipleWindows(true);
webSettings.setGeolocationEnabled(true);
webSettings.setJavaScriptCanOpenWindowsAutomatically(true);
webSettings.setBuiltInZoomControls(true);

progressDialog = new ProgressDialog(MainActivity.this, AlertDialog.THEME_DEVICE_DEFAULT_LIGHT);

webView.setWebChromeClient(new MyWebChromeClient());
webView.setWebViewClient(new MyWebViewClient());

webView.loadUrl("첫시작 url (접속하고싶은 url을 입력합니다^^)");
}

@Override
protected void onResume() {
super.onResume();

webView.resumeTimers();
}

@Override
protected void onPause() {
super.onPause();

webView.pauseTimers();
}

private class MyWebViewClient extends WebViewClient {
public static final String INTENT_URI_START = "intent:";
public static final String INTENT_FALLBACK_URL = "browser_fallback_url";
public static final String URI_SCHEME_MARKET = "market://details?id=";

@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
if (url.toLowerCase().startsWith(INTENT_URI_START) || !url.toLowerCase().startsWith("http")) {
Intent parsedIntent = null;
try {
parsedIntent = Intent.parseUri(url, 0);
startActivity(parsedIntent);
} catch(ActivityNotFoundException | URISyntaxException e) {
return doFallback(view, parsedIntent);
}
} else {
view.loadUrl(url);
}
return true;
}

private boolean doFallback(WebView view, Intent parsedIntent) {
if (parsedIntent == null) {
return false;
}
String fallbackUrl = parsedIntent.getStringExtra(INTENT_FALLBACK_URL);
if (fallbackUrl != null) {
view.loadUrl(fallbackUrl);
return true;
}
String packageName = parsedIntent.getPackage();
if (packageName != null) {
startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(URI_SCHEME_MARKET + packageName)));
return true;
}
return false;
}

@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
super.onPageStarted(view, url, favicon);

myURL = url;

progressDialog.setTitle("제목");
progressDialog.setMessage("Loading");
progressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
progressDialog.setCancelable(true);
progressDialog.setCanceledOnTouchOutside(false);
progressDialog.show();
}

@Override
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);

progressDialog.dismiss();


myURL = url;

if(!childURL.equals("") || !childURL.equals(null) || !childURL.isEmpty()) {
childURL = "";
webView.removeView(childView);
}
}
}

private class MyWebChromeClient extends WebChromeClient {
@Override
public boolean onCreateWindow(WebView view, boolean isDialog, boolean isUserGesture, Message resultMsg) {

count = 1;
webView.removeAllViews();

childView = new WebView(MainActivity.this);
childView.getSettings().setJavaScriptEnabled(true);
childView.setWebChromeClient(this);

childView.setWebViewClient(new WebViewClient() {
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
super.onPageStarted(view, url, favicon);

childURL = url;

if (count == 1) {
count = 0;
if (childURL.contains("웹브라우저로 띄우고싶은 url")) {
webView.removeView(childView);
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(childURL));
startActivity(intent);
childURL="";
}
}
}

@Override
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
count = 1;
}
});

childView.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
webView.addView(childView);
WebView.WebViewTransport transport = (WebView.WebViewTransport) resultMsg.obj;
transport.setWebView(childView);
resultMsg.sendToTarget();
return true;
}

@Override
public void onCloseWindow(WebView window) {
super.onCloseWindow(window);
}

@Override
public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
final JsResult finalRes = result;
myURL = url;
//AlertDialog 생성
new AlertDialog.Builder(view.getContext(), AlertDialog.THEME_DEVICE_DEFAULT_LIGHT)
.setMessage(message)
.setPositiveButton("확인", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
finalRes.confirm();
}
})
.setCancelable(false)
.create()
.show();
return true;
}

@Override
public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
final JsResult finalRes = result;
myURL = url;
//AlertDialog 생성
new AlertDialog.Builder(view.getContext(), AlertDialog.THEME_DEVICE_DEFAULT_LIGHT)
.setMessage(message)
.setPositiveButton("확인", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
finalRes.confirm();
}
})
.setCancelable(false)
.create()
.show();
return true;
}

@Override
public boolean onJsConfirm(WebView view, String url, String message, JsResult result) {
final JsResult finalRes = result;
myURL = url;
//AlertDialog 생성
new AlertDialog.Builder(view.getContext(), AlertDialog.THEME_DEVICE_DEFAULT_LIGHT)
.setMessage(message)
.setPositiveButton("확인", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
finalRes.confirm();
}
})
.setNegativeButton("취소", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
finalRes.cancel();
}
})
.setCancelable(false)
.create()
.show();
return true;
}
}
@Override
public void onBackPressed() {
if (myURL.equals("첫시작 url") && (childURL.equals("") || childURL.equals(null) || childURL.isEmpty())) {
if (System.currentTimeMillis() - lastTimeBackPressed < 2000) {
finish();
return;
}
Toast.makeText(this, "'뒤로' 버튼을 한번 더 누르면 종료됩니다.", Toast.LENGTH_SHORT).show();
lastTimeBackPressed = System.currentTimeMillis();
} else if (webView.canGoBack() && (childURL.equals("") || childURL.equals(null) || childURL.isEmpty())) {
webView.goBack();
} else if(!childURL.equals("") || !childURL.equals(null) || !childURL.isEmpty()) {
webView.removeView(childView);
childURL="";
webView.reload();
}
}
}


MyWebViewClient 클래스의 shouldOverrideUrlLoading

부분은 밑의사이트를 참고했습니다~


출처 : 

https://github.com/nhnent/tui.app-loader/wiki/%EC%95%88%EB%93%9C%EB%A1%9C%EC%9D%B4%EB%93%9C-%EC%9B%B9%EB%B7%B0%EC%97%90%EC%84%9C-%EC%95%B1%EB%A1%9C%EB%8D%94%EA%B0%80-%EC%88%98%ED%96%89%EB%90%98%EC%A7%80-%EC%95%8A%EB%8A%94-%EC%9D%B4%EC%8A%88


하나하나 세세한 설명은 못드렸습니다ㅠㅠ

중간에 한번 날려먹어서 그냥 풀소스로 공개합니다~!!

제가 3주정도 삽질하면서 만든 소스입니다. 이렇게 간단한걸 3주씩이나...털썩...

여러가지 방식이 있지만 저는 url을 받아와서 해당url에맞게 구동되도록했습니다.

다른분들은 어떻게 하셨는지 궁금하기도 합니다ㅎㅎㅎ

url을 변경하시고 실행하면 잘될거라고 믿습니다. 궁금한점은 댓글달아주세요^^


Illegal mix of collations (latin1_swedish_ci,IMPLICIT) and (utf8_general_ci,COERCIBLE) for operation


mysql에서 다음과 같은 오류가 발생했다면 character set의 문제라고 볼수 있습니다.


1. mysql에 접속합니다.


[root@localhost~]# mysql -uroot -p

Enter password:


2. 데이터 베이스를 선택합니다.


[root@localhost~]# use 내가사용할db이름;


예시)

[root@localhost~]# use changmin;


3. 테이블의 collation을 변경해줍니다.


[root@localhost~]# alter table 테이블이름 default character set utf8 collate utf8_general_ci;


예시)

[root@localhost~]# alter table changmin default character set utf8 collate utf8_general_ci;


 utf8이 아닌 다른 캐릭터셋을 변경하고 싶다면 다른 캐릭터셋을 입력하시면 됩니다.


+ Recent posts