웹앱 apk 빠르게 만들기... (feat: 우리는 귀찮은거 시러하니까)
웹사이트 만들어놓고 앱이없냐 이 ㅈㄹ 하면 요로코롬해서 전달해주면됨
준비물:
1. 앱영어이름
2. 앱한글이름
3. 아이콘몬스터(iconmonster)나 오픈클립아트(Openclipart) 같은 무료 이미지사이트를
뒤져서 적절한 앱아이콘 만들어서 png 파일 준비(배경은 투명말고 하얀걸로다가)
[이제시작]
[1. 안드로이드 스튜디오로 새 안드로이드 프로젝트]
만들고 언어는 자바에 empty activity 로 선택하고 앱영어 이름을 넣고 시작한다.
[2. 앱아이콘만든다]
app -> 우클릭해서 new -> image asset 하고 name을 ic_myicon 으로 하고 source asset -> path에서 아까 준비한이미지를 불러오기하고 Finish를 누른다.
[3. http로 접근할수 있으니까 res -> xml -> network_security_config.xml 파일을 새로 만들어서 다음을 기제]
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<base-config cleartextTrafficPermitted="true" />
</network-security-config>
[4. 이미지업로드용 파일제공을위해 res -> xml -> provider_paths.xml 파일을 만들어 다음을 기제]
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-files-path name="my_images" path="Pictures" />
</paths>
[5. AndroidManifest.xml 파일 세팅하기]
manifest -> 바로 밑에 퍼미션 넣어주기 (파일 다운로드 업로드할수 있으니까)
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.CAMERA2" />
<uses-feature android:name="android.hardware.camera" android:required="true" />
manifest -> application -> android:label 을 한글 앱이름으로 변경
manifest -> application 에서 다음 3줄을 추가
android:networkSecurityConfig="@xml/network_security_config"
android:usesCleartextTraffic="true"
android:requestLegacyExternalStorage="true"
manifest -> application 에서 아까만든 ic_myicon 이미지 Asset으로 변경
android:icon="@mipmap/ic_myicon"
android:roundIcon="@mipmap/ic_myicon_round"
manifest -> application -> provider 에 파일프로바이더 기재
(중간 com. 어쩌고는 자기 프로젝트 아이디로 변경)
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="com.example.webappmokup.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths" />
</provider>
manifest -> application -> activity 에 다음을 기제
(화면 돌렸을때 초기화되면 짜증나니까)
android:configChanges="orientation|keyboardHidden|screenSize"
[6.테마바꾸기]
res -> values -> themes -> themes.xml 파일과 themes.xml (night) 파일에서
<style parent="Theme.MaterialComponents.DayNight.NoActionBar"> 로바꾸고
그밑에 Primary와 Secondary칼라 6개는 전부 블랙으로 @color/black 변경
2개 파일 다 바꿔줘야 다크모드에서 색상이 안바뀜.
[7. MainActivity 레이아웃 바꾸기]
res -> layout -> activiy_main.xml 파일을 열어서 코드보기로 변경후
레이아웃을 RelativeLayout 으로 변경하고 안에 있는 TextView를 지우고 다음으로 교체
<WebView
android:id="@+id/webview"
android:layout_width="match_parent"
android:layout_height="match_parent">
</WebView>
[8. MainActivity.java 파일 변경]
app -> java -> com.*** ->MainActivity.java 파일을 열어서
package 다음줄을 아래처럼 변경 (그냥 복붙하자 분석은 붙혀놓고 생각)
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.content.FileProvider;
import android.Manifest;
import android.app.AlertDialog;
import android.app.DownloadManager;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.os.Parcelable;
import android.provider.MediaStore;
import android.util.Log;
import android.webkit.CookieManager;
import android.webkit.DownloadListener;
import android.webkit.URLUtil;
import android.webkit.ValueCallback;
import android.webkit.WebChromeClient;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.Toast;
import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
//롤리팝 이하 버전은 고려안한다.. (지금이 어느세월인데 아직도)
public class MainActivity extends AppCompatActivity {
private WebView mywebView;
private String target_url = "https://www.naver.com";
//이미지파일업로드용
public ValueCallback<Uri[]> filePathCallbackNormal;
public final static int FILECHOOSER_REQ_CODE = 2002;
private String currentPhotoPath;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//권한처리
checkMyPermissions();
//
mywebView=(WebView) findViewById(R.id.webview);
mywebView.setWebViewClient(new mywebClient());
mywebView.setWebChromeClient(new CustomChrome(this)); //크롬클라이언트
mywebView.setDownloadListener(new DownloadListener() { //파일다운로드처리
@Override
public void onDownloadStart(String url, String userAgent, String contentDisposition, String mimeType, long contentLength) {
// 파일명 잘라내기 및 확장자 확인
String fileName = contentDisposition;
if (fileName != null && fileName.length() > 0) {
int idxFileName = fileName.indexOf("filename=");
if (idxFileName > -1) {
fileName = fileName.substring(idxFileName + 9).trim();
}
int idxUnderFileName = fileName.indexOf("filename_=UTF-8");
if (idxUnderFileName > -1) {
fileName = fileName.substring(idxUnderFileName + 15).trim();
}
if (fileName.endsWith(";")) {
fileName = fileName.substring(0, fileName.length() - 1);
}
if (fileName.startsWith("\"") && fileName.startsWith("\"")) {
fileName = fileName.substring(1, fileName.length() - 1);
}
}else{
fileName = URLUtil.guessFileName(url, contentDisposition, mimeType);
}
DownloadManager.Request request = new DownloadManager.Request(Uri.parse(url));
request.setMimeType(mimeType);
String cookies = CookieManager.getInstance().getCookie(url);
request.addRequestHeader("cookie", cookies);
request.addRequestHeader("User-Agent", userAgent);
request.setDescription("Downloading File..");
request.setTitle(fileName);
request.allowScanningByMediaScanner();
request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, fileName);
DownloadManager dm = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);
dm.enqueue(request);
Toast.makeText(getApplicationContext(), "파일다운로드", Toast.LENGTH_LONG).show();
}
});
//
WebSettings webSettings = mywebView.getSettings();
webSettings.setJavaScriptEnabled(true); //자바스크립트허용
webSettings.setSupportMultipleWindows(false); //멀티윈도우 안됨
webSettings.setSupportZoom(true); //줌가능
webSettings.setBuiltInZoomControls(true); //컨트롤로줌가능
webSettings.setDisplayZoomControls(false); //줌컨트롤은없에기
webSettings.setCacheMode(WebSettings.LOAD_NO_CACHE); //캐시없이 네트워크로만
webSettings.setUseWideViewPort(true); // wideviewport사용
webSettings.setJavaScriptCanOpenWindowsAutomatically(false); // 자바스크립트가 window.open()사용
webSettings.setAllowFileAccess(true); //웹뷰가 파일액세스 활성화
webSettings.setLoadWithOverviewMode(true); // 메타태그허용
webSettings.setDomStorageEnabled(true); // 로컬저장소허용
//
mywebView.loadUrl(target_url);
}
//권한처리
public void checkMyPermissions() {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
if (checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED
|| checkSelfPermission(Manifest.permission.ACCESS_NETWORK_STATE) != PackageManager.PERMISSION_GRANTED
|| checkSelfPermission(Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED
|| checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED
|| checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED)
{
//
String[] permissions = {
Manifest.permission.INTERNET,
Manifest.permission.ACCESS_NETWORK_STATE,
Manifest.permission.CAMERA,
Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.READ_EXTERNAL_STORAGE
};
requestPermissions(permissions, 1);
}
}
}
//카메라 기능 구현
public void selectImage() {
//카메라찍기 인텐트
Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
if (takePictureIntent.resolveActivity(getPackageManager()) != null) {
File photoFile = null;
try {
photoFile = createImageFile();
} catch (IOException ex) {
Log.e("webappmokup:", "이미지파일을 생성할수 없음", ex);
}
//파일이 성공적으로 생성된경우 진행
if (photoFile != null) {
//파일프로바이더로 생성된 파일을 가져와야함 -> 엄한짓 하면 안가져옴.. (!!여기서 자기 프로젝트 id로 해야함!!)
Uri photoURI = FileProvider.getUriForFile(this, "com.example.webappmokup.fileprovider", photoFile);
takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI);
} else {
Toast.makeText(this, "이미지파일 생성안됨", Toast.LENGTH_SHORT).show();
}
}
//선택 인텐트
Intent pickIntent = new Intent(Intent.ACTION_PICK);
pickIntent.setType(MediaStore.Images.Media.CONTENT_TYPE);
pickIntent.setData(MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
String pickTitle = "이미지를 가져올 방법 선택";
Intent chooserIntent = Intent.createChooser(pickIntent, pickTitle);
//takPictureIntent 포함해서 startactivity
chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, new Parcelable[]{ takePictureIntent });
startActivityForResult(chooserIntent, FILECHOOSER_REQ_CODE); //이것도 데프리케이트라고?? 이래서 개짜증나는거야..
}
//촬영이미지 파일만들기
private File createImageFile() throws IOException {
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
String imageFileName = "IMG_" + timeStamp + "_";
File imageStorageDir = getExternalFilesDir(Environment.DIRECTORY_PICTURES);
File imageFile = File.createTempFile(
imageFileName, /* 파일명 */
".jpg", /* 확장자 */
imageStorageDir /* 디렉토리 */
);
//
currentPhotoPath = imageFile.getAbsolutePath();
return imageFile;
}
//웹클라이언트 이너클래스
public class mywebClient extends WebViewClient {
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon){
super.onPageStarted(view, url, favicon);
}
@Override
public void onPageFinished(WebView view, String url) {
//쿠키매니저가 종료될때 쿠키허용이 되지 않으면
//이를 기반으로 하는 로그인처리를 할때 매번 다시 로그인해야함.
CookieManager.getInstance().setAcceptCookie(true);
CookieManager.getInstance().acceptCookie();
CookieManager.getInstance().flush();
}
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url){
//전화나 메일 링크이동
if(url.startsWith("tel:")) {
Intent intent = new Intent(Intent.ACTION_DIAL, Uri.parse(url));
startActivity(intent);
return true;
} else if (url.startsWith("mailto:")) {
Intent itt = new Intent(Intent.ACTION_SENDTO, Uri.parse(url));
startActivity(itt);
return true;
}
//
view.stopLoading();
view.loadUrl(url);
return false;
}
}
@Override
public void onBackPressed() {
if (mywebView.canGoBack()) {
mywebView.goBack();
} else {
super.onBackPressed();
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
switch (requestCode)
{
case FILECHOOSER_REQ_CODE:
if (resultCode == RESULT_OK) {
if (filePathCallbackNormal == null) { return; }
if (data == null) { data = new Intent(); }
if (data.getData() == null) { data.setData( Uri.fromFile(new File(currentPhotoPath))); }
//콜백처리
filePathCallbackNormal.onReceiveValue(WebChromeClient.FileChooserParams.parseResult(resultCode, data)); //파일 바인딩
filePathCallbackNormal = null;
} else {
// RESULT_OK 가 아니더라도 콜백클리어
if (filePathCallbackNormal != null) {
filePathCallbackNormal.onReceiveValue(null);
filePathCallbackNormal = null;
}
}
break;
default:
break;
}
//
super.onActivityResult(requestCode, resultCode, data);
}
}
//유틸추가용 크롬클라이언트 (alert, confirm, filechoose 같은것들)
class CustomChrome extends WebChromeClient {
private Context mContext; // WebChromeClient를 호출한 Context
private AlertDialog mAlertDialog; // 경고창을 띄울 Dialog
public CustomChrome(Context context){
mContext = context;
}
@Override
public boolean onJsAlert(WebView view, String url, String message, final android.webkit.JsResult result){
if(mAlertDialog == null){
mAlertDialog = new AlertDialog.Builder(mContext)
.setTitle("알림")
.setMessage(message)
.setPositiveButton(android.R.string.ok, new AlertDialog.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
result.confirm();
mAlertDialog.dismiss();
mAlertDialog = null;
}
})
.setCancelable(false)
.create();
}
mAlertDialog.show();
return true;
}
@Override
public boolean onJsConfirm(final WebView view,final String url, final String message, final android.webkit.JsResult result) {
if(mAlertDialog == null){
mAlertDialog = new AlertDialog.Builder(mContext)
.setTitle("확인")
.setMessage(message)
.setPositiveButton(android.R.string.ok, new AlertDialog.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
result.confirm();
mAlertDialog.dismiss();
mAlertDialog = null;
}
})
.setNegativeButton(android.R.string.cancel,new DialogInterface.OnClickListener(){
public void onClick(DialogInterface dialog, int which) {
result.cancel();
mAlertDialog.dismiss();
mAlertDialog = null;
}
})
.setCancelable(false)
.create();
}
mAlertDialog.show();
return true;
}
@Override
public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, FileChooserParams fileChooserParams) {
MainActivity mainact = (MainActivity) mContext;
//Callback 초기화
if (mainact.filePathCallbackNormal != null) {
mainact.filePathCallbackNormal.onReceiveValue(null);
mainact.filePathCallbackNormal = null;
}
//콜백 다시바인딩
mainact.filePathCallbackNormal = filePathCallback;
//이미지 선택
mainact.selectImage();
return true;
}
}
[!!! 당연히 target_url 에 해당하는 도메인부분과 FileProvider의 Uri의 id는 자기껄로 바꺼야함 !!!]
[9. Gradle Scripts의 app 수준의 build.gradle 에서]
compileSdk 와 targetSdk를 33 으로 바꾼다.
[10. 메뉴 -> Build -> Generate Signed Bundle / APK 로 가서]
APK를 선택하고 key파일이 없으면 새로 생성하고 있으면 선택하여 비번넣고
Next눌러서 release로 선택하고 Finish 하면 대상폴더에 apk 파일 생성됨
이렇게 만들어진 apk파일을 전달해주면 끝!!
물론 아이폰은 안된다고 하고... -_-
(근데 유럽에서 설치파일되게 해달라고 ㅈㄹ해서 조만간 소식있을듯?)
혹시몰라 유튜브 링크 걸어놓음
How To Create WebView App In Android Studio - YouTube
그리고 압축파일도 올려놓음
코틀린으로 할껄그랬나.. -_-;
하여튼 자바/코틀린 왔다갔다 짜증남..
이게다.. 미친 오라클 때문이다..
댓글 없음:
댓글 쓰기