[Javacript] Array remove Index 배열 삭제

Posted by Find my true self Fimtrus
2014.08.08 14:28 Programming/Web

자바스크립트의 Array 객체 ( [] 대괄호를 통해 생성 또는 new Array 로 생성하는 경우) 에는 배열 객체를 삭제하거나, 인덱스를 입력받아 삭제하는 function이 없다.


물론 splice나 split 등의 function을 이용하면 되긴 하지만, 


익숙하지 않은 사람들에게는 상당히 귀찮은 작업이다.


그래서 function을 미리 만들어 놓고 사용하는데,


이것도 마찬가지로 웹페이지가 로드될 때 코드를 추가해주면


모든 Array가 아래 function들을 가지게 된다.



Array.prototype.remove = function() {
    var what, a = arguments, L = a.length, ax;
    while (L && this.length) {
        what = a[--L];
        while ((ax = this.indexOf(what)) !== -1) {
            this.splice(ax, 1);
        }
    }
    return this;
};

Array.prototype.removeIndex = function () {
	var a = arguments, length = a.length;
	this.splice ( a[--length], 1 );
	return this;
}


추가한뒤 array 객체를 보면 remove와 removeIndex가 딱!



이 댓글을 비밀 댓글로

[Javascript] Date format 추가.

Posted by Find my true self Fimtrus
2014.08.08 14:15 Programming/Web

자바스크립트에서 Date 객체에 대한 format function을 지원해 주지 않는다.


해서..... 포멧을 만들려면 상당히 귀찮은 작업을 해야하는데,


아래 코드를 웹페이지 로드될 때, 추가해주면 format function 을 사용할 수 있다.

Date.prototype.format = function(f) { if (!this.valueOf()) return " "; var weekName = ["일요일", "월요일", "화요일", "수요일", "목요일", "금요일", "토요일"]; var d = this; return f.replace(/(yyyy|yy|MM|dd|E|HH|hh|mm|SSS|ss|a\/p)/g, function($1) { switch ($1) { case "yyyy": return d.getFullYear(); case "yy": return (d.getFullYear() % 1000).zf(2); case "MM": return (d.getMonth() + 1).zf(2); case "dd": return d.getDate().zf(2); case "E": return weekName[d.getDay()]; case "HH": return d.getHours().zf(2); case "hh": return ((h = d.getHours() % 12) ? h : 12).zf(2); case "mm": return d.getMinutes().zf(2); case "ss": return d.getSeconds().zf(2); case "a/p": return d.getHours() < 12 ? "AM" : "PM"; case "SSS": var temp = "0000"; var milliSeconds = d.getMilliseconds(); temp = temp + milliSeconds; temp = temp.substring ( temp.length - 3 , temp.length); return temp; default: return $1; } }); };


이 댓글을 비밀 댓글로

[Android, Hybrid] 앱에서 파일 다운로드 구현. URL File Download.

Posted by Find my true self Fimtrus
2014.07.10 15:09 Programming/Android

웹브라우저를 이용해서 파일을 다운로드 받을 경우, 다운로드매니저를 통해 받게 된다.(진저브레드 이상)


앱에서 다운로드 매니저를 사용하여 파일 다운로드 받는 방법을 쓰고자 한다.


앱이나 하이브리드나 다운로드 하는 방법은 동일하다.


다운로드 매소드, 그리고 다운로드매니저로부터 액션을 받을 수 있는 리시버를 만드면 끝.!


Download Method

private DownloadManager mDownloadManager; //다운로드 매니저. 
private int mDownloadQueueId; //다운로드 큐 아이디..
private String mFileName ; //파일다운로드 완료후...파일을 열기 위해 저장된 위치를 입력해둔다.
/**
 * @param url : 파일을 다운로드할 url.
 */
public void download(String url) {
	if (mDownloadManager == null) {
		mDownloadManager = (DownloadManager) mContext.getSystemService(Context.DOWNLOAD_SERVICE);
	}
	Request request = new DownloadManager.Request( Uri.parse(url) );
	request.setTitle("==타이틀==");
	request.setDescription("==설명==");
	List<string> pathSegmentList = uri.getPathSegments();
	Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS + "/temp").mkdirs();  //경로는 입맛에 따라...바꾸시면됩니다.
	request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS + "/temp/", pathSegmentList.get(pathSegmentList.size()-1) );
	mFileName = pathSegmentList.get(pathSegmentList.size()-1);

	mDownloadQueueId = mDownloadManager.enqueue(request);
}



다운로드관련 인텐트를 받을 리시버를 만들고, 등록시켜주면 끝!

(onResume 에서 등록, onPause에서 해제를 추천한다.)


Complete Receiver

/**
 * 다운로드 완료 액션을 받을 리시버.
 */
private BroadcastReceiver mCompleteReceiver = new BroadcastReceiver() {
	@Override
	public void onReceive(Context context, Intent intent) {
		String action = intent.getAction();
		if (action.equals(DownloadManager.ACTION_DOWNLOAD_COMPLETE)) {
			Toast.makeText(context, "Complete.", Toast.LENGTH_SHORT).show();
			Intent intent1 = new Intent();
			intent1.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
			intent1.setAction(android.content.Intent.ACTION_VIEW);
			intent1.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);

			String localUrl = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
					+ "/temp/" + mFileName; //저장했던 경로..
			String extension = MimeTypeMap.getFileExtensionFromUrl(localUrl);
			String mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);

			File file = new File(localUrl);
			intent1.setDataAndType(Uri.fromFile(file), mimeType);
			try {
				startActivity(intent1);
			} catch (ActivityNotFoundException e) {
				Toast.makeText(NPF.this, "Not found. Cannot open file.", Toast.LENGTH_SHORT).show();
				e.printStackTrace();
			}
		}
	}
};


@Override
protected void onPause() {
	super.onPause();
	unregisterReceiver(mCompleteReceiver);
}

@Override
protected void onResume() {
	super.onResume();
	IntentFilter completeFilter = new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE);
	registerReceiver(mCompleteReceiver, completeFilter);
}


하이브리드 앱의 경우 JavaInterface에 해당 method를 넣어주면 되고, 일반적인 앱에서 사용할 경우에는


유틸 같은 곳에 추가하여 사용하면된다.(물론 해당 method를 조금 수정해서 context를 받도록 해야할 것이긴 하지만)



이 댓글을 비밀 댓글로
    • 2014.07.14 09:31
    비밀댓글입니다
    • 조금의(?) 수정
    • 2015.10.21 22:14
    직접 내용을 수정하여 제목, 내용 커스텀 + 경로 지정 가능하게 해보고
    일반 앱에 바로 복붙하고 Import만 하면 가능하게 대충 수정을...

    public void download(String url, String title, String des, String path) {
    DownloadManager mDownloadManager = null; //다운로드 매니저.
    int mDownloadQueueId; //다운로드 큐 아이디..
    String mFileName ; //파일다운로드 완료후...파일을 열기 위해 저장된 위치를 입력해둔다.
    if (mDownloadManager == null) {
    mDownloadManager = (DownloadManager) getApplicationContext().getSystemService(Context.DOWNLOAD_SERVICE);
    }
    DownloadManager.Request request = new DownloadManager.Request( Uri.parse(url) );
    request.setTitle(title);
    request.setDescription(des);
    List<String> pathSegmentList = Uri.parse(url).getPathSegments();
    Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS + "/" + path).mkdirs(); //경로는 입맛에 따라...바꾸시면됩니다.
    request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS + "/" + path + "/", pathSegmentList.get(pathSegmentList.size()-1) );
    mFileName = pathSegmentList.get(pathSegmentList.size()-1);

    mDownloadQueueId = (int) mDownloadManager.enqueue(request);
    }

[Chrome] 크롬 강력한 새로 고침 단축키. 클리어 캐시

Posted by Find my true self Fimtrus
2014.07.09 13:40 Programming

작업 중에 CSS나 html을 수정하였을 경우, 캐시로 인해 새로고침이 정상적으로 이루어 지지 않을 때가 많다.


매번 설정에 들어가서 캐시를 지워야 했지만 크롬에는 강력한 새로고침이라는 기능이 있다.

(옛날 버전에는 안될 수도 있으니,, 최신버전으로..)


마우스 우측버튼 클릭, 개발자 콘솔(또는 검사)를 열고, 새로고침을 롱클릭하게 되면 하위메뉴들이 나오는데,



그중 "강력한 새로고침"과, "캐시비우기 및 강력 새로고침"이라는 선택창이 있다. (윈도우 버전에서만 보인다) 맥 버전에도 추가 됬음!


이중 하나를 선택하면 화면이 리로드 되고, 이미지나 css에서 304 값을 받는 경우는 없을 것이다.


단축키로 사용하고 싶으면  ctrl + shift + r 을 눌리게 되면 강력한 새로고침이 동작한다.


맥에서는 롱클릭하여도 반응이 없고, command + shift + r 단축키를 눌리면 강력한 새로고침이 된다!!

이 댓글을 비밀 댓글로
  1. 감사합니다
  2. 오오 이런 방법이! 감사합니다
    • 2017.08.29 01:30
    오 감사합니다!
    • 2017.12.11 13:54
    비밀댓글입니다
    • 묵사마
    • 2018.05.26 21:13
    좋은 정보 감사ㅋ

[Android] 안드로이드 TextView 글자에 색깔 넣기. 부분적으로 색깔 넣기. html 스타일 적용.

Posted by Find my true self Fimtrus
2014.06.24 16:49 Programming/Android

Spannable 객체를 이용할 경우.


24와 37은 인덱스 값. 24~37 사이에 있는 글자만 변경된다.

Spannable descriptionString = (Spannable) mDescriptionTextView.getText();
descriptionString.setSpan(new StyleSpan(Typeface.BOLD), 24, 37, SpannableString.SPAN_EXCLUSIVE_EXCLUSIVE);
descriptionString.setSpan(new ForegroundColorSpan(getResources().getColor(R.color.guide_message_highlight)), 24, 37, SpannableString.SPAN_EXCLUSIVE_EXCLUSIVE);
		



Html을 이용할 경우.


html에 대해 잘 아시는 분들이면 HTML을 이용하여 글자를 꾸미는게 더 효과적일 수 있다.

(단, 기본적인 것들은 정상적으로 보이지만, 몇몇 태그, css들은 웹과 동일하게 나오지는 않는다)

//HTML
mDescriptionTextView.setText( Html.fromHtml("<div>Te<span style="color:red;">st</span> Code</div>") )"
//
이 댓글을 비밀 댓글로

[Android] TextView, 글자에 외곽선(Border, outline) 넣기

Posted by Find my true self Fimtrus
2014.06.24 16:41 Programming/Android

안드로이드에서 외곽선을 넣기 위해선 TextView를 Customizing해야한다.(다른방법이 있을 수도..;)


TextView를 상속받아서 뷰를 조금 수정해야하고,


편하게 사용하기 위해 attr을 선언해주면 된다.


먼저 TextView를 상속 받은 OutlineTextView를 만든다.


public class OutlineTextView extends TextView {

	private boolean hasStroke = false;
	private float mStrokeWidth = 0.0f;
	private int mStrokeColor;

	public OutlineTextView(Context context) {
		super(context);
	}

	public OutlineTextView(Context context, AttributeSet attrs) {
		super(context, attrs);
		initView(context, attrs);
	}

	public OutlineTextView(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);
		initView(context, attrs);
	}

	@Override
	protected void onDraw(Canvas canvas) {
		if (hasStroke) {
			ColorStateList states = getTextColors();
			getPaint().setStyle(Style.STROKE);
			getPaint().setStrokeWidth(mStrokeWidth);
			setTextColor(mStrokeColor);
			super.onDraw(canvas);

			getPaint().setStyle(Style.FILL);
			setTextColor(states);
		}
		super.onDraw(canvas);
	}

	private void initView(Context context, AttributeSet attrs) {
		TypedArray typeArray = context.obtainStyledAttributes(attrs, R.styleable.OutlineTextView);
		hasStroke = typeArray.getBoolean(R.styleable.OutlineTextView_textStroke, false);
		mStrokeWidth = typeArray.getFloat(R.styleable.OutlineTextView_textStrokeWidth, 0.0f);
		mStrokeColor = typeArray.getColor(R.styleable.OutlineTextView_textStrokeColor, 0xffffffff);
	}
}


그리고 attr을 선언해준다. xml 파싱과정에서 관련된 value들을 받기 위해선 필수!

(\values\attrs.xml 에 선언해주면 된다.)


아래와 같이 선언해 놓으면 OutlineTextView를 사용할 때 해당 attribute를 사용할 수 있게 된다.

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="OutlineTextView">
        <attr name="textStroke" format="boolean"/>
        <attr name="textStrokeWidth" format="dimension"/>
        <attr name="textStrokeColor" format="color"/>
    </declare-styleable>
</resources>


마지막으로 layout에 추가해주면 된다.

* xmlns의 이름 부분에는 xml에서 사용할 이름을 지정하면 된다.

* 패키지명은 반드시 앱 패키지 명과 동일해야 한다.

<linearlayout xmlns:android="http://schemas.android.com/apk/res/android" 
	xmlns:@@이름@@="http://schemas.android.com/apk/res/@@ 패키지명 @@" 
	android:layout_width="fill_parent" 
	android:layout_height="fill_parent" 
	android:orientation="vertical">
	<com.fimtrus.flicktalk.view.OutlineTextView
		android:id="@+id/outlineTextView1" 
		android:layout_width="match_parent" 
		android:layout_height="wrap_content" 
		android:gravity="center_horizontal" 
		android:layout_above="@+id/relativeLayout1" 
		android:layout_centerhorizontal="true" 
		android:textsize="50sp" 
		android:layout_marginbottom="20dp" 
		android:text=" " 
		android:textcolor="@color/white" 
		android:padding="5dp" 
		android:singleline="false" 
		@@이름@@:textstroke="true" 
		@@이름@@:textstrokecolor="#000000" 
		@@이름@@:textstrokewidth="7.0">
	</com.fimtrus.flicktalk.view.OutlineTextView>
</linearlayout>


이 댓글을 비밀 댓글로
    • 2015.12.11 14:10
    비밀댓글입니다
  1. 조금더 보태자면 <attr name="textStrokeWidth" format="float">
    textStrokeWidth를 dimension으로 변경하고,
    mStrokeWidth = typeArray.getFloat(R.styleable.OutlineTextView_textStrokeWidth, 0.0f);
    getFloat -> getDimension으로 하시면 dp 값으로 적용 가능합니다.

[Android] 안드로이드에서 httpPost multiparts로 전송하기(멀티파트, 멀티파츠)

Posted by Find my true self Fimtrus
2014.06.24 16:10 Programming/Android

안드로이드에서는 httpClient의 버전이 낮아 multiparts를 지원하지 않는다.


보내고 싶다면 httpConnection을 열고, buffer를 통해서 전송해야 하는데,


만들기가 여간 귀찮은 것이 아니다.


apache에서 httpComponents를 제공하고 있는데, 해당 라이브러리를 사용하면,


별다른 구현을 하지 않더라도 multiparts로 전송할 수 있게 된다.


물론 약 1메가 정도의...라이브러리가 추가되긴 하지만..



아파치 사이트로 이동하여 httpClient 최신버전을 다운받는다.(전 4.3.X 버전 사용중)


Download Page(Click)


당연히 jar 파일이 필요하기 때문에 binary를 클릭해서 받아준다.




다운후 압축을 해제하게 되면, examples, lib, tutorial 폴더가 나오는데,


lib에 있는 jar파일들을 안드로이드 프로젝트의 libs 폴더로 복사하면 된다.


이제 multiparts로 전송하기 위한 준비는 다 되었고, 아래의 코드를 이용하여 보내면 된다.


//Multipart 객체를 선언한다.
MultipartEntityBuilder builder = MultipartEntityBuilder.create() //객체 생성...
	.setCharset(Charset.forName("UTF-8")) //인코딩을 UTF-8로.. 다들 UTF-8쓰시죠?
	.setMode(HttpMultipartMode.BROWSER_COMPATIBLE); 
builder.addPart("content",  new FileBody(== 파일객체 ==) ); //빌더에 FileBody 객체에 인자로 File 객체를 넣어준다.
builder.addPart("testKey",  "testData" ); //스트링 데이터..

HttpClient client = AndroidHttpClient.newInstance("Android");

HttpPost post = new HttpPost(== url ==); //전송할 URL 
try {
	post.setEntity(builder.build()); //builder.build() 메쏘드를 사용하여 httpEntity 객체를 얻는다.
	HttpResponse httpRes;
	httpRes = client.execute(post);
	HttpEntity httpEntity = httpRes.getEntity();
	if (httpEntity != null) {
		response = EntityUtils.toString(httpEntity);
	}
} catch (UnsupportedEncodingException e) {
} catch (ClientProtocolException e1) {
} catch (IOException e1) {
} catch (ParseException e) {
}


위와 같이 전송하게 되면, 내부적으로 Stream을 이용하여 서버로 전송하게 되어


따로 작업을 하지 않더라도 multiparts 형태로 전송할 수 있다.


이 댓글을 비밀 댓글로
    • huhu
    • 2015.12.19 22:16
    예제 소스 파일좀 부탁드리겠습니다. 아무리해도 적용이 안됩니다.;

[Android, Hybrid]openFileChooser 킷캣에서 동작하지 않는 문제( openFileChooser Kitkat bug )

Posted by Find my true self Fimtrus
2014.06.24 15:20 Programming/Hybrid App

[2014.10.31]


openFileChooser가 문제군요...ㅎ


코멘트 잘 읽어 보았습니다.


킷캣에서 파일업로드 버그는...웹적인 요소로 해결할 수 있는 방안은 없습니다.


그래서 이런방법도 있다는 것을 알려드리기 위해서 글을 쓴건데 더욱 혼란스럽게 해드린 것 같네요.


제가 아래 글에 빨간색으로 표시를 해뒀는데...이 방법은 파일에 대한 정보를 네이티브 단에 저장해 놓고


전송에 대한 이벤트를 받으면 NATIVE 단에서 서버로 전송하게 됩니다.


서버로 전송하는 부분은 각자 프로젝트에 맞게...직접 구현하셔야되구요.


멀티파츠로 전송하신다면 제 블로그 찾아보시면 네이티브에서 멀티파츠로 전송하는 방법이라고 있습니다.


그부분 참고하시면 됩니다.


아래 발췌된 부분은 참고용이지 복사 붙여넣기를 한다고해서 동작하는 코드는 아닙니다.


그래서 간단한 샘플을 하나 만들었습니다.


아래 코드가 잘 이해안되시는 분들은 다운받아서 확인해보시면 되겠습니다.



imageupload_sample.zip



================================================================


openFileChooser 안드로이드 4.4 킷캣(kitkat) 이하에서는 openFileChooser를 


WebChromeClient class 에서 구현을 하게되면 정상적으로 callback을 받았었다.


그렇기 때문에 네이티브에서 별다른 작업(물론...openFileChooser에 대한 작업은 해줘야한다) 없이도


웹 기능만으로도 파일을 서버로 전송(multiparts 같은...)할 수 있었다. 


하지만 4.4 킷캣 버전부터 해당 메쏘드를 호출하지 않기 때문에, 


하이브리드 앱의 경우 기존의 방법으로는  카메라 및 갤러리 호출, 그리고 전송을 할 수 없다.


해당문제를 해결하기 위해서 페이지가 웹뷰를 통해 보여지고 있고, 킷캣일 경우에는 


JavascriptInterface를 통해 카메라 및 갤러리를 호출 할  수 있도록 대응하였다.


sequence는 다음과 같다.


웹페이지에서 input tag 클릭 -> 클릭이벤트를 통해 window.Android.open 호출(JavascriptInterface) 

-> WebViewImageUploadHelper 를 통해 갤러리 또는 카메라 호출 -> 선택된 파일의 썸네일을 화면에 보여주고, 파일 정보저장

-> 전송 버튼 클릭시 Native 단에서 데이터 전송


* 썸네일 보여주는 방법을 모르시면 (Click!)

* JavascriptInterface 에 대해 모르시면 링크 참조(Click!)


JavascriptInterface 를 만든다. (필자는 open 과 send라는 메쏘드를 만들었다)

만든 후에는 웹뷰에 JavascriptInterface를 연결한다.

public class WebViewInterface {

	private WebView mAppView;
	private Activity mContext;

	/**
	 * 생성자.
	 * @param activity : context
	 * @param view : 적용될 웹뷰
	 */
	public WebViewInterface(Activity activity, WebView view) {
		mAppView = view;
		mContext = activity;
	}
	/**
	 * 갤러리 및 카메라 열기.
	 * @param key : 어떤 input box를 클릭했는지 구분을 위한 Unique한 key.
	 * @param thumbnailId : 선택된 이미지의 Thumbnail을 보여줄 division id.
	 */
	@JavascriptInterface
	public Uri open(String key, String thumbnailId ) {
		WebViewImageUploadHelper.getInstance(mContext, mAppView).open(key, thumbnailId);
		return null;
	}
	/**
	 * 파일 전송.
	 */
	@JavascriptInterface
	public File send(String key) {
		WebViewImageUploadHelper.getInstance(mContext, mAppView).send(type);
		return null;
	}
}


이미지 업로드를 담당할 WebViewImageUploadHelper 클래스를 만든다.


해당 메쏘드에서 카메라, 갤러리 호출 및 파일 전송을 담당하게 된다.


Singleton으로 제작.

public class WebViewImageUploadHelper {

	public final static int INTENT_CALL_GALLERY = 3001; //갤러리 requestCode
	public final static int INTENT_CALL_CAMERA = 4001; //카메라 requestCode

	private static WebViewImageUploadHelper mHelper;
	private Context mContext;

	private File mContents; //파일객체.

	/**
	 * 생성자. 외부에서 불릴일은 없다.
	 * @param context : context
	 */
	private WebViewImageUploadHelper(Context context) { 
		mContext = context;
		mDialog = new CommonDialogs(context);
		mDialogCallbackListener = new DialogInterface.OnClickListener() {

			@Override
			public void onClick(DialogInterface dialog, int which) {

				if (which == 0) {
					callCamera(INTENT_CALL_CAMERA);//카메라 호출.
				} else if (which == 1) {
					callGallery(INTENT_CALL_GALLERY); //갤러리 호출.
				}
			}
		};
	}
	/**
	 * 생성자. WebViewImageUploadHelper에 접근을 위해서는 이 메쏘드를 통해야 한다.
	 * @param context
	 *            : activity context
	 * @return
	 */
	public static final WebViewImageUploadHelper getInstance(Context context, WebView webView) {

		if (mHelper == null) {
			mHelper = new WebViewImageUploadHelper(context);
			mHelper.mWebView = webView;
		}
		return mHelper;

	}
	/**
	 * 갤러리를 호출한다.
	 */
	public void callGallery(int requestCode) {
		Intent intent = new Intent();
		intent.setAction(Intent.ACTION_PICK);
		intent.setType("image/*");
		((MainActivity) mContext).startActivityForResult(Intent.createChooser(intent, "File Chooser"), requestCode);
	}

	/**
	 * 카메라를 호출한다.
	 */
	public void callCamera(int requestCode) {
		Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); //이미지 캡처를 위한 인텐트

		File directory = new File(Constant.DIRECTORY_PHOTO_PATH); //파일이 저장된 디렉토리.

		if (!directory.exists()) {
			directory.mkdir(); //디렉토리가 없으면 만들고.
		}
		mTempFile = new File(directory, "photo_" + new Date().getTime() + ".jpg"); //저장될 파일을 선언.
		intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(mTempFile)); //파일정보를 인텐트에 함께 넣어준다.
		// intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
		((MainActivity) mContext).startActivityForResult(intent, requestCode);
	}
	/**
	 * file 정보를 base64로 인코딩한다.
	 * @param file : target file
	 * @return
	 */
	public String fileToString(File file) {
		
		String fileString = new String();
		FileInputStream inputStream = null;
		ByteArrayOutputStream byteOutStream = null;

		try {
			inputStream = new FileInputStream(file);
			byteOutStream = new ByteArrayOutputStream();

			int len = 0;
			byte[] buf = new byte[1024];
			while ((len = inputStream.read(buf)) != -1) {
				byteOutStream.write(buf, 0, len);
			}

			byte[] fileArray = byteOutStream.toByteArray();
			fileString = new String(Base64.encodeBase64(fileArray));

		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			try {
				inputStream.close();
				byteOutStream.close();
			} catch (Exception e) {
				e.printStackTrace();
				return null;
			}
		}
		return fileString;
	}
	/**
	 * 화면 세팅을 위해. mimetype을 알아낸다.
	 * @param uri
	 * @return
	 */
	public String getMimeType(Uri uri) {
		ContentResolver cR = mContext.getContentResolver();
		MimeTypeMap mime = MimeTypeMap.getSingleton();
		String type = cR.getType(uri);
//		String type = mime.getExtensionFromMimeType(cR.getType(uri));
	    return type;
	}
	/**
	 * 갤러리로부터 받은 파일정보를 통해 웹뷰 화면을 업데이트 한다.
	 * @param uri : file path
	 */
	public final void updateContent(Uri uri) {
		if (uri == null) {
			return;
		}
		
		File file = uriToFile(uri);
		
		String type = getMimeType(uri);
		
		// 파일 path 저장
		if (mKey.equals("contents")) {
			mContents = file;
		} else if (mKey.equals("contents2")) {
			mContents2 = file;
		} else if (mKey.equals("contents3")) {
			mContents3 = file;
		} else if (mKey.equals("contents4")) {
			mContents4 = file;
		}

		// 웹뷰로 썸네일 보냄.
		updateImage(file, type);

	}
	/**
	 * 카메라로부터 받은 파일정보를 통해 웹뷰 화면을 업데이트 한다.
	 * @param uri : file path
	 */
	public final void updateContent() {
		Uri uri = Uri.fromFile(mTempFile);
		
		// 미디어 스캐닝 실행.
		((MainActivity) mContext).sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, uri));
		
		File file = mTempFile;
		
		String type = getMimeType(uri);
		
		// 파일 path 저장
		if (mKey.equals("contents")) {
			mContents = file;
		} else if (mKey.equals("contents2")) {
			mContents2 = file;
		} else if (mKey.equals("contents3")) {
			mContents3 = file;
		} else if (mKey.equals("contents4")) {
			mContents4 = file;
		}
		
		// 웹뷰로 썸네일 보냄.
		updateImage(file, type);
		
		mTempFile = null;
		
	}

	/**
	 * 썸네일을 보여준다.
*/ private void updateImage(final File file,final String type) { //Task를 실행시킨다. Thread를 돌리지 않으면, ANR로 앱이 강제종료될 수 있기 때문. new AsyncTask() { @Override protected String doInBackground(Void... params) { String mimeType = type; String base64EncodedImage = fileToString(file); return "javascript:$(" + "\"#" + mThumbnailId + "\"" + ").attr(\"src\", " + "\"data:" + mimeType + ";base64," + base64EncodedImage + "\");"; } @Override protected void onPostExecute(String result) { super.onPostExecute(result); mWebView.loadUrl(result); } }.execute(); } }


여기서 상당히 중요한데, 카메라와 갤러리를 호출하였기 때문에


선택되었을 경우 onActivityResult를 통해 uri가 리턴된다.(카메라 : callCamera 메쏘드에서 선언한 file, 갤러리 : uri )


그리고 킷캣부터 Document(?)라는 앱이 추가되어서, 기존의 데이터 Scheme와는 전혀 다르게 리턴해주기 때문에


거기에 따른 처리도 함께 들어가야한다.

public class MainActivity {
	...... 생략 ........
	@Override
	protected void onActivityResult(int requestCode, int resultCode, Intent data) {

		super.onActivityResult(requestCode, resultCode, data);

		if (resultCode == Activity.RESULT_OK) {
			if ( requestCode == WebViewImageUploadHelper.INTENT_CALL_GALLERY ) {

				Uri result = data == null || resultCode != RESULT_OK ? null : data.getData();
				WebViewImageUploadHelper.getInstance(this, mWebView).updateContent(result);

				return;
			} else if ( requestCode == WebViewImageUploadHelper.INTENT_CALL_CAMERA) {
				WebViewImageUploadHelper.getInstance(this, mWebView).updateContent();
			}
		}
	}
}


그리고 웹페이지의 input tag에 클릭이벤트를 추가한다.

$("input").delegate("#inputFileUpload", 'click', function (e) {

	window.Android.open( "contents", "imgThumbnail"); //"contents" : key , "imgThumbnail" : Thumbnail 이 표시될 div
	
});



클래스 생성 및 이벤트 추가 완료 후에, input tag를 클릭하게 되면, 해당 네이티브 로직을 통해 파일을 읽어 들이게 되고,


이미지의 경우 img tag에 보여주게 된다.


궁금한 사항이나...이상한 부분이 있으면 댓글 달아주세요.


파일 전송을 위해서는 HttpConnection을 이용하여 전송하면 되고(파일에 대한 정보는 다 있으니...조금만 응용하면...)


안드로이드에서의 multiparts 전송은 다음에 포스팅하는 걸로...


아유카와 님께서... uriToFile이 없다고하셔서 추가합니다~

/**
 * 카메라 또는 갤러리로부터 받은 url정보를 file 정보로 변환한다.
 * @param uri 
 * @return
 */
@TargetApi(19)
private File uriToFile ( Uri uri ) {
	
	String filePath = "";
	
	if ( uri.getPath().contains(":") ) {
		//:이 존재하는 경우		

		String wholeID = DocumentsContract.getDocumentId(uri);

		// Split at colon, use second item in the array
		String id = wholeID.split(":")[1];

		String[] column = { MediaStore.Images.Media.DATA };     

		// where id is equal to             
		String sel = MediaStore.Images.Media._ID + "=?";

		Cursor cursor = mContext.getContentResolver().
		                          query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, 
		                          column, sel, new String[]{ id }, null);


		int columnIndex = cursor.getColumnIndex(column[0]);

		if (cursor.moveToFirst()) {
		    filePath = cursor.getString(columnIndex);
		}   

		cursor.close();
		
	} else {
		//:이 존재하지 않을경우
		String id = uri.getLastPathSegment(); 
	    final String[] imageColumns = {MediaStore.Images.Media.DATA };
	    final String imageOrderBy = null;

	    String selectedImagePath = "path";
	    String scheme = uri.getScheme();
	    if ( scheme.equalsIgnoreCase("content") ) {
	    	 Cursor imageCursor = mContext.getContentResolver().query(uri, imageColumns, null, null, null);

			    if (imageCursor.moveToFirst()) {
			    	filePath = imageCursor.getString(imageCursor.getColumnIndex(MediaStore.Images.Media.DATA));
			    }
	    } else {
	    	filePath = uri.getPath();
	    }
	}
    
    File file = new File( filePath );
    
    return file;
}


멀티파츠 전송 샘플

이 댓글을 비밀 댓글로
  1. 이전 댓글 더보기
  2. 위에 소스로 백날 복사 붙여넣기 해봤자 실행 불가능합니다. 시간낭비 하지마시고 필요한 부분만 가져다 쓰세요 그냥
    • 지나가다
    • 2014.10.20 16:13
    여기 주인장이 스터디 목적으로 남긴 글인듯 합니다.
    위의 소스로는 컴파일이 안되니 그냥 참고로만 하세요.
    아무리 그래도 구현하다가 막혀서 답답한 마음에 구글링해서 찾아온 방문객들인데 완전하지 않은 소스를 이렇게 완전한것인양 오해하겠끔 소개하는 것은 좀..
  3. 프로젝트 진행중에 킷캣 이슈가 생겨서..

    비슷한 문제로 고생하실까봐 핵심부분만 발췌해서 올렸습니다.

    하지만 별로 도움이 안되는것 같군요 ㅠ

    조만간 간단한 샘플 하나 만들어서 올리도록하겠습니다.
  4. 발췌를 햇다는건 원본 게시물이 있다는건가요??
    그리고 이 게시물이 근본적으로 잘못되었다고 생각되는게
    문제점은 <input type="file"...> 일때 openFileChooser 함수가 4.4부터 없기때문에 발생하는 문제를 해결하기 위함인데
    소슨는 카메라,갤러리의 이미지를 input이 아닌 다른 element에 attribute를 지정하는 방식입니다.
    결론적으로 문제점과 해결방법이 서로 다르다는 뜻입니다. 사진을 지정할 수는 있겠으나 javascript의 file로 variable을 넘겨주지 못한다는 점에서는 마찬가지로 해결방안이 없어보입니다.
    게시물을 조금 수정할 필요가 있을것 같습니다.
    • 말씀하신대로 file 객체를 웹단으로 던질 수 있는 방법은 없습니다.
      하지만 네이티브단이든...웹단이든 모바일에서 서버로 데이터를 보내야 한다는 것은 동일합니다.
      결과가 동일하다면 해결방안이라고 말할 수 있지 않을까요?
    • hi9
    • 2014.10.22 16:39
    WebViewImageUploadHelper 에 open()메소드가 안나와 있네요....
    대충 짐작으론 다이얼로그 띄워서 사진찍을꺼냐 갤러리 부를꺼냐 선택하는 부분인거 같은데.....
    그거보다 바로 fileChooser 띄워서 처리시킴이 더 낳지않을까요....그럼 callcamera, callGallery는 필요없을듯.. 리턴된 uri만 처리해주면 더 심플하지 않을까...생각되네요...
    글쓴이님 보시면 open() 메세드도 소스 공개해 주세요....
    • 궁금증?
    • 2014.11.03 10:40
    window.Android.open 실행후 $("#thumbnail1").attr("src") 로 값을 가져 오려해보았지만 안되네요 혹 updateImage에서 img tag에만 src를 넣게 javascript 를 return 하는데 제가 이걸 좀 바꿔서 input type="text" 에 넣어 보려 file.toStirng() 으로 바꿔주고 다 해보았는데 들어 가질 않고 에러만 나는데 img tag만 가능한건가요????
    • 궁금증?
    • 2014.11.03 10:40
    img 태그에 이미지가 잘 들어가는 것을 확인하였습니다. 궁금한 점이 화면 img 태그 src에 데이터 들어간 뒤
    img 태그에서 src 데이터를 가져오기 위해

    <body>
    <div class="wrap">
    <img id="thumbnail1" style="width : 200px; height : 200px;"/>
    <input id="input_upload_sample" type="file"></input>
    <input type="text" value="" id="filePath"/>
    </div>
    </body>

    if ( isKitkat ) {
    window.Android.open("input_upload_sample", "thumbnail1");
    }

    이 부분-> $("#filePath").val($("#thumbnail1").attr("src"));

    이렇게 하였는데 데이터 값이 안들어 오는데 시점이 달라서 인가요 아님 소스상

    return "javascript:$(" + "\"#" + mThumbnailId + "\"" + ").attr(\"src\", " + "\"data:" + mimeType + ";base64," + base64EncodedImage + "\");";

    여기에 새롭게 들어가지는 것 인가요?
    • input 에 base64로 인코딩 되어 있는 이미지 정보를 value 값으로 받고 싶다는 것인가요???

      return "javascript:$(" + "\"#" + mThumbnailId + "\"" + ").attr(\"src\", " + "\"data:" + mimeType + ";base64," + base64EncodedImage + "\");";

      이부분은 doinbackground에서 발생하는 부분이고.

      @Override
      protected void onPostExecute(String result) {
      super.onPostExecute(result);
      mWebView.loadUrl(result);
      clearId();
      }

      이부분에서 mWebView.loadUrl 시점에 데이터가 웹화면으로 넘어갑니다.

      썸네일 이미지에서 src를 추출하려면

      loadUrl 이후가 되어야 하겠죠.
    • 지구민
    • 2014.11.18 15:43
    갤러리에서 불러온건 잘되는데
    카메라로 찍은건 화면페이지가 깜빡이더니 메인페이지로 이동해버립니다.
    이게 모든 카메라가 그런건 아니라 Uplus 카메라로 찍으면 잘되는데
    갤럭시노트2 기본카메라로 찍을때만 그럽니다. 어떡해야 하까요?
    • mesya
    • 2014.12.22 02:28
    노트2에서 테스트 해봤는데요, 갤러리에서 이미지 선택하면 이미지는 프리뷰로 보이는데, 실제 파일이 올라가질 않아요. 이건 왜그런걸까요? ㅜㅜ
    • 가장 좋은방법은...내장 갤러리 같은걸 사용하지 않고

      킷캣에 추가된 "문서"라는 앱을 강제로 연결시켜주는 것도 하나의 방법입니다.

      몇몇 타사앱의 경우 "문서"로만 연결되도록 해놨더군요.
    • hope
    • 2014.12.27 12:04
    안녕하세요.
    여기것으로 거의 따라 해보았는데...
    1. 킷캣 버전에서 문제점 : 파일을 업로드를 한개라도 하고 앱을 종료후 다시 파일 업로드 시도하면 클릭 자체가 안되고
    먹통이 되버립니다.

    2. 킷캣 이하버전에서는 파일을 업로드 선택후 카메라, 갤러리 선택 화면에서 취소시 다시 클릭하면 카메라, 갤러리 선택하는게 안되고 아예 먹통이 되버리고 앱을 종료후 다시 들어오면 하얀 바탕화면만 나옵니다. ㅠ.ㅠ

    어떤것이 빠졌는지 모르겠네요 ㅠ.ㅠ
    • 샘플을 한번 돌려보셨는지 모르겠네요.

      저는 그런증상을 경험 못했거든요.

      먹통이 된다는건 스택 오버플로우가 났거나,

      어디선가 무한루프도는 이벤트로 인해

      앱이 감당하지 못할 경우 자주 발생합니다.

      이런 경우도 있는데요. 전송이 완료되기 전에 앱을 종료하게 되면

      종료한 것처럼 보이지만 실제로 쓰레드는 백그라운드에서 돌아가고 있습니다(쓰레드 또는 AsyncTask를 사용하였을 경우)

      이경우는 테스크의 문제가 있을 수 있겠네요.

      앱을 종료할 때 AsyncTask를 종료하는 코드를 넣어 주거나 아니면

      전송중일때 앱을 종료하지 못하도록 해야합니다.

      그리고 메모리가 1기가 미만인 폰의 경우....고용량의 사진 (약 3메가)을 전송하게 되면 오버플로우가 나는 경우가 있습니다.(대표적으로 갤럭시 s2, s3 3g 버전)


      위의 3가지에 해당하는지 확인해보세요
    • 2015.01.15 16:41
    비밀댓글입니다
    • 실제 파일 업로드는 안드로이드 앱단에서 전송하게 됩니다.

      일반적으로 멀티파츠를 많이 사용하니... 멀티파츠를 참고하시면 될 것 같네요.

      http://fimtrus.tistory.com/entry/Android-%EC%95%88%EB%93%9C%EB%A1%9C%EC%9D%B4%EB%93%9C%EC%97%90%EC%84%9C-httpPost-multiparts%EB%A1%9C-%EC%A0%84%EC%86%A1%ED%95%98%EA%B8%B0%EB%A9%80%ED%8B%B0%ED%8C%8C%ED%8A%B8-%EB%A9%80%ED%8B%B0%ED%8C%8C%EC%B8%A0
    • hope
    • 2015.01.18 00:25
    앞전에 글 남겼던 사람인데요. 아직도 해결을 못하여 아무래도 여기 소스를 거의 사용했기에 여기 물어보는게 빠를것 같아서
    다시 찾아왔습니다. ㅠ.ㅠ

    일단 4.3. 버전은 해결을 했는데 킷캣 4.4.2 에서 카메라로 사진을 찍고 앱을 종료하고 다시 들어와서 다시 사진을 찍기위해

    해당 부분을 터치시 카메라,갤러리 가 뜨지 않고 에러가 주루루~~~

    혹시 이것 봐도 모르시면 메일로 소스를 보여드려도 될런지요 ㅠ.ㅠ 너무 답답해서 에궁 지송합니다.

    PS : 폰은 갤럭시 S4 입니다. 버전은 : 4.4.2 입니다.
    그리고 여기 소스 import 해서 폰으로 테스트 해보았는데
    밑에 오류 내용하고 거의 똑갔네요 ㅠ.ㅠ
    한번 파일선택하고 이미지 나오고 앱 종료하고 다시
    들어와서 하면 파일선택을 클릭해도 카메라, 갤러리가 안뜨네요 ㅠ.ㅠ
    그리고 혹시 업로드 파일을 리사이즈해서 돌려보았는데 에러내용은
    똑같네요 ㅠ.ㅠ

    힝잉 ~~ ㅠ.ㅠ



    //================== 오류부분 ==========================///

    input 클릭시
    01-15 18:40:49.216: D/AbsListView(31913): Get MotionRecognitionManager
    01-15 18:40:49.226: W/HardwareRenderer(31913): Attempting to initialize hardware acceleration outside of the main thread, aborting
    01-15 18:40:49.236: D/AbsListView(31913): unregisterIRListener() is called
    01-15 18:40:49.296: D/AbsListView(31913): unregisterIRListener() is called
    01-15 18:40:49.296: D/AbsListView(31913): unregisterIRListener() is called
    01-15 18:40:49.306: D/AbsListView(31913): unregisterIRListener() is called
    01-15 18:40:49.336: D/AbsListView(31913): unregisterIRListener() is called

    사진을 찍고나서
    01-15 18:41:34.136: E/Debug(31913): here camera2
    01-15 18:41:34.266: D/dalvikvm(31913): GC_FOR_ALLOC freed 2921K, 21% free 21497K/27180K, paused 37ms, total 38ms
    01-15 18:41:34.276: I/dalvikvm-heap(31913): Grow heap (frag case) to 29.068MB for 4192272-byte allocation
    01-15 18:41:34.276: D/AbsListView(31913): [unregisterDoubleTapMotionListener]
    01-15 18:41:34.276: I/MotionRecognitionManager(31913): .unregisterListener : / listener count = 0->0,
    01-15 18:41:34.276: D/AbsListView(31913): unregisterIRListener() is called
    01-15 18:41:34.296: D/dalvikvm(31913): GC_FOR_ALLOC freed 2767K, 26% free 24871K/33324K, paused 17ms, total 17ms
    01-15 18:41:34.316: D/dalvikvm(31913): GC_FOR_ALLOC freed 25K, 19% free 27271K/33324K, paused 16ms, total 16ms
    01-15 18:41:34.326: I/dalvikvm(31913): Total arena pages for JIT: 11
    01-15 18:41:34.326: I/dalvikvm(31913): Total arena pages for JIT: 12
    01-15 18:41:34.326: I/dalvikvm(31913): Total arena pages for JIT: 13
    01-15 18:41:34.326: I/dalvikvm(31913): Total arena pages for JIT: 14
    01-15 18:41:34.336: D/AbsListView(31913): [unregisterDoubleTapMotionListener]
    01-15 18:41:34.336: I/MotionRecognitionManager(31913): .unregisterListener : / listener count = 0->0,
    01-15 18:41:34.336: D/AbsListView(31913): unregisterIRListener() is called
    01-15 18:41:34.376: D/dalvikvm(31913): GC_FOR_ALLOC freed 1612K, 21% free 28893K/36560K, paused 33ms, total 33ms
    01-15 18:41:34.416: D/dalvikvm(31913): GC_FOR_ALLOC freed 2046K, 16% free 39835K/47128K, paused 13ms, total 13ms
    01-15 18:41:34.486: D/dalvikvm(31913): GC_FOR_ALLOC freed 15509K, 40% free 34198K/56836K, paused 14ms, total 14ms
    01-15 18:41:34.486: I/dalvikvm-heap(31913): Grow heap (frag case) to 43.791MB for 6623542-byte allocation
    01-15 18:41:34.496: D/dalvikvm(31913): GC_FOR_ALLOC freed 1K, 29% free 40784K/56836K, paused 15ms, total 15ms
    01-15 18:41:34.516: I/dalvikvm-heap(31913): Grow heap (frag case) to 53.380MB for 9935308-byte allocation
    01-15 18:41:34.536: D/dalvikvm(31913): GC_FOR_ALLOC freed 6507K, 34% free 43981K/66540K, paused 13ms, total 13ms
    01-15 18:41:34.606: I/System.out(31913): main(HTTPLog):SmartBonding Enabling is false, log to file is false, DBG is false
    01-15 18:41:34.686: D/dalvikvm(31913): GC_FOR_ALLOC freed 16384K, 25% free 35388K/47120K, paused 25ms, total 25ms
    01-15 18:41:34.696: D/dalvikvm(31913): GC_FOR_ALLOC freed 9K, 21% free 37427K/47120K, paused 13ms, total 13ms
    01-15 18:41:34.706: I/dalvikvm-heap(31913): Grow heap (frag case) to 45.365MB for 4967842-byte allocation
    01-15 18:41:34.716: E/Debug(31913): File is written


    앱종료후
    01-15 18:42:30.506: W/UnimplementedWebViewApi(31913): Unimplemented WebView method onKeyDown called from: android.webkit.WebView.onKeyDown(WebView.java:2182)
    01-15 18:42:30.706: W/UnimplementedWebViewApi(31913): Unimplemented WebView method onKeyDown called from: android.webkit.WebView.onKeyDown(WebView.java:2182)
    01-15 18:42:31.196: W/IInputConnectionWrapper(31913): showStatusIcon on inactive InputConnection


    다시 앱실행후 input 클릭시
    01-15 18:43:06.466: D/AbsListView(31913): Get MotionRecognitionManager
    01-15 18:43:06.476: W/HardwareRenderer(31913): Attempting to initialize hardware acceleration outside of the main thread, aborting
    01-15 18:43:06.476: W/System.err(31913): android.view.WindowManager$BadTokenException: Unable to add window -- token android.os.BinderProxy@429f0de8 is not valid; is your activity running?
    01-15 18:43:06.476: W/System.err(31913): at android.view.ViewRootImpl.setView(ViewRootImpl.java:753)
    01-15 18:43:06.476: W/System.err(31913): at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:278)
    01-15 18:43:06.476: W/System.err(31913): at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:69)
    01-15 18:43:06.476: W/System.err(31913): at android.app.Dialog.show(Dialog.java:288)
    01-15 18:43:06.476: W/System.err(31913): at kr.co.test.image.CommonDialogs.showListDialog(CommonDialogs.java:137)
    01-15 18:43:06.476: W/System.err(31913): at kr.co.test.image.WebViewImageUploadHelper.open(WebViewImageUploadHelper.java:141)
    01-15 18:43:06.476: W/System.err(31913): at kr.co.test.image.WebViewInterface.open(WebViewInterface.java:182)
    01-15 18:43:06.476: W/System.err(31913): at com.android.org.chromium.base.SystemMessageHandler.nativeDoRunLoopOnce(Native Method)
    01-15 18:43:06.476: W/System.err(31913): at com.android.org.chromium.base.SystemMessageHandler.handleMessage(SystemMessageHandler.java:27)
    01-15 18:43:06.476: W/System.err(31913): at android.os.Handler.dispatchMessage(Handler.java:102)
    01-15 18:43:06.486: W/System.err(31913): at android.os.Looper.loop(Looper.java:157)
    01-15 18:43:06.486: W/System.err(31913): at android.os.HandlerThread.run(HandlerThread.java:61)
    01-15 18:43:06.496: E/raon(31913): Uncaught Error: Error calling method on NPObject. -- From Line 130ofhttp://test.co.kr/m/html2/board/gallery.php?action=write&page=1&one_idx=9&one_code=one20141121145235

    ///============ 끝 =========================================

    • 01-15 18:43:06.496: E/raon(31913): Uncaught Error: Error calling method on NPObject. -- From Line 130ofhttp://test.co.kr/m/html2/board/gallery.php?action=write&page=1&one_idx=9&one_code=one20141121145235

      이미 스크립트단에서 에러가 발생했네요.

      아마 종료하였을때 웹페이지에 대한 캐시가 남아 있어서 그런 것이 아닐까요??
    • hope
    • 2015.01.20 21:41
    에궁 ㅠ.ㅠ
    System.exit(0); 으로 해결했습니다.
    그리고 한가지 더 해결 못한게 있는데 주인장님 해당 소스 카메라 부분에는
    이미지 회전 및 리사이즈가 아쉽게도 없네요.

    현재 웹뷰로 카메라 회전, 리사이즈 까지 적용을 했는데 안드로이드 프로그래밍에 대한 지식이
    거의 없어 이리저리 찾아서 붙여넣고 있습니다. 시간도 현재 많지 않고 어찌하다 작업을 하게되었는데

    카메라로 찍은후에 이미지가 하나는 리사이즈, 회전 안된 이미지 하나, 리사이즈, 회전 된 이미지 두개가 생기네요.
    웹으로 보여지는것은 리사이즈 회전 된 이미지 보여지고 웹에서 파일 저장시 회전 및 리사이즈된 이미지 저장되고요...
    하려고 하는것은 회전 및 리사이즈 된 이미지 하나만 해당 폴더에 저장 하고 싶습니다.
    뭘 어떻게 손을 봐야 할지 조언 좀 부탁드립니다. ㅠ.ㅠ



    //===== 소스

    @Override
    protected void onActivityResult(int requestCode, int resultCode,
    Intent data) {

    super.onActivityResult(requestCode, resultCode, data);
    if (resultCode == Activity.RESULT_OK) {

    if (requestCode == FILECHOOSER_RESULTCODE) { //파일 선택.
    if (null == mUploadMessage)
    return;
    Uri result = data == null || resultCode != RESULT_OK ? null : data.getData();

    if ( mTempFile.exists() ) {
    Log.e("Debug", "here file exists");
    try {
    String imagePath = Uri.fromFile(mTempFile).getPath(); // 이미지 경로와 이름
    Bitmap image = BitmapFactory.decodeFile(imagePath);

    // 이미지를 상황에 맞게 회전시킨다.
    ExifInterface exif = new ExifInterface(imagePath);
    int exifOrientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL);
    int exifDegree = exifOrientationToDegrees(exifOrientation);

    int newWidth = 200;
    int newHeight = 220;
    float ratioX = newWidth / (float) image.getWidth();
    float ratioY = newHeight / (float) image.getHeight();
    float middleX = newWidth / 2.0f;
    float middleY = newHeight / 2.0f;
    Matrix scaleMatrix = new Matrix();
    scaleMatrix.setScale(ratioX, ratioY, middleX, middleY);
    scaleMatrix.postRotate(exifDegree);
    try
    {
    Bitmap converted;
    converted = Bitmap.createBitmap(image, 0, 0, image.getWidth(), image.getHeight(), scaleMatrix, true);

    if(image != converted)
    {
    image.recycle();
    image = converted;
    }
    }
    catch(OutOfMemoryError ex)
    {
    // 메모리가 부족하여 회전을 시키지 못할 경우 그냥 원본을 반환합니다.
    }

    cnt++;
    String resizedFileName = "photo_" + new Date().getTime() + ".jpg";
    File dir = new File(Environment.getExternalStorageDirectory() + File.separator+"test");
    File filedir = new File(dir, resizedFileName);


    File directory = new File(Environment.getExternalStorageDirectory() + File.separator + "test");
    if (!directory.exists()) {
    directory.mkdir();
    }
    mTempFile = new File(directory, "photo_" + new Date().getTime() + ".jpg");


    FileOutputStream fos = new FileOutputStream(filedir);
    image.compress(CompressFormat.JPEG, 100, fos);
    fos.close();
    mUploadMessage.onReceiveValue(getImageContentUri(Test.context, filedir));
    mUploadMessage = null;
    }
    catch(Exception e)
    {
    Toast.makeText(this, "오류발생: " + e.getLocalizedMessage(), Toast.LENGTH_LONG).show();
    }

    } else {

    mUploadMessage.onReceiveValue(result);
    mUploadMessage = null;
    }

    return;
    } else if ( requestCode == WebViewImageUploadHelper.KITKAT_FILECHOOSER ) { //킷캣.
    Uri result = data == null || resultCode != RESULT_OK ? null : data.getData();

    WebViewImageUploadHelper.getInstance(this, mWebView).updateContent(result);
    return;
    } else if ( requestCode == WebViewImageUploadHelper.KITKAT_CAMERA) { //킷캣 카메라.
    Log.e("Debug", "here camera2");
    WebViewImageUploadHelper.getInstance(this, mWebView).updateContent();
    } else {
    mUploadMessage.onReceiveValue(null);
    mUploadMessage = null;
    }
    } else {
    mUploadMessage.onReceiveValue(null);
    mUploadMessage = null;
    }

    }






    public class CustomWebChromeClient extends WebChromeClient {
    // 자바스크립트 에러 발생 시 로그 출력부
    public boolean onConsoleMessage(ConsoleMessage cm) {
    Log.e("raon", cm.message() + " -- From Line " + cm.lineNumber() + "of" + cm.sourceId());
    return true;
    }
    // For Android > 4.1
    public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType, String capture) {
    openFileChooser(uploadMsg);
    }
    // Andorid 3.0 +
    public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType) {
    openFileChooser(uploadMsg);
    }
    /**
    * 파일 업로드. input tag를 클릭했을 때 호출된다.<br>
    * 카메라와 갤러리 리스트를 함께 보여준다.
    * @param uploadMsg
    */
    public void openFileChooser(ValueCallback<Uri> uploadMsg) {
    mUploadMessage = uploadMsg;
    Log.e("raon", mUploadMessage + "" );

    File directory = new File(Environment.getExternalStorageDirectory() + File.separator + "test");
    if (!directory.exists()) {
    directory.mkdir();
    }
    mTempFile = new File(directory, "photo1_" + new Date().getTime() + ".jpg");



    final List<Intent> cameraIntents = new ArrayList<Intent>();
    final Intent captureIntent = new Intent(android.provider.MediaStore.ACTION_IMAGE_CAPTURE);
    final PackageManager packageManager = getPackageManager();
    final List<ResolveInfo> listCam = packageManager.queryIntentActivities(captureIntent, 0);

    for(ResolveInfo res : listCam) {
    final String packageName = res.activityInfo.packageName;
    final Intent i = new Intent(captureIntent);
    i.setComponent(new ComponentName(res.activityInfo.packageName, res.activityInfo.name));
    i.setPackage(packageName);
    i.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(mTempFile));
    cameraIntents.add(i);

    }

    Log.e("Debug", "1here camera");

    Intent i = new Intent(Intent.ACTION_GET_CONTENT);
    i.addCategory(Intent.CATEGORY_OPENABLE);
    i.setType("image/*");
    Intent chooserIntent = Intent.createChooser(i,"File Chooser");
    chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, cameraIntents.toArray(new Parcelable[]{}));
    Test.this.startActivityForResult(chooserIntent, FILECHOOSER_RESULTCODE);
    }
    }
    • 이미지 파일이 존재할 경우 리사이즈 및 회전을 하시는거죠??

      그럼 기존의 파일을 삭제하도록하시면 될 것 같네요.

      요즘 업데이트는 안하고 있지만...제가 자주 사용하던 메쏘드들을 정리해놓은 소스가 있습니다.

      여기에 이미지 리사이즈.크기조절 같은 것도 있구요 필요하시면 참조하세요.

      https://code.google.com/p/jonghyun-jhlibrary/
    • 한돌물
    • 2015.01.23 00:59
    왜 안되나 궁금 했는데, 킷캣에서 문제가 있었군요. ㅠㅠ
    좋은 소스 공유 해 주셔서 감사드립니다.
    그런데 죄송하게도..댓글 쓰신 분 처럼 저도 비슷한 증상이 있네요.
    처음 시작해서 한번은 갤러리,사진이 잘 뜨는데요.
    예를 들어 갤러리 선택 후, 화면에 보여진 후에..버튼을 다시 클릭하면 동작이 안되네요.
    답 주신 것 중에서..웹캐시때문일수도 있을 것 같다고 하셨는데요.
    죄송하지만..제가 이해가 잘 안되서요. ㅠㅠ
    system.exit 로 해결 하신 분은...혹시 어느 부분에 넣으셨는지 알려 주시면 감사하겠습니다.
      • 익명
      • 2015.02.06 14:40
      저도 포스팅 보면서 조금씩 해결하고 있는데...
      한번 적용되고 그 이후에 먹통되시는 분들은 onActivityResult 확인한번 해보세요.
      저도 전에 다른 곳에서 openFileChoose부분만 가져와서 테스트해보니까 한번만 실행이 되어서, 여기 소스를 전체 적용했더니 해결되었어요.
      그리고 system.exit 는 onDestroy에 넣으시면 될거 같네요~

      좋은 포스팅 감사드립니다.^_^
    • 도움이 되었다니 다행입니다.

      제가 요즘 블로그에 글을 잘 쓸 수 없는 상황이라 답변이 다소 느리네요.

      하단에 메일이 있으니 혹시 궁금한점이 있으시면 메일로 질문 주시면

      아는 한도내에서 답변 드리도록하겠습니다.

      감사합니다.
      • YBH_LOVE
      • 2015.07.04 22:07
      onDestroy 에 system.exit(0) 추가하면 문제없이 동작하네요~
      킷캣 참 어이없지만 그렇게 해결했어요~
    • 2015.04.14 17:03
    비밀댓글입니다
  5. 안녕하세요~ 파일 업로드가 골치아픈 상황에 많은 도움이 되었어요.
    안드로이드 상에서 Multipart하는 상황은 너무 복잡해서 좀 수정을 해보았어요.

    input file 적용은 힘들지만 FormData를 통해 Blob형식으로 파일 첨부 가능합니다.
    안드로이드에서가 아닌 javascript상에서 Multipart 전송이 가능합니다.


    안드로이드에서 updateImage 부분입니다. 안드로이드에서 파일을 선택하면 base64로 변환해서 javascript로 데이터를 날려줍니다.

    private void updateImage(final String mKey, final File file, final String mimeType) {

    new AsyncTask<Void, Void, String>() {

    @Override
    protected void onPreExecute() {
    super.onPreExecute();
    }

    @Override
    protected String doInBackground(Void... params) {

    String base64EncodedImage = fileToString(file);

    // 자바스크립트 함수 setImageFromAndroid로 base64, mimeType을 같이 넘깁니다.
    return "javascript:setImageFromAndroid(\"" + mKey + "\", \"" + base64EncodedImage + "\", \"" + mimeType + "\")";

    }

    @Override
    protected void onPostExecute(String result) {

    super.onPostExecute(result);
    mWebView.loadUrl(result);
    clearId();

    }
    }.execute();


    }

    자바스크립트 부분입니다.
    여기서 주의해야할게 이미지는 보이지만 Blob로 변환시 문제가 생기는 경우가 있어요.
    mimeType이 "image/webp"일 경우 이미지는 보이지만 문제가 생겼습니다. Blob변환에 문제인지 전송에 문제있는지는 정확히 모르겠어서 "image/jpeg", "image/gif", "image/png" 만 허용하는 식으로 쓰고있습니다.
    이부분은 따로 잡아주셔야 할 겁니다.

    // Blob의 배열로 사용합니다.
    var image = new Array();

    // 안드로이드에서 호출되는 함수
    function setImageFromAndroid(key, base64EncodedImage, mimeType) {

    // base64의 데이터를 Blob로 변환
    image[key] = b64toBlob(base64EncodedImage, mimeType);

    // base64를 이용하여 이미지 보이기
    $("#image-" + key).attr(\"src\", " + "\"data:" + mimeType + ";base64," + base64EncodedImage + "\");";

    }

    // base64의 데이터를 Blob로 변환
    function b64toBlob(b64Data, contentType) {

    contentType = contentType || '';
    var sliceSize = 512;

    var byteCharacters = atob(b64Data);
    var byteArrays = [];

    for (var offset = 0; offset < byteCharacters.length; offset += sliceSize) {

    var slice = byteCharacters.slice(offset, offset + sliceSize);
    var byteNumbers = new Array(slice.length);

    for (var i = 0; i < slice.length; i++) {
    byteNumbers[i] = slice.charCodeAt(i);
    }

    var byteArray = new Uint8Array(byteNumbers);
    byteArrays.push(byteArray);

    }

    var blob = new Blob(byteArrays, {type: contentType});
    return blob;

    }

    위에서 얻은 image[key]를 다음처럼 FormData로 넘겨주시면 됩니다.
    formData.append("images[" + key + "]", image[key]);
    또는 formData.append("images[" + key + "]", image[key], filename);
    • g2사용자
    • 2015.07.30 18:35
    안드로이드 킷캣 4.4.2 이용하고 LG G2 사용하는 사람인데요.. 요새 이거 많이 사용하는데.. 샘플소스 사용했는데.. 가상머신 킷캣에서는 잘됩니다. 근데 제껀 안되요 ㅠㅠ
    갤럭시 노트4 롤리팝에서도 업로드 잘 열리는데.. 왜 G2에선 안열릴까요?
    사용가능하게 하고싶습니다.. 조언좀해주세요 ㅠㅠ
    • 증상을 자세히 말씀해주시면 답변하기가 더 편할 듯하네요.
      갤러리 종류에 따라 리턴되는 데이터가 다른 경우가 있기 때문에
      실제 디버깅을 해봐야 알 수 있을 것 같습니다.
    • huhu
    • 2015.12.19 22:15
    이미지말고 파일로 업로드하는 부분 안드로이드 스튜디오 예제 소스좀 부탁드리겠습니다. ㅠ.ㅠ 아무리 해도 적용이 안되서요. 참고좀 할려구요. 여기에 포스팅해주신 소스는 참고 하였는데 onDestroy 부분에 System.exit(0);를 넣어도 파일선택창이 한번 나왔다가 다음부턴 먹통이 됩니다.
    • won
    • 2016.03.27 15:39
    send 쓰는 방법을 알고 싶습니다 ㅠㅠ 썸네일만 올라가고 파일이 실제로 올라가지 않아요 ㅠㅠㅠ
    • mukuku
    • 2016.10.20 18:57
    감사합니다 정말 도움이 많이 되었습니다.이제 소프트 키보드 안열리는 것만 해결하면 되겠군여.......=_=...............................

[Android, Hybrid] JavascriptInterface 사용법.

Posted by Find my true self Fimtrus
2014.06.24 14:21 Programming/Hybrid App

아이폰의 경우 Native와 Web간의 통신을 위해서 scheme를 이용하지만 


안드로이드에서는 JavascriptInterface를 이용하여 더욱 편하게 데이터를 주고 받을 수 있다.

예제로 간단한 Toast를 보여주는 인터페이스를 만들어 보겠다.


먼저 웹뷰에 추가 시킬 JavascriptInterface 클래스를 만든다.


public class WebViewInterface {

	private WebView mAppView;
	private Activity mContext;

	/**
	 * 생성자.
	 * @param activity : context
	 * @param view : 적용될 웹뷰
	 */
	public WebViewInterface(Activity activity, WebView view) {
		mAppView = view;
		mContext = activity;
	}
	/**
	 * 안드로이드 토스트를 출력한다. Time Long.
	 * @param message : 메시지
	 */
	@JavascriptInterface
	public void toastLong (String message) {
		Toast.makeText(mContext, message, Toast.LENGTH_LONG).show();
	}
	/**
	 * 안드로이드 토스트를 출력한다. Time Short.
	 * @param message : 메시지
	 */
	@JavascriptInterface
	public void toastShort (String message) { // Show toast for a short time
		Toast.makeText(mContext, message, Toast.LENGTH_SHORT).show();
	}
}

안드로이드 4.3.X 젤리빈 이상부터 "@JavascriptInterface" annotation을 붙이지 않을 경우

해당 메쏘드는 동작하지 않기 때문에 반드시 붙여줘야한다.(킷캣이상인가??;;)



그리고 Activity의 onCreate 혹은 Fragment의 onCreateView 에서 loadUrl이 불리기전에

WebView 객체의 addJavascriptInterface 메쏘드를 이용해 연결시켜 주면 바로 사용할 수 있다.

무지 간단하다!

public class MainActivity {

	private WebView mWebView = null;
	private WebViewInterface mWebViewInterface;

	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		getWindow().requestFeature(Window.FEATURE_PROGRESS);
		setContentView(R.layout.activity_main);
		mWebView = (WebView) findViewById(R.id.webview); //웹뷰 객체
		mWebViewInterface = new WebViewInterface(MainActivity.this, mWebView); //JavascriptInterface 객체화
		mWebView.addJavascriptInterface(mWebViewInterface, "Android"); //웹뷰에 JavascriptInterface를 연결
	}
}

addJavascriptInterface를 보면 인터페이스 객체와, 웹에서 사용될 객체 명을 넣어준다.

위와 같이 연결을 하게 되면, 웹페이지에 window.Android 객체가 생성되고, WebViewInterface에 정의된

모든 메쏘드를 사용할 수 있게 된다.


웹페이지에 아래와 같이 입력하게 되면, 안드로이드 Toast를 통해 "JavascriptInterface Test"라는 문구가 보이게 된다.

window.Android.toastShort( "JavscriptInterface Test" );


이 댓글을 비밀 댓글로

[Web] window.print() 로 원하는 영역 인쇄하기.

Posted by Find my true self Fimtrus
2014.06.19 10:47 Programming/Web

window.print 함수를 사용하여 인쇄할 때의 영역은 

document.body 의 innerHTML이 잡히게 된다.

흔히 많이 사용하는 것중에 하나로,

원하는 영역의 html을 body에 넣어서 인쇄하는 방법이다.

하지만 single page 기반이나, jsp를 사용하지 않는 순수한 html의 경우

이방법을 사용하였을 때, 페이지가 거침없이 깨지는 경우가 대부분이다.

그래서 새창을 통해 인쇄하는 방법을 사용한다.


먼저 팝업 윈도우하나를 선언한다.

이때 새창이 생기게 되고, 정상적으로 열렸다면 popupWindow에는

새창에 대한 window 객체가 들어오게 된다. 팝업 차단으로 인해 열리지 않았을 경우

popupWindow 에는 undefined가 리턴되게 된다.



var popupWindow = window.open("", "_blank" );


대부분 개발자들은 jquery를 사용한다.(아마도??)

그 중에 jquery ui를 쓰는 사람도 적지 않다.

그러면 당연히 새창에서도 화면 구성에 필요한 js, css 등

plugin 들을 importing 해줘야 정상적으로 화면이 뜰 것이다.

그래서 아래의 코드로 헤더를 추가해준다.

- write 함수의 경우 document 객체에 직접 html을 추가해주는 역할을 한다.

- $('head')를 통해 헤더 객체를 얻고, html 함수를 사용하여 모든 헤더정보를 불러온다.



popupWindow.document.write( "<헤드태그>"); //head 제대로 쓰니까..티스토리 에러가...
popupWindow.document.write( $('head').html() );
popupWindow.document.write( '<헤드태그 닫기>' ); 


헤더를 추가한 후, 실제 표시될 컨텐츠를 추가해주면

새창에 원하는 컨텐츠가 추가되게 된다.


var $table = $("화면에 표시할 컨텐츠");

popupWindow.document.write( '<바디태그>' );
popupWindow.document.write( '
' ); popupWindow.document.write( $table.html() ); popupWindow.document.write( '
<바디태그 닫기>' );


컨텐츠 추가 완료 후 document를 close 시킨 후(반드시 close 함수를 호출해야한다)

새 창에서 print 함수를 호출해주면 인쇄창이 뜬다.


popupWindow.document.close();

//popupWindow 의 print 이다. 가끔 document.print 를 치시는 분들이..
popupWindow.print();


위의 코드들은 크롬 기반에서 테스트 하였으며, 익스플로러의 경우 정상적으로 동작하지 않을 수 있다.

이 댓글을 비밀 댓글로