使用GitLabApi獲取遠(yuǎn)程倉庫中的文件內(nèi)容

在一些實(shí)際情況中,希望能夠直接像讀取本地文件一樣讀取遠(yuǎn)程倉庫中的文件內(nèi)容贷屎,避免git操作失敗的情況下讀取的本地緩存的文件內(nèi)容贺氓。由于項(xiàng)目使用gitLab管理配置文件,查詢了GitLabApi续捂,其提供了諸多API接口毫玖,包括常見的git操作、項(xiàng)目管理以及我們需要的獲取文件內(nèi)容等接口。

1.接口分析

查詢GitLab api宜雀,可以容易找到獲取文件內(nèi)容的API文檔:GitLab獲取倉庫中文件內(nèi)容,可以發(fā)現(xiàn)怕享,其格式要求為:

GET /projects/:id/repository/files/:file_path
curl --request GET --header 'PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK' 'https://gitlab.example.com/api/v4/projects/13083/repository/files/app%2Fmodels%2Fkey%2Erb?ref=master'

通過分析可以發(fā)現(xiàn)提澎,如果想獲取倉庫中文件內(nèi)容,需要以下幾個(gè)要素:

  • 倉庫地址
  • 項(xiàng)目id
  • 用戶的private token
  • 經(jīng)過url編碼的文件全路徑
  • 文件所在的分支
    由此侮邀,我在實(shí)現(xiàn)時(shí)將API接口整理為一個(gè)常量:
private static String GITLAB_FILECONTENT_API = "http://#{REPO_IP}/api/v3/projects/#{PROJECT_ID}/repository/files?private_token=#{PRIVATE_TOKEN}&file_path=#{FILE_PATH}&ref=#{BRANCH_NAME}";

2.獲取用戶的private token

GitLabApi關(guān)于獲取個(gè)人口令的API可以整理為常量類:

private static String GITLAB_SESSION_API = "http://#{REPO_IP}/api/v3/session?login=#{USER_NAME}&password=#{PASSWORD}";

提供如下3個(gè)變量即可獲取指定用戶的token:

  • 倉庫地址
  • 用戶名
  • 密碼
    經(jīng)過簡(jiǎn)單的json解析就能獲取到結(jié)果坏怪,代碼如下:
/**
     * 根據(jù)用戶名稱和密碼獲取gitlab的private token,為Post請(qǐng)求
     * 
     * @param ip    gitlab倉庫的ip
     * @param userName  登陸gitlab的用戶名
     * @param password  登陸gitlab的密碼
     * @return  返回該用戶的private token
     */
    public static String getPrivateTokenByPassword(String ip, String userName, String password) {

        /** 1.參數(shù)替換绊茧,生成獲取指定用戶privateToken地址 */
        //  校驗(yàn)參數(shù)
        Objects.requireNonNull(ip, "參數(shù)ip不能為空铝宵!");
        Objects.requireNonNull(userName, "參數(shù)userName不能為空!");
        Objects.requireNonNull(password, "參數(shù)password不能為空华畏!");
        //  參數(shù)準(zhǔn)備鹏秋,存入map
        Map<String, String> params = new HashMap<String, String>(4);
        params.put("REPO_IP", ip);
        params.put("USER_NAME", userName);
        params.put("PASSWORD", password);
        // 調(diào)用工具類替換,得到具體的調(diào)用地址
        String reqUserTokenUrl = PlaceholderUtil.anotherReplace(GITLAB_SESSION_API, params);
        sysLogger.debug(String.format("獲取用戶:%s的private token地址為:%s", userName,reqUserTokenUrl));
        
        /** 2.訪問url,獲取指定用戶的信息 */
        RestTemplate restTemplate = new RestTemplate();
        ResponseEntity<String> response = restTemplate.postForEntity(reqUserTokenUrl, null,String.class);
        sysLogger.debug(String.format("響應(yīng)頭為:%s亡笑,響應(yīng)體為:%s", response.getHeaders(), response.getBody()));
        

        /** 3.解析結(jié)果 */
        String body = response.getBody();
        JSONObject jsonBody = JsonUtil.parseObjectToJSONObject(body);
        String privateToken =jsonBody.getString("private_token");
        sysLogger.debug(String.format("獲取到用戶:%s的privateToken為:%s", userName, privateToken));
        
        /** 4.返回privateToken */     
        return privateToken;

    }

注意侣夷,這個(gè)接口時(shí)POST請(qǐng)求,正確的HTTP響應(yīng)碼是201仑乌,而非200.

3.獲取項(xiàng)目的projectId

projectId可以直接到gitlab具體項(xiàng)目信息中查找百拓,但是也有相關(guān)的api可以依據(jù)項(xiàng)目名稱查詢。從使用的角度講绝骚,配置項(xiàng)目的名稱更加方便耐版,配置項(xiàng)目的projectId增加配置項(xiàng),且如果提供獲取projectId的方法压汪,內(nèi)部調(diào)用可以去掉該項(xiàng)的配置粪牲。
獲取projectId的api參考:獲取指定項(xiàng)目的projectId,需要如下3個(gè)要素:

  • 倉庫ip
  • 項(xiàng)目id:The ID or URL-encoded path of the project止剖,即提供id或者是項(xiàng)目path腺阳,需要經(jīng)url編碼(namespace + projectName)落君,參見:項(xiàng)目path的url編碼
  • private token
    提取出常量類:
private static String GITLAB_SINGLE_PROJECT_API = "http://#{REPO_IP}/api/v3/projects/#{PROJECT_PATH}?private_token=#{PRIVATE_TOKEN}";

然后對(duì)響應(yīng)碼為200的正確json返回結(jié)果信息解析即可得到,代碼如下:

/**
     * 使用gitLab api獲取指定項(xiàng)目的projectId亭引,為Get請(qǐng)求
     * 
     * @param ip  項(xiàng)目倉庫的ip地址
     * @param projectPath   項(xiàng)目的path绎速,如:http://192.168.59.185/acountting/dispatcher-cloud.git,則projectPath為:acountting/dispatcher-cloud
     * @param privateToken  用戶個(gè)人訪問gitlab庫時(shí)的privateToken焙蚓,可以通過{@link GitLabAPIUtils#getPrivateTokenByPassword}獲取
     * @return  返回目的projectId
     */
    public static String getProjectId(String ip, String projectPath, String privateToken) {
        /** 1.參數(shù)替換纹冤,生成訪問獲取project信息的uri地址 */
        //  校驗(yàn)參數(shù)
        Objects.requireNonNull(ip, "參數(shù)ip不能為空!");
        Objects.requireNonNull(projectPath, "參數(shù)projectPath不能為空购公!");
        Objects.requireNonNull(privateToken, "參數(shù)privateToken不能為空萌京!");
        //  參數(shù)準(zhǔn)備,存入map
        Map<String, String> params = new HashMap<String, String>(4);
        params.put("REPO_IP", ip);
        params.put("PRIVATE_TOKEN", privateToken);      
        // gitlab api要求項(xiàng)目的path需要安裝uri編碼格式進(jìn)行編碼宏浩,比如"/"編碼為"%2F"
        try {
            params.put("PROJECT_PATH", URLEncoder.encode(projectPath, "UTF-8"));
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(String.format("對(duì)%s進(jìn)行URI編碼出錯(cuò)知残!", projectPath));
        }
        // 調(diào)用工具類替換,得到具體的調(diào)用地址
        String getSingleProjectUrl = PlaceholderUtil.anotherReplace(GITLAB_SINGLE_PROJECT_API, params);
        sysLogger.debug(String.format("獲取projectId的url:%s", getSingleProjectUrl));

        //  創(chuàng)建URI對(duì)象
        URI url = null;
        try {
            url = new URI(getSingleProjectUrl);
        } catch (URISyntaxException e) {
            throw new RuntimeException(String.format("使用%s創(chuàng)建URI出錯(cuò)!", getSingleProjectUrl));
        }
        
        /** 2.訪問url比庄,獲取制定project的信息 */
        RestTemplate restTemplate = new RestTemplate();
        ResponseEntity<String> reslut = restTemplate.getForEntity(url, String.class);
        sysLogger.debug(String.format("響應(yīng)頭為:%s求妹,響應(yīng)體為:%s", reslut.getHeaders(), reslut.getBody()));
        
        /** 3.解析結(jié)果 */
        if (reslut.getStatusCode() != HttpStatus.OK ) {
            throw new RuntimeException(String.format("請(qǐng)求%s出錯(cuò)!錯(cuò)誤碼為:%s", url, reslut.getStatusCode()));
        }
        //  如果響應(yīng)碼是200佳窑,說明正常拿到響應(yīng)結(jié)果制恍,解析出projectId返回即可
        JSONObject responBody = JsonUtil.parseObjectToJSONObject(reslut.getBody());
        String projectRepo = responBody.getString("http_url_to_repo");
        String projectId = responBody.getString("id");
        sysLogger.info(String.format("獲取到項(xiàng)目:%s的projectId為:%s", projectRepo, projectId));
        
        /** 4.返回projectId */
        return projectId;
    }

需要特別注意,在方法中對(duì)配置好的project path進(jìn)行url編碼后华嘹,沒有直接使用RestTemplate創(chuàng)建get請(qǐng)求獲取項(xiàng)目信息吧趣,因?yàn)閷?shí)踐中發(fā)現(xiàn)會(huì)出現(xiàn)將本義編碼好的如:src%2FHelloWorld.java變?yōu)椋簊rc%256FHelloWorld.java法竞,具體沒有深入RestTemplate源碼耙厚,所以直接創(chuàng)建URI對(duì)象,避免這種情況出現(xiàn)岔霸。

4 獲取倉庫文件內(nèi)容

api參考:gitlab獲取倉庫文件內(nèi)容薛躬,需要提供3個(gè)參數(shù):

  • private token
  • projectId
  • 文件全路徑,需經(jīng)過url編碼呆细,如: main%2Fclass%HelloWorld.java
  • 文件所在分支branch
    整理出具體api的常量類:
private static String GITLAB_FILECONTENT_API = "http://#{REPO_IP}/api/v3/projects/#{PROJECT_ID}/repository/files?private_token=#{PRIVATE_TOKEN}&file_path=#{FILE_PATH}&ref=#{BRANCH_NAME}";

返回的內(nèi)容json格式如下:

{
  "file_name": "HelloWorld.java",
  "file_path": "main/class/HelloWorld.java",
  "size": 1476,
  "encoding": "base64",
  "content": "IyA9PSBTY2hlbWEgSW5mb3...",
  "content_sha256": "4c294617b60715c1d218e61164a3abd4808a4284cbc30e6728a01ad9aada4481",
  "ref": "master",
  "blob_id": "79f7bbd25901e8334750839545a9bd021f0e4c83",
  "commit_id": "d5a3ff139356ce33e37e73add446f16869741b50",
  "last_commit_id": "570e7b2abdd848b95f2f578043fc23bd6f6fd24d"
}

特別需要注意的是返回的文件內(nèi)容是base64編碼型宝,拿到結(jié)果后需要解碼才能獲取原始內(nèi)容。如果想直接獲取原始文件內(nèi)容:獲取原始文件內(nèi)容
所以獲取方法如下:

public static String getFileContentFromRepository(String ip,String projectPath,String userName,String password,
            String fileFullPath, String branchName) throws Exception {
        //  校驗(yàn)參數(shù)
        Objects.requireNonNull(ip, "參數(shù)ip不能為空絮爷!");
        Objects.requireNonNull(projectPath, "參數(shù)projectPath不能為空趴酣!");
        Objects.requireNonNull(userName, "參數(shù)userName不能為空!");
        Objects.requireNonNull(password, "參數(shù)password不能為空坑夯!");
        Objects.requireNonNull(fileFullPath, "參數(shù)fileFullPath不能為空岖寞!");
        Objects.requireNonNull(branchName, "參數(shù)branchName不能為空!");
        
        /** 1.依據(jù)用戶名柜蜈、密碼獲取到用戶的privateToken */
        String privateToken = getPrivateTokenByPassword(ip, userName, password);
        
        /** 2.使用privateToken獲取項(xiàng)目的projectId */
        String projectId = getProjectId(ip, projectPath, privateToken);
        
        /** 3.使用參數(shù)替換形成請(qǐng)求git庫中文件內(nèi)容的uri */
        //  參數(shù)準(zhǔn)備仗谆,存入map
        Map<String, String> params = new HashMap<String, String>(4);
        params.put("REPO_IP", ip);
        params.put("PROJECT_ID", projectId);    
        params.put("PRIVATE_TOKEN", privateToken);  
        params.put("FILE_PATH", fileFullPath);      
        params.put("BRANCH_NAME", branchName);  
        //  使用工具類替換參數(shù)
        String reqFileCotnetUri = PlaceholderUtil.anotherReplace(GITLAB_FILECONTENT_API, params);
        sysLogger.debug(String.format("獲取文件:%s的uri:%s", fileFullPath, reqFileCotnetUri));       
        
        /** 4.請(qǐng)求gitlab獲取文件內(nèi)容 */
        RestTemplate restTemplate = new RestTemplate();
        ResponseEntity<String> response = restTemplate.getForEntity(reqFileCotnetUri, String.class);
        sysLogger.debug(String.format("響應(yīng)頭為:%s指巡,響應(yīng)體為:%s", response.getHeaders(), response.getBody()));
        
        /** 5.解析響應(yīng)結(jié)果內(nèi)容 */
        String body = response.getBody();
        JSONObject jsonBody = JsonUtil.parseObjectToJSONObject(body);
        String fileName = jsonBody.getString("file_name");
        String filePath = jsonBody.getString("file_path");
        String encoding = jsonBody.getString("encoding");
        String content = jsonBody.getString("content");
        String commitId = jsonBody.getString("commit_id");
        String lastCommitId = jsonBody.getString("last_commit_id");

        //  內(nèi)容已經(jīng)base64編碼,如果需要獲取原始文件內(nèi)容可以參看api:https://docs.gitlab.com/ee/api/repository_files.html#get-raw-file-from-repository
        content = new String(Base64.decode(content), "UTF-8");

        sysLogger.debug(String.format(
                "獲取http://%s 上%s項(xiàng)目 %s分支的%s文件,響應(yīng)信息是:fileName :%s ,filePath:%s , 編碼:%s ,內(nèi)容:%s , commitId:%s ,lastCommitId :%s",
                ip,projectPath, branchName, fileFullPath, fileName, filePath, encoding, content, commitId,
                lastCommitId));

        /** 6.返回指定文件的內(nèi)容 */
        sysLogger.debug(String.format("解析得到文件內(nèi)容為:%s", content));
        return content;
    }
    

大致經(jīng)過如下過程:

  • 使用用戶名隶垮、密碼獲取private token
  • 使用private token藻雪,project path獲取projectId
  • 使用private token,projectId狸吞,結(jié)合file path勉耀、分支branch參數(shù),獲取文件base64編碼內(nèi)容蹋偏,然后解碼即可

此外瑰排,考慮到項(xiàng)目中實(shí)際獲取的是配置文件內(nèi)容,為了剔除不必要的空行暖侨、注釋行椭住,提供了工具類方法對(duì)解碼的原始文件內(nèi)容進(jìn)行處理:

public static String getCleanFileContentFromRepository(String ip,String projectPath,String userName,String password,
            String fileFullPath, String branchName) throws Exception{
        
        /**  1.獲取到原始文件內(nèi)容 */
        String fileContent = getFileContentFromRepository(ip, projectPath, userName, password, fileFullPath, branchName);
        if (fileContent.isEmpty()) {
            return fileContent;
        }
        
        /** 2.所有行轉(zhuǎn)換為list,并過濾掉空行字逗、#開頭的注釋行 */
        List<String> list = Arrays.asList(fileContent.split("\n")).stream().filter(a-> (!a.trim().isEmpty()&&!a.trim().startsWith("#"))).collect(Collectors.toList());
        
        /** 3.轉(zhuǎn)為一整行字符串返回 */
        StringBuilder sb = new StringBuilder();
        int size =list.size();
        for (int i = 0; i < size; i++) {
            sb.append(list.get(i).trim());
        }
        return sb.toString();
    }

5 其它相關(guān)

5.1 替換參數(shù)工具類

對(duì)#{}包裹的參數(shù)進(jìn)行替換京郑,代碼如下:

public class PlaceholderUtil {
    /** 默認(rèn)替換形如#{param}的占位符 */
    private static Pattern pattern = Pattern.compile("\\#\\{.*?\\}");
    
    /**
     * 替換字符串中形如#{}的占位符
     * @param src
     * @param parameters
     * @return
     */
    public static String replace(String src, Map<String, Object> parameters) {
        Matcher paraMatcher = pattern.matcher(src);

        // 存儲(chǔ)參數(shù)名
        String paraName = "";
        String result = new String(src);

        while (paraMatcher.find()) {
            paraName = paraMatcher.group().replaceAll("\\#\\{", "").replaceAll("\\}", "");
            Object objParam = parameters.get(paraName);
            if(objParam!=null){
                result = result.replace(paraMatcher.group(), objParam.toString());
            }
        }
        return result;
    }
    
    
    /**
     * 替換字符串中形如#{}的占位符
     * @param src
     * @param parameters
     * @return
     */
    public static String anotherReplace(String str, Map<String, String> params) {
        Map<String, Object> newParams = new HashMap<>(params);
        return replace(str, newParams);
    }
    
}

5.2 測(cè)試類:

public class GitLabAPIUtilsTest {
    
    String repoIp; 
    String privateToken;
    String projectPath1;
    String projectPath2;
    

    String userName;
    String password;
    
    String fileFullPath;
    String branchName;

    @Before
    public void setUp() throws Exception {
        repoIp = "gitlab倉庫ip";
        projectPath1 = "acountting/accounting-config-repo";
        projectPath2 = "acountting/csv-filefront-cloud";
        
        userName = "用戶名";
        password="密碼";
        
        fileFullPath = "/apps/cmup-clearing/0055-account.config";
        branchName = "develop";
    }

    @After
    public void tearDown() throws Exception {
        repoIp = null;
        projectPath1  = null;
        projectPath2  = null;       
        userName = null;
        password = null;        
        fileFullPath = null;
        branchName = null;
    }

    @Test
    public void testGetProjectId() {
        String privateToken = GitLabAPIUtils.getPrivateTokenByPassword(repoIp, userName, password);
        String projectId = GitLabAPIUtils.getProjectId(repoIp, projectPath1, privateToken);
        System.out.println("projectId = " + projectId);
    }

    @Test
    public void testGetPrivateToken() {
        String privateToken = GitLabAPIUtils.getPrivateTokenByPassword(repoIp, userName, password);
        System.out.println("projectId = " + privateToken);
    }
    
    @Test
    public void testGetFileContent() {
        String fileContent;
        try {
            fileContent = GitLabAPIUtils.getCleanFileContentFromRepository(repoIp, projectPath1, userName, password, fileFullPath, branchName);

            System.out.println("fileContent = " + fileContent);
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市葫掉,隨后出現(xiàn)的幾起案子些举,更是在濱河造成了極大的恐慌,老刑警劉巖俭厚,帶你破解...
    沈念sama閱讀 211,194評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件户魏,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡挪挤,警方通過查閱死者的電腦和手機(jī)叼丑,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,058評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來扛门,“玉大人鸠信,你說我怎么就攤上這事÷壅” “怎么了星立?”我有些...
    開封第一講書人閱讀 156,780評(píng)論 0 346
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)葬凳。 經(jīng)常有香客問我绰垂,道長(zhǎng),這世上最難降的妖魔是什么火焰? 我笑而不...
    開封第一講書人閱讀 56,388評(píng)論 1 283
  • 正文 為了忘掉前任劲装,我火速辦了婚禮,結(jié)果婚禮上荐健,老公的妹妹穿的比我還像新娘酱畅。我一直安慰自己琳袄,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,430評(píng)論 5 384
  • 文/花漫 我一把揭開白布纺酸。 她就那樣靜靜地躺著窖逗,像睡著了一般。 火紅的嫁衣襯著肌膚如雪餐蔬。 梳的紋絲不亂的頭發(fā)上碎紊,一...
    開封第一講書人閱讀 49,764評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音樊诺,去河邊找鬼仗考。 笑死,一個(gè)胖子當(dāng)著我的面吹牛词爬,可吹牛的內(nèi)容都是我干的秃嗜。 我是一名探鬼主播,決...
    沈念sama閱讀 38,907評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼顿膨,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼锅锨!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起恋沃,我...
    開封第一講書人閱讀 37,679評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤必搞,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后囊咏,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體恕洲,經(jīng)...
    沈念sama閱讀 44,122評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,459評(píng)論 2 325
  • 正文 我和宋清朗相戀三年梅割,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了霜第。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,605評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡炮捧,死狀恐怖庶诡,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情咆课,我是刑警寧澤,帶...
    沈念sama閱讀 34,270評(píng)論 4 329
  • 正文 年R本政府宣布扯俱,位于F島的核電站书蚪,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏迅栅。R本人自食惡果不足惜殊校,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,867評(píng)論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望读存。 院中可真熱鬧为流,春花似錦呕屎、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,734評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至莲祸,卻和暖如春蹂安,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背锐帜。 一陣腳步聲響...
    開封第一講書人閱讀 31,961評(píng)論 1 265
  • 我被黑心中介騙來泰國打工田盈, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人缴阎。 一個(gè)月前我還...
    沈念sama閱讀 46,297評(píng)論 2 360
  • 正文 我出身青樓允瞧,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國和親蛮拔。 傳聞我的和親對(duì)象是個(gè)殘疾皇子瓷式,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,472評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn)语泽,斷路器贸典,智...
    卡卡羅2017閱讀 134,629評(píng)論 18 139
  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,749評(píng)論 25 707
  • 這周的主題其實(shí)想了很久,打開電腦的時(shí)候甚至腦子里都完全是一片空白踱卵,所以就先把電腦晾在一邊廊驼,打開“得到”隨意瀏覽了起...
    麥斯巴斯曼閱讀 2,680評(píng)論 1 0
  • 那是一雙怎樣的眼睛 如墨般漆黑 那是雙愛笑的眼睛 它看著周圍的歡樂 它也笑了 那是雙會(huì)流淚的眼睛 在深夜 在無人...
    招搖的狗尾巴草閱讀 150評(píng)論 0 2
  • 互聯(lián)網(wǎng)時(shí)代,風(fēng)云變幻惋砂、暗流涌動(dòng)妒挎,大到信息產(chǎn)業(yè)巨頭、小到我們每一個(gè)普普通通的人都深處其中西饵、受其影響酝掩。從農(nóng)耕時(shí)代到工業(yè)...
    閆留威_強(qiáng)化班閱讀 406評(píng)論 4 4