Android常用項目

      在〈Android常用項目〉中尚無留言

全螢幕設計

正常Activity畫面

正常繼承AppCompactActivity的畫面如下, 有Status Bar, Action Bar, 顯示區, 及軟体按鍵區(平版)

android_fullscreen_1

刪除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>

android_fullscreen_2

刪除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);
    }
}

android_fullscreen_3

螢幕轉向

限制螢幕的方向, 需更改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

發佈留言