Popular Posts
Enable SSL connection for Jsoup import org.jsoup.Connection; import org.jsoup.Jsoup; import javax.net.ssl.*; import java.io.IOException; import java.security.KeyManagement... Word break tag : <wbr/> (HTML5) The  HTML  <wbr>  tag  is  used  defines  a  potential  line  break  point  if  needed.  This  stands  for  Word  BReak. This  is  u... Build an OpenVPN server on android device Preparation An android device, in this case, Sony xperia Z is used Root permission required Linux Deploy for deploy i...
Blog Archive
Stats
List changed files between commits
List all changed files between commits of git.
git diff --name-only SHA1 SHA2
or
git diff --name-only HEAD~10 HEAD~5
Problem on result of DexFile.entries()

Recently I upgrade my Android Studio to version 2.0, and try to build and run some app on devices. Unfortunately something goes wrong when app executing. When app invokes DexFile.entries() for the class names, the result between android 4 and 5+ is different.

Android version


Okay, let's do some simple test, here is a brand new project I've currently created. The build config and code is below:
App only invokes DexFile.entries() and show the result on a TextView.
On the result, there is a large difference between android 4.2.2 and android 5.0.0
On version 4.2.2, the listed class count is 1555, but on version 5.0.0, there are only 31 entries return.
build.gradle
apply plugin: 'com.android.application'

android {
    compileSdkVersion 23
    buildToolsVersion "23.0.3"

    defaultConfig {
        applicationId "com.prhythm.myapplication"
        minSdkVersion 14
        targetSdkVersion 23
        versionCode 1
        versionName "1.0"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    testCompile 'junit:junit:4.12'
    compile 'com.android.support:appcompat-v7:23.3.0'
}
MainActivity.java
package com.prhythm.myapplication;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.text.TextUtils;
import android.widget.TextView;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;

import dalvik.system.DexFile;

public class MainActivity extends AppCompatActivity {

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

        TextView text = (TextView) findViewById(R.id.content);

        try {
            List<String> paths = new ArrayList<>();
            DexFile dex = new DexFile(getApplicationInfo().sourceDir);
            for (Enumeration<String> entries = dex.entries(); entries.hasMoreElements(); ) {
                paths.add(entries.nextElement());
            }
            text.append(String.format("Total entry count: %d%n", paths.size()));
            text.append("* " + TextUtils.join("\n* ", paths));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
Run Result on android 4.2.2 & 5.0.0
Solution:

Looks like this issue is related to the new InstantRun feature in the Android Plugin for Gradle 2.0.0. getPackageCodePath() gets a String pointing towards the base.apk file in the Android file system. If we unzip that apk we can find one or several .dex files inside its root folder. The entries obtained from the method df.entries() iterates over the .dex files found in that root folder in order to obtain all of its compiled classes. However, if we are using the new Android Plugin for Gradle, we will only find the .dex related to the android runtime and instant run (packages com.tools.android.fd.runtime, com.tools.android.fd.common and com.tools.android.tools.ir.api). Every other class will be compiled in several .dex files, zipped into a file called instant-run.zip and placed into the root folder of the apk. That's why the code posted in the question is not able to list all the classes within the app. Still, this will only affect Debug builds since the Release ones don't feature InstantRun.

List<String> getClasses() throws IOException {
    List<String> paths = new ArrayList<>();
    File instantRunDir = new File(getBaseContext().getFilesDir(), "instant-run/dex");
    if (instantRunDir.exists()) {
        for (File dexPath : instantRunDir.listFiles()) {
            DexFile dex = new DexFile(dexPath);
            for (Enumeration<String> entries = dex.entries(); entries.hasMoreElements(); ) {
                paths.add(entries.nextElement());
            }
        }
    }

    DexFile dex = new DexFile(getApplicationInfo().sourceDir);
    for (Enumeration<String> entries = dex.entries(); entries.hasMoreElements(); ) {
        paths.add(entries.nextElement());
    }

    return paths;
}
Operate form in WebView and get content
class ContentLoader {
    @JavascriptInterface
    public void load(String body) {
        // do something
    }
}

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

    WebView webView = createWebView();

    webView.loadUrl("https://www.somedoamin.com/product.jsp");
    // enable javascript
    webView.getSettings().setJavaScriptEnabled(true);
    // add interface
    webView.addJavascriptInterface(new ContentLoader(), "ContentLoader");
    webView.setWebViewClient(new WebViewClient() {

        int action;

        @Override
        public void onPageFinished(WebView view, String url) {
            switch (action) {
                case 0:
                    // fill field and submit
                    view.loadUrl("javascript:{\n" +
                            "\tdocument.querySelector('input[name=cgi_tel_no]').value='12345678';\n" +
                            "\tdocument.querySelector('input[name=cgi_id_no]').value='abcdef';\n" +
                            "\tsubmitMyForm();\n" +
                            "};");
                case 1:
                    // get content from 'ContentLoader' interface
                    view.loadUrl("javascript:{\n" +
                            "\twindow.ContentLoader.load('<body>'+document.body.innerHTML+'</body>');\t\n" +
                            "};");
                    break;
            }

            action++;
        }
    });
}

WebView createWebView() {
    WindowManager windowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
    WindowManager.LayoutParams params = new WindowManager.LayoutParams(WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY, WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE, PixelFormat.TRANSLUCENT);
    params.gravity = Gravity.TOP | Gravity.LEFT;
    params.x = 0;
    params.y = 0;
    params.width = 0;
    params.height = 0;

    LinearLayout layout = new LinearLayout(this);
    layout.setLayoutParams(new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT));

    WebView view = new WebView(this);
    view.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT));
    layout.addView(view);

    windowManager.addView(layout, params);

    return view;
}
Gitlab

安裝

線上安裝,支援的 OS 有 Ubuntu、Debian、CentOS、Raspberry PI 2 on Raspbian。直接依照說明即可完成。

建議安裝時也同時下載目前安裝的版本,避免之後有移轉問題需要還原。各版本下載位址為

備份

# 執行備份
gitlab-rake gitlab:backup:create

還原

# 複製備份檔至備份路徑
sudo cp 1493107454_2017_04_25_9.1.0_gitlab_backup.tar /var/opt/gitlab/backups/

# 停止服務
sudo gitlab-ctl stop unicorn
sudo gitlab-ctl stop sidekiq

# 開始還原
sudo gitlab-rake gitlab:backup:restore BACKUP=1493107454_2017_04_25_9.1.0

# 啟動系統
sudo gitlab-ctl start

# 檢查還原結果
sudo gitlab-rake gitlab:check SANITIZE=true

由 SVN 移轉

建立 svn 與 git 作者的 mapping 檔(author.txt)

nanashi07 = Bruce <nanashi07@haha.com>

由 svn 簽出為 git 專案

# clone from svn
# with standard layout(trunk/branches/tags), "-s" argument is required
# "author.txt" contains a list that svn accounts mapping to git authors
git svn clone https://10.10.10.162/svn/caml/Caml.Maker --authors-file=author.txt --no-metadata -s Caml.Maker
cd Caml.Maker
# convert tags
git for-each-ref refs/remotes/tags | cut -d / -f 4- | grep -v @ | while read tagname; do git tag "$tagname" "tags/$tagname"; git branch -r -d "tags/$tagname"; done
# convert branches
git for-each-ref refs/remotes | cut -d / -f 3- | grep -v @ | while read branchname; do git branch "$branchname" "refs/remotes/$branchname"; git branch -r -d "$branchname"; done
# add remote repository
git remote add origin http://nanashi07:password@10.10.10.222/caml/Caml.Maker.git
# push to remote
git push origin --all
cd ..

升級

# 停止服務
sudo gitlab-ctl stop unicorn
sudo gitlab-ctl stop sidekiq
sudo gitlab-ctl stop nginx

# 備份
sudo gitlab-rake gitlab:backup:create

# 安裝下載的檔案
# Ubuntu/Debian:
sudo dpkg -i gitlab_x.x.x-omnibus.xxx.deb

# CentOS:
sudo rpm -Uvh gitlab-x.x.x_xxx.rpm

# 重新設定與啟動
sudo gitlab-ctl reconfigure
sudo gitlab-ctl restart
Output a file like url via routing
Prepare a binary output action for file output
[RoutePrefix("images")]
public class ImageController : Controller
{
    // GET: ~/images/profile.png
    // GET: ~/images/1873429732.jpg
    [HttpGet]
    [Route("{name}.{ext}")]
    public ActionResult ReadImage(string name, string ext)
    {
        if (System.IO.File.Exists(Server.MapPath(string.Format("~/uploads/{0}.{1}", name, ext))))
        {
            byte[] data = System.IO.File.ReadAllBytes(Server.MapPath(string.Format("~/uploads/{0}.{1}", name, ext)));
            string contentType = MimeMapping.GetMimeMapping(System.IO.Path.GetFileName(string.Format("{0}.{1}", name, ext)));
            return File(data, contentType);
        }
        else
        {
            return new HttpStatusCodeResult(404);
        }
    }
}

Then try test it, but respone 404 not found.




Add runAllManagedModulesForAllRequests="true" on modules section.


<?xml version="1.0" encoding="utf-8"?>
<configuration>
  ...
  <system.webServer>
    ...
    <modules runAllManagedModulesForAllRequests="true">
      <remove name="ApplicationInsightsWebTracking"/>
      <add name="ApplicationInsightsWebTracking" type="Microsoft.ApplicationInsights.Web.ApplicationInsightsHttpModule, Microsoft.AI.Web"
        preCondition="managedHandler"/>
    </modules>
  </system.webServer>
  ...
</configuration>

Then try it again, image rendered success.


Install subversion with apache server on linux (ubuntu)
# 更新 apt package
sudo apt-get update
# 安裝 package
sudo apt-get install subversion apache2 libapache2-svn apache2-utils
# 建立 svn 資料夾
sudo mkdir -p /svn/repos/
# 建立 svn 使用者 (apache)
sudo mkdir -p /svn/users/
# 建立使用者(第一次時有參數-c)
sudo htpasswd -cm /svn/users/svnpasswd nanashi07
# 刪除使用者後再重建(重設密碼)
#sudo htpasswd -D /svn/users/svnpasswd nanashi07
#sudo htpasswd -m /svn/users/svnpasswd nanashi07
# 修改 apache 設定
sudo vi /etc/apache2/mods-enabled/dav_svn.conf
<Location /svn>
  DAV svn
  SVNParentPath /svn/repos/
  AuthType Basic
  AuthName "SVN Repository"
  AuthUserFile /svn/users/svnpasswd
  Require valid-user
</Location>
# 修改 apache 監聽埠
sudo vi /etc/apache2/ports.conf
# 重新啟動 apache
sudo service apache2 restart
# 建立資源庫
sudo svnadmin create /svn/repos/myproject
# 設定資源庫權限
sudo chown -R www-data:www-data /svn/repos/myproject
# 設定資源庫權限
sudo chmod -R g+rws /svn/repos/myproject
Add file to google drive
using Google.Apis.Auth.OAuth2;
using Google.Apis.Drive.v2;
using Google.Apis.Drive.v2.Data;
using Google.Apis.Services;
using Google.Apis.Util.Store;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace SyncDump
{
    class Program
    {
        /// <summary>
        /// 存取權限
        /// </summary>
        static string[] Scopes = { DriveService.Scope.Drive, DriveService.Scope.DriveFile };
        static string ApplicationName = "Drive Uploader";

        static void Main(string[] args)
        {
            // 檢查輸入參數
            if (args.Length < 1)
            {
                Console.WriteLine("No target file argument!");
                return;
            }

            // 要上傳的檔案
            string dumpPath = args[0];
            string dumpName = System.IO.Path.GetFileName(dumpPath);

            // 載入驗證資訊
            GoogleCredential credential;
            using (var stream = new System.IO.FileStream("client_secret.json", System.IO.FileMode.Open, System.IO.FileAccess.Read))
            {
                credential = GoogleCredential.FromStream(stream);
            }

            // 建立 service
            DriveService service = new DriveService(new BaseClientService.Initializer()
            {
                HttpClientInitializer = credential.CreateScoped(Scopes),
                ApplicationName = ApplicationName
            });
            
            // 進行上傳
            insertFile(
                service,
                dumpName,
                dumpName,
                System.Configuration.ConfigurationManager.AppSettings["drive.folder.id"],
                "application/oct-stream",
                dumpPath
            );
        }

        /// <summary>
        /// 新增檔案
        /// </summary>
        /// <param name="service">Drive API service instance.</param>
        /// <param name="title">Title of the file to insert, including the extension.</param>
        /// <param name="description">Description of the file to insert.</param>
        /// <param name="parentId">Parent folder's ID.</param>
        /// <param name="mimeType">MIME type of the file to insert.</param>
        /// <param name="filename">Filename of the file to insert.</param><br>  /// <returns>Inserted file metadata, null is returned if an API error occurred.</returns>
        private static File insertFile(DriveService service, string title, string description, string parentId, string mimeType, string filename)
        {
            // 檔案資訊
            File body = new File();
            body.Title = title;
            body.Description = description;
            body.MimeType = mimeType;

            // 設定上傳位置
            if (!string.IsNullOrEmpty(parentId))
            {
                body.Parents = new List<ParentReference>() { new ParentReference() { Id = parentId } };
            }

            // 檔案內容
            byte[] byteArray = System.IO.File.ReadAllBytes(filename);
            System.IO.MemoryStream stream = new System.IO.MemoryStream(byteArray);
            try
            {
                FilesResource.InsertMediaUpload request = service.Files.Insert(body, stream, mimeType);
                request.Upload();

                File file = request.ResponseBody;

                Console.WriteLine("{0:yyyy/MM/dd HH:mm:ss} File {1} is uploaded to drive", DateTime.Now, file.Title);

                return file;
            }
            catch (Exception e)
            {
                Console.WriteLine("An error occurred: " + e.Message);
                return null;
            }
        }
    }
}