搜尋此網誌

2017年12月31日 星期日

【Android Studio】Instant Run is disabled for non-debug variants


If you have manually set it in the manifest, then remove it and let the IDE automatically assign it. 
If you are using Gradle, make sure that your current variant is debuggable.

buildTypes {
    release {
        debuggable true  //發佈時 記得改為false
    }

2017年11月20日 星期一


https://www.ithome.com.tw/news/118397

除了用戶方面升級外,研究人員也建議,app開發商可以在WindowsManager中啟動FLAG_SECURE參數, 可確保app視窗內容不得被螢幕擷圖,或是在不安全環境下顯示。

2017年11月12日 星期日

【Android】This AsyncTask class should be static or leaks might occur

How to use a static inner AsyncTask class

ref:https://stackoverflow.com/a/46166223

To prevent leaks, you can make the the inner class static. The problem with that, though, is that you no longer have access to the Activity's UI views or member variables. You can pass in a reference to the Context but then you run the same risk of a memory leak. (Android can't garbage collect the Activity after it closes if the AsyncTask class has a strong reference to it.) The solution is to make a weak reference to the Activity (or whatever Context you need).
public class MyActivity extends AppCompatActivity {

    int mSomeMemberVariable = 123;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // start the AsyncTask, passing the Activity context
        // in to a custom constructor 
        new MyTask(this).execute();
    }

    private static class MyTask extends AsyncTask<Void, Void, String> {

        private WeakReference<MyActivity> activityReference;

        // only retain a weak reference to the activity 
        MyTask(MyActivity context) {
            activityReference = new WeakReference<>(context);
        }

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

            // do some long running task...

            return "task finished";
        }

        @Override
        protected void onPostExecute(String result) {

            // get a reference to the activity if it is still there
            MyActivity activity = activityReference.get();
            if (activity == null) return;

            // modify the activity's UI
            TextView textView = activity.findViewById(R.id.textview);
            textView.setText(result);

            // access Activity member variables
            activity.mSomeMemberVariable = 321;
        }
    }
}

Notes

  • As far as I know, this type of memory leak danger has always been true, but I only started seeing the warning in Android Studio 3.0. A lot of the main AsyncTask tutorials out there still don't deal with it (see hereherehere, and here).
  • You would also follow a similar procedure if your AsyncTask were a top-level class. A static inner class is basically the same as a top-level class in Java.
  • If you don't need the Activity itself but still want the Context (for example, to display a Toast), you can pass in a reference to the app context. In this case the AsyncTask constructor would look like this:
    private WeakReference<Application> appReference;
    
    MyTask(Application context) {
        appReference = new WeakReference<>(context);
    }
  • There are some arguments out there for ignoring this warning and just using the non-static class. After all, the AsyncTask is intended to be very short lived (a couple seconds at the longest), and it will release its reference to the Activity when it finishes anyway. See this and this.
  • Excellent article: How to Leak a Context: Handlers & Inner Classes

2017年10月15日 星期日

【Android】如何顯示Splash Screen Image不會Delay


很多android app都會使用splash screen,如下:
<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".SplashScreenActivity">
    <android.support.v7.widget.AppCompatImageView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_margin="40dp"
        android:scaleType="centerInside"
        android:layout_gravity="center"
        app:srcCompat="@drawable/img_splash_screen"/>
</FrameLayout>


通常在splash screen會顯示只有一個image的layout,這樣沒問題嗎?

是的,除了在顯示splash screen前會短暫地顯示一個全白畫面。"短暫地"是多長的時間取決於你的android硬體設備。WHY?



根據以下影片Cyril Mottier關於view hierarchy的演講,得知window theme的畫面顯示優先權高於你的layout

那要如何讓白畫面消失?幸運地,android 提供了Drawableandroid:windowBackground屬性。


解決辦法


1.Turn the splash screen to a Drawable.
drawable/img_splash_screen_for_window_theme.xml
drawable/img_splash_screen_for_window_theme.xml


2.Add android:windowBackground in your values/styles.xml.

values/styles.xml
values/styles.xml

3.Add android:windowBackground in your values/styles.xml.

2017年10月10日 星期二

【Android】使用webview速記

ref:
https://developer.android.com/guide/webapps/webview.html
  • 首先,把網頁檔(包含.htm, .html, .css)通通丟進assets目錄中,如果沒有assets資料夾,main目錄下建立一個assets資料夾。
    • 其中,這裡舉例網頁的主畫面為other.html,一個頁面透過不同的超連結直接對應各網頁。
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <link href="main.css" rel="stylesheet" type="text/css">
</head>
<body>
<h1><span id=title>webview示範</span></h1>
<p><b>Version <span id=version>0.0.1 (201710100640)</span></b></p>
<p>
<ul>
    <li><a href="cookbk.htm">使用說明</a></li>
</ul>
</p>
<p>
<!-- <p><ul><li><a href="http://www.youtube.com/watch?v=testtest">介紹影片</a></li></ul></p> -->
<p>
</p>
<p>
<ul>
    <li><a href="version_log.html">版本更新資訊</a></li>
</ul>
</p>
<p><b>需要的權限:</b>
<ul>
    <li>RECEIVE_BOOT_COMPLETED: 當設備重開機時,自動啟動通知提醒功能</li>
    <!--<li>SYSTEM_ALERT_WINDOW: 用於顯示應用使用時間過長的提醒訊息</li>-->
    <!--<li>WRITE_EXTERNAL_STORAGE: 儲存程式的執行紀錄</li>-->
</ul>
</p>

<p><b>Email:</b> <a href="mailto:xxyyzz@gmail.com">xxyyzz@gmail.com</a></p>

<p>Copyright(C) 2012-2017 ptodue Note. All Rights Reserved.</p>
</body>
</html>

  • 建立一個webview layout頁面,很多教學網頁都有,就不舉例。
  • 在webViewactivity的onCreate()載入webview。
WebView myWebView = (WebView) findViewById(R.id.webview);
myWebView.loadUrl("file:///android_asset/zh/other.html");

  • 基本上,一個載入簡單html的功能就完成。
-----------------------我是分隔線-----------------------------------------
但是,在點擊網頁中預設的連結時,總是會crash,並中斷程式。透過查firebase crash report發現是android.os.FileUriExposedExceptionj問題,通常是在android N以上會出錯。

The exception that is thrown when an application exposes a file:// Uri to another app.
  • This exposure is discouraged since the receiving app may not have access to the shared path. For example, the receiving app may not have requested the READ_EXTERNAL_STORAGE runtime permission, or the platform may be sharing the Uri across user profile boundaries.
    Instead, apps should use content:// Uris so the platform can extend temporary permission for the receiving app to access the resource.
    This is only thrown for applications targeting N or higher. Applications targeting earlier SDK versions are allowed to share file:// Uri, but it's strongly discouraged.
  • 大意:android 7.0(N)以後,不能使用file://Uri,只能使用content://Uri,因為有安全因素,所以要定義好權限問題(Intent.FLAG_GRANT_READ_URI_PERMISSION)。有看沒有懂?!
  • 網路上搜尋解決辦法:使用FileProvider,可參考
  • 把原本的做法繞過去,不確定是否最佳解?
    • 簡單來說,因為首次讀取網頁不會跳exception,所以,不透過webview hyperlink,而是直接重新載入activity,但webView load會替換成,剛剛使用者所選取的網頁路徑。
    • 做法:擷取當使用者點擊網頁對應hyper link path,並透過intentExtra Bundle傳遞此資訊,context.startActivity(this,this.class);,所以uri path會替換成剛Bundle收到的hyper link path。
    • codet趁記憶簡單寫一下:

@Override
onCraete()

WebView myWebView = (WebView) findViewById(R.id.webview);  
refreshWebView(myWebView);    //擷取網頁點擊動作,並載入

//...略   

自定義的refreshWebView(WebView myWebView),以version_log作示範:

  • 使用shouldOverrideUrlLoading 偵測擷取uri path
The shouldOverrideUrlLoading(WebView view, String url) method is deprecated in API 24 and the shouldOverrideUrlLoading(WebView view, WebResourceRequest request) method is added in API 24. If you are targeting older versions of android, you need the former method, and if you are targeting 24 (or later, if someone is reading this in distant future) it's advisable to override the latter method as well.(ref:https://stackoverflow.com/a/38484061)

String fromPath = getIntent().getExtras().getString("webviewEX","zh/other.html");  //預設路徑為zh/other.html
myWebView.loadUrl("file:///android_asset/" + fromPath);    //載入webview內容

view.setWebViewClient(new WebViewClient() {    
            @SuppressWarnings("deprecation")
            @Override
            public boolean shouldOverrideUrlLoading(WebView view, String url) {
                if (url.contains("version_log.html")) {
                    goClickPage(ctx, "zh/version_log.html");
                    return true;
                }
                return false;
            }

            @TargetApi(Build.VERSION_CODES.N)
            @Override
            public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
                if (request.getUrl().toString().contains("version_log.html")) {
                    goClickPage(ctx, "zh/version_log.html");
                }
                return true;
            }
        });

自定義的goClickPage(Context ctx, String path)

Intent starter = new Intent(context, webViewactivity.class);
starter.putExtra("webviewEX", path);
context.startActivity(starter);
完成
-----------------------我是分隔線-----------------------------------------
題外話:
如果想完完全全在activity開啟自己的瀏覽網頁的瀏覽器,而不是額外開啟Chrome之類的。可參考:
Android Working with WebView – Building a Simple In-App Browser