全螢幕設計
正常Activity畫面
正常繼承AppCompactActivity的畫面如下, 有Status Bar, Action Bar, 顯示區, 及軟体按鍵區(平版)
刪除Action Bar
於設定檔<activity>標簽加入如下即可刪除Action Bar
<application android:allowBackup="true" android:icon="@drawable/icon" android:label="@string/app_name" android:largeHeap="true" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/Theme.AppCompat.Light.NoActionBar"> <==加於此處, 所有畫面都有效 <activity android:name=".MainActivity" android:theme="@style/Theme.AppCompat.Light.NoActionBar"> <==加於此處, 只有此畫面生效 <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity>
刪除Status Bar
於MainActivity.java加入如下即可刪除Status Bar. 撘配上述取消Action Bar, 即可達成全螢幕之設定
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); getWindow().setFlags( WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN ); setContentView(R.layout.activity_main); } }
螢幕轉向
限制螢幕的方向, 需更改AndroidManifest.xml的屬性, 橫向為 landscape, 縱向為portrait, 如下藍色部份
<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" package="net.ddns.mahaljsp.patrolcar"> <application ......... tools:ignore="LockedOrientationActivity" ........> <activity android:name=".MainActivity" android:screenOrientation="landscape" /> </application> </manifest>
螢幕尺寸
先宣告DisplayMeterics物件, 再由WindowManager將螢幕尺寸放入meterics物件
DisplayMetrics metrics = new DisplayMetrics(); getWindowManager().getDefaultDisplay().getMetrics(metrics); ratio = metrics.widthPixels / 1920.0f;
防止進入休眠狀態
防止進入休眠需取得權限, 所以請在AndroidManifest.xml加入如下代碼
<uses-permission android:name="android.permission.WAKE_LOCK" />
在Java代碼中, 調用PowerManager的newWakeLock取得PowerManager.WakeLock物件
PowerManager.WakeLock wakeLock;//物件變數 PowerManager pm=(PowerManager)context.getSystemService(Context.POWER_SERVICE); wakeLock=pm.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK, "LOCK");
防止休眠
wakeLock.acquire();
回復原狀
wakeLock.release();
版本取得
使用PackageManager取得packageInfo, 再由packageInfo取得versionName即可
txtVersion = (TextView) findViewById(R.id.txtVersion); try { PackageManager packageManager = getPackageManager(); PackageInfo info = packageManager.getPackageInfo(getPackageName(), 0); txtVersion.setText("Ver" + info.versionName); } catch (PackageManager.NameNotFoundException e) {}
聲音播放
聲音資源檔
在res內新增一個目錄, 目錄名稱為 raw, 然後把聲音檔copy到raw中
SoundPool
建立一個SoundPool物件, 再將聲音資源id放入SoundPool物件. 後續即可使用soundPool.play進行播放
public class MainActivity extends AppCompatActivity { SoundPool soundPool; int[] soundId = new int[2]; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); soundPool = new SoundPool.Builder() .setMaxStreams(2) .build(); soundId[0] = soundPool.load(this, R.raw.camera_click, 1); soundId[1] = soundPool.load(this, R.raw.camera_focus, 1); } public void btn1Click(View view){ soundPool.play(soundId[0], 1, 1, 1, 0, 1); } public void btn2Click(View view){ soundPool.play(soundId[1], 1, 1, 1, 0, 1); } }
Device CPU核心數
取得 /sys/devices/system/cpu目錄下, 以cpu開頭, 並緊接數字的目錄數, 即為核心數. 比如cpu0, cpu1……等目錄, 即是每個核心的資料
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
int cores=0;
try {
cores = new File("/sys/devices/system/cpu/").listFiles(CPU_FILTER).length;
}
catch (SecurityException e) {}
catch (NullPointerException e) {}
str+=String.format("CPU 核心數 : %d\n", cores);
}
private static final FileFilter CPU_FILTER = new FileFilter() {
@Override
public boolean accept(File pathname) {
String path = pathname.getName();
if (path.startsWith("cpu")) {
for (int i = 3; i < path.length(); i++) {
if (path.charAt(i) < '0' || path.charAt(i) > '9') {
return false;
}
}
return true;
}
return false;
}
};
Device Ram
Ram的資訊存放於ActivityManager.MemoryInfo物件中, 所以先產生一個memInfo空白的物件, 然後再利用ActivityManager.getMemoryInfo取得資訊, 放入memInfo 中.
ActivityManager.MemoryInfo memInfo = new ActivityManager.MemoryInfo(); ActivityManager am = (ActivityManager)getSystemService(Context.ACTIVITY_SERVICE); am.getMemoryInfo(memInfo); str+=String.format("Memory : %.2f G\n\n", memInfo.totalMem/1024/1024/1024.0f);
Device Sensor
每個人都會臭屁自已的手機有多麼的好, 多麼的神. 以下使用SensorManager直接列出所支援的Sensor種類.
SensorManager manager=(SensorManager)getSystemService(SENSOR_SERVICE); List<Sensor> list=manager.getSensorList(Sensor.TYPE_ALL); str+="支援Sensor\n"; for (Sensor s: list){ str+=String.format("設備名稱 : %s, 版本 : %s, 供應商 : %s\n", s.getName(), s.getVersion(), s.getVendor()); } txt.setText(str); txt.setMovementMethod(new ScrollingMovementMethod());
Lambda Support
Android Studio 可以支援Java 8 的 Lambda. 以下的操作步驟在Android Studio 3.2.1以上的版本驗證無誤.
1. JDK 記得要更新為 8.0
2. 在build.gradle(Module:app)新增如下藍色部份的編譯條件即可
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility = '1.8'
targetCompatibility = '1.8'
}
程式編寫
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
new Thread(()->{
while(true){
try {Thread.sleep(33);} catch (InterruptedException e) {}
}
}).start();
}
測試環境
經測試, 就算平板手機是 Android 5.0的版本, 也可以正常運作
Android 9 (API 28)網路報錯
為保証用戶數據和設備的安全, Google針對Android 9.0及以上的版本, 要求默譇使加密連線. 所以禁止 App 使用所有未加密的連線. 因此在 API 28 開始, 若使用HttpUrlConnection 進行http請求時會出現例外
W/System.err: java.io.IOException: Cleartext HTTP traffic to **** not permitted
解決方式為在AndroidManifest.xml 的 <Application ….>中, 加入
android:usesCleartextTraffic="true"
也可以使用如下比較麻煩的方法
1. 在 res/xml下新增network_security_config.xml檔
<?xml version="1.0" encoding="utf-8"?> <network-security-config> <base-config cleartextTrafficPermitted="true" /> </network-security-config>
2. 在AndroidManifest.xml application新增
<application ........ android:networkSecurityConfig="@xml/network_security_config" />
檢查是否有網路
private boolean isAvailableInternet(){ ConnectivityManager connManager = (ConnectivityManager) this.getSystemService(Context.CONNECTIVITY_SERVICE); NetworkInfo info=connManager.getActiveNetworkInfo(); if (info == null || !info.isConnected() || !info.isAvailable())return false; else return true; }
http連線類型
new Thread(()->{ String urlString = String.format("%s/login/patrolcar.php?userAccount=%s&userPassword=%s",G.url_http, strAccount, strPassword); HttpURLConnection connection = null; try { URL url = new URL(urlString); connection = (HttpURLConnection) url.openConnection(); connection.setReadTimeout(3000); connection.setConnectTimeout(3000); connection.addRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.71 Safari/537.36"); connection.setInstanceFollowRedirects(true); // 若要求回傳 200 OK 表示成功取得網頁內容 if( connection.getResponseCode() == HttpsURLConnection.HTTP_OK ){ // 讀取網頁內容 InputStream inputStream = connection.getInputStream(); BufferedReader bufferedReader = new BufferedReader( new InputStreamReader(inputStream) ); String tempStr; StringBuffer stringBuffer = new StringBuffer(); while( ( tempStr = bufferedReader.readLine() ) != null ) { stringBuffer.append( tempStr ); } bufferedReader.close(); inputStream.close(); /* // 取得網頁內容類型 String mime = connection.getContentType(); //boolean isMediaStream = false; // 判斷是否為串流檔案 if( mime.indexOf("audio") == 0 || mime.indexOf("video") == 0 ){ //isMediaStream = true; } */ // 網頁內容字串 String strWeb=stringBuffer.toString(); //Log.d("Thomas", strWeb); String[] webResult=strWeb.split("<br/>"); if(webResult.length==1){ String []tmp=webResult[0].split(":"); if(tmp[1].equals("APError"))handler.sendEmptyMessage(APERROR); else handler.sendEmptyMessage(NOTDRIVER); } else if(webResult.length>1) { G.carId = Integer.parseInt(webResult[1].split(":")[1]); G.carNo = webResult[2].split(":")[1]; G.userName = webResult[3].split(":")[1]; G.dbAccount = webResult[4].split(":")[1]; G.dbPassword = webResult[5].split(":")[1]; G.ftpAccount = webResult[6].split(":")[1]; G.ftpPassword = webResult[7].split(":")[1]; spEditor.putInt("CARID", G.carId); spEditor.putString("CARNO", G.carNo); spEditor.putString("USERNAME", G.userName); spEditor.putString("DBACCOUNT", G.dbAccount); spEditor.putString("DBPASSWORD", G.dbPassword); spEditor.putString("FTPACCOUNT", G.ftpAccount); spEditor.putString("FTPPASSWORD", G.ftpPassword); spEditor.putString("USERACCOUNT", strAccount); spEditor.putString("USERPASSWORD", strPassword); spEditor.apply(); //取得區域iList areaList G.mahalConn = new MahalConn("ip", "資料庫名", "帳號", "密碼"); try { List<Map<String, String>> list = new MahalSqlCommand("select * from 區域", G.mahalConn).executeQuery(); if (list != null && list.size()>0) { G.areaMap = new HashMap<>(); for(int i=0;i<list.size();i++){ Map<String, String> map = list.get(i); G.areaMap.put(map.get("area"), Integer.parseInt(map.get("id"))); } } } catch(SQLException ex){} handler.sendEmptyMessage(SERVER_CONNECTED); } } else{ throw new IOException("NO_INTERNET"); } } catch (IOException e) { handler.sendEmptyMessage(NO_INTERNET); } finally { if( connection != null ) { connection.disconnect(); } } }).start();
以上的代碼, 如果 build.gradle裏的 compileSdkVersion 大於等於28的話, 就無法連線. 這是因為 Google 已強制性規定不允許使用 http明碼連線. 需改為https. 而且現在也會強迫使用 28的版本.
但如果還是需要連線http, 那就需要作如下設定.
在專案res內新增xml/app actions xml file, 然後輸入檔名, 如network_security_config. 此時在res/xml/下就會多出 network_security_config.xml, 並改成如下
<?xml version="1.0" encoding="utf-8"?> <network-security-config> <base-config cleartextTrafficPermitted="true" /> </network-security-config>
另外, 在AndroidManiFest.xml需在application加入如下. 請注意, 若有 <metadata>的話, 要刪除
<application android:networkSecurityConfig="@xml/network_security_config" .....>
todo