2022年3月21日 星期一

Android Studio IDE 錯誤

 :app:compile xxxxx JavaWithJavac FAILED

An exception has occurred in the compiler (1.8.0_312). Please file a bug against the Java compiler via the Java bug reporting page (http://bugreport.java.com) after checking the Bug Database (http://bugs.java.com) for duplicates. Include your program and the following diagnostic in your report. Thank you.

java.lang.AssertionError: annotationType(): unrecognized Attribute name MODULE (class com.sun.tools.javac.util.UnsharedNameTable$NameImpl)

 at com.sun.tools.javac.util.Assert.error(Assert.java:133)

 at com.sun.tools.javac.code.TypeAnnotations.annotationType(TypeAnnotations.java:231)

 at com.sun.tools.javac.code.TypeAnnotations$TypeAnnotationPositions.separateAnnotationsKinds(TypeAnnotations.java:294)

 at com.sun.tools.javac.code.TypeAnnotations$TypeAnnotationPositions.visitMethodDef(TypeAnnotations.java:1066)

... 


問題發生原因:

Android Studio Arctic Fox 中更新 gradle jdk 的路徑有問題

Arctic Fox 會使 JDK 1.8 無法正常工作


解決辦法:

更新 Android Studio 

並設定 gradle 路徑

改成 JDK 11 點擊 OK 即可生效。


Windows:

File> Project structure> SDK Location>Gradle Settings


Mac:

Android Studio > Preferences > Build, Execution, Deployment > Build Tools > Gradle > Gradle JDK 

2022年3月15日 星期二

Android 分享功能

 

網路圖片
https://stickershop.line-scdn.net/stickershop/v1/sticker/438329029/android/sticker.png

Intent intent = new Intent(Intent.ACTION_SEND);
if (imgPath == null || imgPath.equals("")) {
intent.setType("text/plain"); // 純文字
} else {
File f = new File(imgPath);
if (f != null && f.exists() && f.isFile()) {
intent.setType("image/jpg");
Uri u = Uri.fromFile(f);
intent.putExtra(Intent.EXTRA_STREAM, u);
}
}
intent.putExtra(Intent.EXTRA_SUBJECT, msgTitle);
intent.putExtra(Intent.EXTRA_TEXT, msgText);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
getActivity().startActivity(Intent.createChooser(intent, activityTitle));

Android 網路圖片轉Drawable

1.有一張網路圖片路徑
private void initAudio(){
new DownloadImageTask().execute(YouTubeUtil.getYoutubePic(youtubeId));
}
2. 透過異步下載
private class DownloadImageTask extends AsyncTask<String, Void, Drawable> {
protected Drawable doInBackground(String... urls) {
return loadImageFromNetwork(urls[0]);
}

protected void onPostExecute(Drawable result) {
tracks.add(new Track("Track 0", "Artist 0", result));
// mImageView.setImageDrawable(result);
}
}

Android Drawable 轉 Bitmap

 


BitmapDrawable bd = (BitmapDrawable) track.getDrawable();
icon = bd.getBitmap();

LeetCode 167. Two Sum II - Input Array Is Sorted

 https://leetcode.com/problems/two-sum-ii-input-array-is-sorted/


題目是Input是一個陣列,且經過小到大的排序,需要將兩數相加,回傳該兩數的位置+1,且兩個位置不能相同。

Example 1:

Input: numbers = [2,7,11,15], target = 9
Output: [1,2]
Explanation: The sum of 2 and 7 is 9. Therefore, index1 = 1, index2 = 2. We return [1, 2].

Example 2:

Input: numbers = [2,3,4], target = 6
Output: [1,3]
Explanation: The sum of 2 and 4 is 6. Therefore index1 = 1, index2 = 3. We return [1, 3].

Example 3:

Input: numbers = [-1,0], target = -1
Output: [1,2]
Explanation: The sum of -1 and 0 is -1. Therefore index1 = 1, index2 = 2. We return [1, 2].


解題:
一開始用最直觀的暴力解

LeetCode 9. Palindrome Number

 https://leetcode.com/problems/palindrome-number/


給定一個數字,假如數字從頭到中間和從中間到末端是一樣的
回傳 true,反之回傳 false
範例:
Example 1:

Input: x = 121
Output: true
Explanation: 121 reads as 121 from left to right and from right to left.

Example 2:

Input: x = -121
Output: false
Explanation: From left to right, it reads -121. From right to left, it becomes 121-. Therefore it is not a palindrome.

Example 3:

Input: x = 10
Output: false
Explanation: Reads 01 from right to left. Therefore it is not a palindrome.

進階解題,建議不要用字串型別來解此題


解題答案

我先直接判斷是否為 0 , 是否為負數, 判斷是否為偶數長度, 是否為奇數長度

LeetCode 13. Roman to Integer


題目是將羅馬文字轉成數字
Symbol Value
I 1
V 5
X 10
L 50
C 100
D 500
M 1000
For example, 2 is written as II in Roman numeral, just two one's added together. 12 is written as XII, which is simply X + II. The number 27 is written as XXVII, which is XX + V + II.

Roman numerals are usually written largest to smallest from left to right. However, the numeral for four is not IIII. Instead, the number four is written as IV. Because the one is before the five we subtract it making four. The same principle applies to the number nine, which is written as IX. There are six instances where subtraction is used:

I can be placed before V (5) and X (10) to make 4 and 9.
X can be placed before L (50) and C (100) to make 40 and 90.
C can be placed before D (500) and M (1000) to make 400 and 900.
Given a roman numeral, convert it to an integer.

LeetCode 1. Two Sum

 https://leetcode.com/problems/two-sum/


題目是Input是一個陣列,且沒有排序,需要將兩數相加,回傳該兩數的位置,且兩個位置不能相同。
範例:
Example 1:

Input: nums = [2,7,11,15], target = 9
Output: [0,1]
Explanation: Because nums[0] + nums[1] == 9, we return [0, 1].

Example 2:

Input: nums = [3,2,4], target = 6
Output: [1,2]

Example 3:

Input: nums = [3,3], target = 6
Output: [0,1]

解題:

LeetCode 14. Longest Common Prefix

https://leetcode.com/problems/longest-common-prefix/
題目:

Write a function to find the longest common prefix string amongst an array of strings.

If there is no common prefix, return an empty string "".

Example 1:

Input: strs = ["flower","flow","flight"]
Output: "fl"

Example 2:

Input: strs = ["dog","racecar","car"]
Output: ""
Explanation: There is no common prefix among the input strings.


Constraints:
  • 1 <= strs.length <= 200
  • 0 <= strs[i].length <= 200
  • strs[i] consists of only lower-case English letters.

2021年10月28日 星期四

Android Studio 聊天室下拉加載歷史訊息

 一開始需求是每次有新的訊息要在最下面

然後就很突然的,將 RecyclerView 設置為倒序

LinearLayoutManager linearLayoutManager = new LinearLayoutManager(getActivity(), 
LinearLayoutManager.VERTICAL, true); // 列表翻轉
linearLayoutManager.setStackFromEnd(true); // 列表在底部開始展示, 反轉後由上面開始展示
rvMessagesChat.setLayoutManager(linearLayoutManager);
rvMessagesChat.setHasFixedSize(true);
rvMessagesChat.setAnimation(null);


但是在加載新資料的時候,舊資料會被擠下去

int beforeSize = mList.size();
int afterSize = model.vModelRecord.size();
profileMessagesChatAdapter.addList(model.vModelRecord);
profileMessagesChatAdapter.notifyItemRangeInserted(beforeSize, afterSize);


所以需要新增這行,這樣就不會有擠壓的動畫

rvMessagesChat.setAnimation(null);

2021年9月15日 星期三

Samba - Ubunta 和 Windows 之間的橋樑

 1. 先在 Ubunta 安裝 Samba 

sudo apt-get install samba


2. 設定 Samba 工作群組,要和Windows 相同

 vim /etc/samba/smb.conf

3. 設定要分享的資料夾,格式如下

[ShareName] 分享資料夾名稱

path = /var/usr/    分享資料夾路徑

read only = no    是否唯讀

create mask = 777     檔案遮罩

directory mask = 777    資料夾遮至

writable = yes    是否可寫入

4. 將 Samba 加進防火牆白名單

sudo ufw status verbose → 確認防火牆狀態

sudo ufw allow samba → 將 Samba 加進防火牆白名單

sudo ufw reload → 重啟防火牆

5. 重啟 Samba 讓他吃修改後的設定檔

sudo service smbd restart

6. 完成

2021年8月25日 星期三

AppsFlyer 廣告追蹤

 Apps Flyer 是一個多平台整合的廣告追蹤網站


Apps Flyer 相關

Android SDK 集成

iOS SDK 集成


Facebook 相關

Facebook 廣告配置指南

Facebook SDK

Facebook Dashboard



iOS 14.5 官方將廣告追蹤透明化

必須經過用戶同意方可進行追蹤

再設定 Facebook 追蹤時,

必須將事件管理工具 > 資料來源 > 設定 > 為 SKAdNetwork 設定應用程式事件 開啟

並編輯事件,才可以收到來自 iOS 的資料


Swift 

AppEvents.logEvent(AppEvents.Name.completedRegistration)


2021年8月24日 星期二

iOS 訂閱性商品是否可取得續訂資料

 可以透過 iOS 驗證收據中,取得最新的訂閱資料中查看 訂閱截止日

作法:

  1. iOS成功購買產品後,呼叫 Fetch the Receipt Data(備註1) 去取 Apple Store 收據
  2. 因為 Apple Store 收據 本身有經過加密,也因為安全性的問題,需由後端去與 Apple 伺服器溝通
  3. 承第二點可透過 POST MAN 進行測試實際用法參考 (備註2)
  4. 將第一點取得的加密字串傳給後端API
  5. 由後端去和APPLE伺服器溝通,取得解碼後的收據詳細資料(JSONObject)
  6. 儲存至DB

備註:
1. iOS 原生

if let appStoreReceiptURL = Bundle.main.appStoreReceiptURL, FileManager,default.fileExists(atPath: appStoreReceiptURL.path){
  do{
    let receiptData = try Data(contentsOf: appStoreReceiptURL, options: .alwaysMapped)
    let receiptString = receiptData.base64EncodedString(options: [])

    // 呼叫 API

  } catch {
    print("Couldn't read receipt data with error: " + error.localizedDescription)
  }
}

2. 驗證收據是否正確
正式驗證 API 網址
https://buy.itunes.apple.com/verifyReceipt
沙箱驗證 API 網址
https://sandbox.itunes.apple.com/verifyReceipt
資料來源:https://www.jianshu.com/p/d0ac8cec794b

3. 驗證資料的格式,必須自建為 JSONObject
https://developer.apple.com/documentation/appstorereceipts/requestbody

4.回傳的狀態碼
https://developer.apple.com/documentation/appstorereceipts/status

5. 後端

function setReceiptIos($arr){
		if(!isset($arr)){
			return 'ESY9001';
		} else if(empty($arr["receipt_data"])){
			return 'EPT0015';
		} else if(empty($arr["uid"])){
			return 'EPT0007';
		} else if(empty($arr["iap_type"])){
			return 'EPT0016';
		} else if(empty($arr["ptid"])){
			return 'EPT0004';
		}
		
		// get mydb
		$mydb = &$this->mydb;
		
		date_default_timezone_set("Asia/Taipei");
		
		function acurl($receipt_data, $sandbox=0){
			
			// SandBox , Official
			$secret = apple_connect_serect_key;
			$POSTFIELDS = array("receipt-data" => $receipt_data, 'password'=>$secret, "");
			$POSTFIELDS = json_encode($POSTFIELDS);
	 
			//正式購買地址 沙盒購買地址
			$url_buy     = "https://buy.itunes.apple.com/verifyReceipt";
			$url_sandbox = "https://sandbox.itunes.apple.com/verifyReceipt";
			$url = $sandbox ? $url_sandbox : $url_buy;
	 
			//簡單的curl
			$ch = curl_init($url);
			curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
			curl_setopt($ch, CURLOPT_POST, 1);
			curl_setopt($ch, CURLOPT_POSTFIELDS, $POSTFIELDS);
			$result = curl_exec($ch);
			curl_close($ch);
			return $result;
		}
		
		// 請求驗證
	    $html = acurl($arr["receipt_data"]);
		$data = json_decode($html, true);
		// 如果是沙盒資料 則驗證沙盒模式
	    if($data['status']=='21007'){
	        // 請求驗證
	        $html = acurl($arr["receipt_data"], 1);
	        $data = json_decode($html,true);
	        $data['sandbox'] = '1';
	    }
		
		 // 判斷是否購買成功
	    if(intval($data['status'])===0){
	        $result=array(
	            'status'  => $data['status'],
	            'message' => '購買成功'
	            );
	    }else{
			
	        $result=array(
	            'status'  => $data['status'],
	            'message' => '購買失敗'
	            );
				
			return $result;
	    }
		
		$latestPosition = 0;
		
		
		
		$purchase_date_ms = "0";
		
		$receipt = array();
		// latest_receipt_info 只會回傳可續訂的收據, 其他收據要在 IN_APP 裡面找
		if ($arr["iap_type"] == "subscription") {
			$receipt = $data["latest_receipt_info"];
		} 
		else {
			$receiptArr = $data["receipt"]["in_app"];
			$receiptArrLength = count($receiptArr);
			if ( $receiptArrLength > 0){
				for ($i=0; $i<$receiptArrLength; $i++){
					if ( (double) $receiptArr[$i]["purchase_date_ms"] > (double) $purchase_date_ms ){
						$purchase_date_ms = (double) $receiptArr[$i]["purchase_date_ms"]; 
						$latestPosition = $i;
					}
				}
			}
			
			$receipt = $receiptArr;
		}
		
		
		$transaction_id = 'NULL';
		if (isset($receipt[$latestPosition]["transaction_id"]) ){
			$transaction_id = "'".$receipt[$latestPosition]["transaction_id"]."'"; 
		}
		
		$product_id = 'NULL';
		if (isset($receipt[$latestPosition]["product_id"]) ){
			$product_id = "'".$receipt[$latestPosition]["product_id"]."'"; 
		}
		
		
		$purchase_date = 'NULL';
		if (isset($receipt[$latestPosition]["purchase_date"]) ){
			$purchase_date = "'".date("Y-m-d H:i:s",strtotime($receipt[$latestPosition]["purchase_date"]))."'"; 
		}
		
		$expires_date = 'NULL';
		if (isset($receipt[$latestPosition]["expires_date"]) ){
			$expires_date = "'".date("Y-m-d H:i:s",strtotime($receipt[$latestPosition]["expires_date"]))."'"; 
		}
		
		$cancellation_date = 'NULL';
		if (isset($receipt[$latestPosition]["cancellation_date"]) ){
			$cancellation_date = "'".date("Y-m-d H:i:s",strtotime($receipt[$latestPosition]["cancellation_date"]))."'"; 
		}
		
		$cancellation_reason = 'NULL';
		if (isset($receipt[$latestPosition]["cancellation_reason"]) ){
			$cancellation_reason = "'".$receipt[$latestPosition]["cancellation_reason"]."'"; 
		}
		
		$original_purchase_date = 'NULL';
		if (isset($receipt[$latestPosition]["original_purchase_date"]) ){
			$original_purchase_date = "'".date("Y-m-d H:i:s",strtotime($receipt[$latestPosition]["original_purchase_date"]))."'"; 
		}
		
		{ $query = "
		INSERT INTO `".PREFIX_TABLE_0."_".DB_NAME_1."`.`".PREFIX_TABLE_0."_history_user_ios_receipt` (
			`prefixid` ,
			`huirid` ,
			`uid` ,
			`transaction_id` ,
			`product_id` ,
			`purchase_date` ,
			`expires_date` ,
			`cancellation_date` ,
			`cancellation_reason` ,
			`original_purchase_date` ,
			`encode_string` ,
			`ptid` ,
			`seq` ,
			`switch` ,
			`insertt`,
			`modifyt`
		) VALUES (
			'".PREFIX_INDEX_0."',
			NULL,
			'".$arr["uid"]."',
			{$transaction_id},
			{$product_id},
			{$purchase_date},
			{$expires_date},
			{$cancellation_date},
			{$cancellation_reason},
			{$original_purchase_date},
			'".$arr["receipt_data"]."',
			'".$arr["ptid"]."',
			'10',
			'Y',
			NOW(),
			NOW()
		)";
		}
		
		
		// echo $query ; exit;
		if(!$mydb->query($query)) return 'ESY9002';
			
		return $result;
	}

2020年8月29日 星期六

PHP Session 使用範例說明

今天想用一個session來實現用戶登錄判斷,也算是對之前session的探究,查了下資料session的運行機制如下:


  session是服務器端的一種會話機制,當客戶端的請求服務器創建一個session時,服務器會先檢測該請求裡面是否包含一個惟一的sessionID,如果是,說明服務器已經為該用戶創建過session,只要按照該sesionID檢索出該用戶的session供用戶使用,如果沒有sessionID,服務器會為該用戶新建一個帶有唯一表示服sessionID的session。創建完成後,該sessionID會被服務器返回給客戶端,保存到客戶端本地。


  一般保存該session ID的機制是Cookie,但是由於Cookies可以被人為禁止,這就得保證Cookies被禁止之後,仍舊可以通過session進行會話,一般是通過url重寫進行,表現形式為http://...../xxx;jsessionid= ByOK3vjFD75aPnrF7C2HmdnV6QZcEbzWoWiBYEnLerjQ99zWpBng!-145788764,另一種是作為查詢字符串附加在URL後面,表現形式為http://...../xxx?jsessionid=ByOK3vjFD75aPnrF7C2HmdnV6QZcEbzWoWiBYEnLerjQ99zWpBng!-145788764這兩種方式對於用戶來說是沒有區別的,只是服務器在解析的時候處理的方式不同,採用第一種方式也有利於把session id的信息和正常程序參數區分開來。

為了在整個交互過程中始終保持狀態,就必須在每個客戶端可能請求的路徑後面都包含這個session id。





另外是關於session失效的誤區:

  在談論session機制的時候,常常聽到這樣一種誤解“只要關閉瀏覽器,session就消失了”。其實可以想像一下會員卡的例子,除非顧客主動 對店家提出銷卡,否則店家絕對不會輕易刪除顧客的資料。對session來說也是一樣的,除非程序通知服務器刪除一個session,否則服務器會一直保 留,程序一般都是在用戶做log off的時候發個指令去刪除session。然而瀏覽器從來不會主動在關閉之前通知服務器它將要關閉,因此服務器根本不會有機會知道瀏覽器已經關閉,之所 以會有這種錯覺,是大部分session機制都使用會話cookie來保存session id,而關閉瀏覽器後這個 session id就消失了,再次連接服務器時也就無法找到原來的session。如果服務器設置的cookie被保存到硬盤上,或者使用某種手段改寫瀏覽器發出的 HTTP請求頭,把原來的session id發送給服務器,則再次打開瀏覽器仍然能夠找到原來的session。


  恰恰是由於關閉瀏覽器不會導致session被刪除,迫使服務器為seesion設置了一個失效時間,當距離客戶端上一次使用session的時間超過這個失效時間時,服務器就可以認為客戶端已經停止了活動,才會把session刪除以節省存儲空間。


好了,廢話說了一大堆,說session丟失的解決辦法吧:

1、session_start();應該盡量放置到頁面的頂部;

2、如果php.ini裡面沒有配置 session Autostart的話,每次會話之前,都得手動開啟session:session_start();

3、session是php裡面的超全局變量,跟$_GET,$_POST,$_SERVER一樣,所以使用的時候必須大寫:$_SESSION['username']=$username;

4、跨頁面傳遞示例:a.php頁面傳遞$_SESSION['username']到b.php:

a.php: 

<?php
session_start();
$username=$_POST['username'];
$_SESSION['username']=$username;
?>

b.php

<?php
session_start();
echo $_SESSION['username'];
?>

2019年8月27日 星期二

GitHub 上傳物件步驟

STEP.1 開啟 Git Bash
STEP.2 cd 至 你的目錄

STEP.3 echo "# Homeet" >> README.md -- 新增一個README.md 檔案

STEP.4 git init -- 初始化 git


STEP.5 git add . -- 新增所有檔案

STEP.6 git commit -m "提交記錄文字" -- 提交檔案

STEP.7 git push -u origin master -- 推取(上傳)至master 分支

STEP.8 完成

2019年6月10日 星期一

期末專題:RWD、版控


  1. 因每個人製作的網頁不同,為了讓整個網站整齊一致,所以統一網頁的寬度,並因平台不同,順便制定需使用RWD,對各平台撰寫CSS。
  2. 因為版本會有三個版本,目前是用GitLab+TortoiseGit來做版本控制
選用GitLabTortoiseGit的原因有兩個:
1. 它們有中文
2. GitLab的專案可以私人化

2019年6月7日 星期五

Git Bash 帳號密碼輸入錯誤 如何重新輸入

解決辦法:
1. 打開控制台 ( Win+R,輸入control)
2. 打開認證管理員
3. 找到你所要修改的相關認證,編輯或刪除即可

2019年3月14日 星期四

Android Studio IDE 錯誤

 :app:compile xxxxx JavaWithJavac FAILED An exception has occurred in the compiler (1.8.0_312). Please file a bug against the Java compiler ...