<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[舞影凌风]]></title><description><![CDATA[Thoughts, stories and ideas.]]></description><link>http://www.apkfuns.com/</link><generator>Ghost 0.7</generator><lastBuildDate>Wed, 29 Apr 2026 16:02:47 GMT</lastBuildDate><atom:link href="http://www.apkfuns.com/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[Android ANR日志分析]]></title><description><![CDATA[<h3 id="anr">ANR介绍和类型</h3>

<blockquote>
  <p>当Android应用程序的UI线程被阻塞太久时，会触发一个“应用程序没有响应”(ANR)错误。如果app在前台，系统会向用户显示一个对话框，如图1所示。ANR对话框为用户提供了强制退出应用程序的机会。 <a href="https://developer.android.com/topic/performance/vitals/anr">https://developer.android.com/topic/performance/vitals/anr</a></p>
</blockquote>

<p>出现ANR的一般有以下几种类型：搜索<code>ANR in</code> 可以查看抛出ANR日志线程</p>

<h5 id="1keydispatchtimeout">1: KeyDispatchTimeout</h5>

<ul>
<li>input事件在5S内没有处理完成发生了ANR。</li>
<li>logcat日志关键字：Reason: Input dispatching timed out (Waiting because the focused window has not finished
processing the input events that were previously delivered to it.</li></ul>]]></description><link>http://www.apkfuns.com/android-anr-log-analysis/</link><guid isPermaLink="false">3da1a3a3-7ae5-4717-be19-8797a10a690c</guid><category><![CDATA[android]]></category><category><![CDATA[anr]]></category><dc:creator><![CDATA[舞影凌风]]></dc:creator><pubDate>Wed, 18 Dec 2019 07:20:18 GMT</pubDate><content:encoded><![CDATA[<h3 id="anr">ANR介绍和类型</h3>

<blockquote>
  <p>当Android应用程序的UI线程被阻塞太久时，会触发一个“应用程序没有响应”(ANR)错误。如果app在前台，系统会向用户显示一个对话框，如图1所示。ANR对话框为用户提供了强制退出应用程序的机会。 <a href="https://developer.android.com/topic/performance/vitals/anr">https://developer.android.com/topic/performance/vitals/anr</a></p>
</blockquote>

<p>出现ANR的一般有以下几种类型：搜索<code>ANR in</code> 可以查看抛出ANR日志线程</p>

<h5 id="1keydispatchtimeout">1: KeyDispatchTimeout</h5>

<ul>
<li>input事件在5S内没有处理完成发生了ANR。</li>
<li>logcat日志关键字：Reason: Input dispatching timed out (Waiting because the focused window has not finished
processing the input events that were previously delivered to it.)</li>
</ul>

<h5 id="2broadcasttimeout">2: BroadcastTimeout</h5>

<ul>
<li>前台Broadcast：onReceiver在10S内没有处理完成发生ANR。</li>
<li>后台Broadcast：onReceiver在60s内没有处理完成发生ANR。</li>
<li>logcat日志关键字：Reason: Broadcast of Intent</li>
</ul>

<h5 id="3servicetimeout">3: ServiceTimeout</h5>

<ul>
<li>前台Service：onCreate，onStart，onBind等生命周期在20s内没有处理完成发生ANR。</li>
<li>后台Service：onCreate，onStart，onBind等生命周期在200s内没有处理完成发生ANR</li>
<li>logcat日志关键字：Reason: Executing service</li>
</ul>

<h5 id="4contentprovidertimeout">4: ContentProviderTimeout</h5>

<ul>
<li>ContentProvider 在10S内没有处理完成发生ANR。 </li>
<li>logcat日志关键字：Reason: timeout publishing content providers</li>
</ul>

<h4 id="anr">诊断ANR</h4>

<ul>
<li>CPU密集，导致主线程没法抢占cpu时间片,要注意cpu占用高的进程</li>
<li>高IO，如不当访问数据库导致数据库负载过重时(log中cpu的使用iowait占比高)</li>
<li>低内存（low memory），如内存不足导致block在创建bitmap上</li>
<li>死锁引发ANR,非主线程持有主线程需要的锁对象,导致主线程等待超时,通常log中会有以下字段 Blocked | - locked | waiting to lock | held by thread,这个时候cpu多数是空闲，使用占比很低</li>
<li>当前应用进程进行进程间通信请求其他进程，其他进程的操作长时间没有反馈,例如操作硬件Camera</li>
<li>Service binder数量达到上限</li>
<li>在system_server中触发WatchDog ANR</li>
</ul>

<p>当遇到ANR时，Android会存储跟踪信息。 在较旧的OS版本上，设备上只有一个/data/anr/traces.txt文件。 在较新的OS发行版中，存在多个/ data / anr / anr_ *文件。可以通过adb 获取: </p>

<pre><code>adb root  
adb shell ls /data/anr  
adb pull /data/anr/&lt;filename&gt;  
</code></pre>

<h4 id="traces">traces日志参数介绍</h4>

<pre><code>AsyncTask #2" prio=5 tid=18 Runnable  
  | group="main" sCount=0 dsCount=0 obj=0x12c333a0 self=0x94c87100
  | sysTid=25287 nice=10 cgrp=default sched=0/0 handle=0x94b80920
  | state=R schedstat=( 0 0 0 ) utm=757 stm=0 core=3 HZ=100
  | stack=0x94a7e000-0x94a80000 stackSize=1038KB
  | held mutexes= "mutator lock"(shared held)
  at com.android.developer.anrsample.BubbleSort.sort(BubbleSort.java:8)
  at com.android.developer.anrsample.MainActivity$LockTask.doInBackground(MainActivity.java:147)
  - locked &lt;0x083105ee&gt; (a java.lang.Boolean)
  at com.android.developer.anrsample.MainActivity$LockTask.doInBackground(MainActivity.java:135)
  at android.os.AsyncTask$2.call(AsyncTask.java:305)
  at java.util.concurrent.FutureTask.run(FutureTask.java:237)
  at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:243)
  at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1133)
  at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:607)
  at java.lang.Thread.run(Thread.java:761)
</code></pre>

<ul>
<li><strong>main</strong>：main标识是主线程，如果是线程，那么命名成“Thread-X”的格式,x表示线程id,逐步递增。</li>
<li><strong>prio</strong>：线程优先级,默认是5</li>
<li><strong>tid</strong>：tid不是线程的id，是线程唯一标识ID</li>
<li><strong>group</strong>：是线程组名称</li>
<li><strong>sCount</strong>：该线程被挂起的次数</li>
<li><strong>dsCount</strong>：是线程被调试器挂起的次数</li>
<li><strong>obj</strong>：对象地址</li>
<li><strong>self</strong>：该线程Native的地址</li>
<li><strong>sysTid</strong>：是线程号(主线程的线程号和进程号相同)</li>
<li><strong>nice</strong>：是线程的调度优先级</li>
<li><strong>sched</strong>：分别标志了线程的调度策略和优先级</li>
<li><strong>cgrp</strong>：调度归属组</li>
<li><strong>handle</strong>：线程处理函数的地址。</li>
<li><strong>state</strong>：是调度状态</li>
<li><strong>schedstat</strong>：从 /proc/[pid]/task/[tid]/schedstat读出，三个值分别表示线程在cpu上执行的时间、线程的等待时间和线程执行的时间片长度，不支持这项信息的三个值都是0；</li>
<li><strong>utm</strong>：是线程用户态下使用的时间值(单位是jiffies）</li>
<li><strong>stm</strong>：是内核态下的调度时间值</li>
<li><strong>core</strong>：是最后执行这个线程的cpu核的序号。</li>
</ul>

<h4 id="">线程状态</h4>

<p><img src="https://upload-images.jianshu.io/upload_images/279004-23f8a173e9950f14.png" alt=""></p>

<h4 id="anrlog">ANR发生之后log中记录的关键字段</h4>

<ul>
<li>am_anr： 描述了anr发生时的事件，原因，进程。如果是自己的代码导致的，这里会输出你代码具体的调用类</li>
<li>ANR in：main<em>log 或 Sys</em>log ，描述anr发生的应用模块</li>
<li>Reason:原因</li>
<li>Blocked | - locked | waiting to lock | held by thread  发生在死锁的情况下就会出现这三个关键字段</li>
<li>WATCHDOG KILLING SYSTEM PROCESS </li>
</ul>

<pre><code>grep -rn "ANR in\|ANRManager: Reason:\|ANRManager: Android time\|am_anr\|WATCHDOG KILLING SYSTEM PROCESS\| iowait\| Blocked\| held by thread\| waiting to lock"

# sublime
ANR in|Reason:|Android time|am_anr|WATCHDOG KILLING SYSTEM PROCESS| iowait| Blocked| held by thread| waiting to lock

# 高效的过滤命令，虽然grep很好用，文件超大的时候还是ag速度快
ag "ANR in|ANRManager: Reason:|ANRManager: Android time|am_anr|WATCHDOG KILLING SYSTEM PROCESS| iowait| Blocked| held by thread| waiting to lock"  
</code></pre>

<h3 id="">参考文档</h3>

<ul>
<li><a href="https://developer.android.com/topic/performance/vitals/anr">ANRs</a></li>
<li><a href="https://juejin.im/post/5be698d4e51d452acb74ea4c">Android ANR日志分析指南</a></li>
<li><a href="https://mp.weixin.qq.com/s/4w202K0WnNrazmEHd6grQA">看完这篇 Android ANR 分析，就可以和面试官装逼了！</a></li>
<li><a href="https://stackoverflow.com/questions/704311/android-how-do-i-investigate-an-anr">Android - how do I investigate an ANR?</a></li>
<li><a href="https://www.jianshu.com/p/8964812972be">如何分析ANR Log的总结</a></li>
<li><a href="https://www.jianshu.com/p/90eede51df55">给解决问题ANR一个印象</a></li>
<li><a href="https://www.jianshu.com/p/d19c34e7e9bd">Android ANR分析详解</a></li>
<li><a href="https://www.jianshu.com/p/388166988cef">Android ANR：原理分析及解决办法</a></li>
</ul>]]></content:encoded></item><item><title><![CDATA[this view is not available until indices are built 解决方案]]></title><description><![CDATA[<p>在jetbrains插件开发过程中经常遇到下面这个问题，启动IDE后项目需要同步，插件需要等待项目同步完成才能显示，明明插件和项目同步没有关系的。</p>

<blockquote>
  <p>this view is not available until indices are built</p>
</blockquote>

<p>怎么去解决这个问题呢。在jetbrains论坛找到了解决方案。实现<code>ToolWindowFactory</code>接口的同时还需要实现<code>DumbAware</code>接口，这样就不需要等项目同步完成了。</p>

<pre><code class="language-java">public class DevToolFactory implements ToolWindowFactory, DumbAware {  
    @Override
    public void createToolWindowContent(@NotNull Project project, @NotNull ToolWindow toolWindow) {
        MainComponent.getInstance(project).initView(toolWindow);
    }
}
</code></pre>

<h3 id="">参考文档</h3>

<ul>
<li><a href="https://intellij-support.jetbrains.com/hc/en-us/community/posts/206783775-Keeping-tool-window-enabled-while-IDEA-in-dumb-mode">Keeping tool window enabled while IDEA in</a></li></ul>]]></description><link>http://www.apkfuns.com/this-view-is-not-available-until-indices-are-built-jie-jue-fang-an/</link><guid isPermaLink="false">2bcdfc59-d48b-42c9-b00d-dd6c11ec5d6c</guid><category><![CDATA[intellij IDEA]]></category><category><![CDATA[插件]]></category><dc:creator><![CDATA[舞影凌风]]></dc:creator><pubDate>Fri, 03 May 2019 13:32:08 GMT</pubDate><content:encoded><![CDATA[<p>在jetbrains插件开发过程中经常遇到下面这个问题，启动IDE后项目需要同步，插件需要等待项目同步完成才能显示，明明插件和项目同步没有关系的。</p>

<blockquote>
  <p>this view is not available until indices are built</p>
</blockquote>

<p>怎么去解决这个问题呢。在jetbrains论坛找到了解决方案。实现<code>ToolWindowFactory</code>接口的同时还需要实现<code>DumbAware</code>接口，这样就不需要等项目同步完成了。</p>

<pre><code class="language-java">public class DevToolFactory implements ToolWindowFactory, DumbAware {  
    @Override
    public void createToolWindowContent(@NotNull Project project, @NotNull ToolWindow toolWindow) {
        MainComponent.getInstance(project).initView(toolWindow);
    }
}
</code></pre>

<h3 id="">参考文档</h3>

<ul>
<li><a href="https://intellij-support.jetbrains.com/hc/en-us/community/posts/206783775-Keeping-tool-window-enabled-while-IDEA-in-dumb-mode">Keeping tool window enabled while IDEA in "dumb" mode</a></li>
</ul>]]></content:encoded></item><item><title><![CDATA[scrapy入门教程]]></title><description><![CDATA[<h3 id="">安装和入门案例</h3>

<ul>
<li><a href="https://cloud.tencent.com/developer/article/1004722">腾讯云主机Python3环境安装Scrapy爬虫框架过程及常见错误</a></li>
<li><a href="https://www.cnblogs.com/qcloud1001/p/6826573.html">scrapy 爬虫框架入门案例详解</a></li>
</ul>

<h3 id="">常见问题</h3>

<ul>
<li><a href="https://stackoverflow.com/questions/50324329/running-scrapy-but-it-error-no-module-named-util">No module named _util</a></li>
</ul>

<pre><code>pip install Twisted==16.4.1  
</code></pre>

<h3 id="">参考文档</h3>

<ul>
<li><a href="https://zhuanlan.zhihu.com/p/33816647">Scrapy连接到各类数据库(SQLite,Mysql,Mongodb,Redis)</a></li>
</ul>]]></description><link>http://www.apkfuns.com/scrapy-introductory-course/</link><guid isPermaLink="false">ac281bba-78f5-415e-a945-e14649f8a514</guid><category><![CDATA[python学习笔记]]></category><category><![CDATA[scrapy]]></category><dc:creator><![CDATA[舞影凌风]]></dc:creator><pubDate>Sat, 16 Mar 2019 10:28:32 GMT</pubDate><content:encoded><![CDATA[<h3 id="">安装和入门案例</h3>

<ul>
<li><a href="https://cloud.tencent.com/developer/article/1004722">腾讯云主机Python3环境安装Scrapy爬虫框架过程及常见错误</a></li>
<li><a href="https://www.cnblogs.com/qcloud1001/p/6826573.html">scrapy 爬虫框架入门案例详解</a></li>
</ul>

<h3 id="">常见问题</h3>

<ul>
<li><a href="https://stackoverflow.com/questions/50324329/running-scrapy-but-it-error-no-module-named-util">No module named _util</a></li>
</ul>

<pre><code>pip install Twisted==16.4.1  
</code></pre>

<h3 id="">参考文档</h3>

<ul>
<li><a href="https://zhuanlan.zhihu.com/p/33816647">Scrapy连接到各类数据库(SQLite,Mysql,Mongodb,Redis)</a></li>
</ul>]]></content:encoded></item><item><title><![CDATA[Java代理模式-动态代理]]></title><description><![CDATA[<h3 id="">描述</h3>

<blockquote>
  <p>代理模式是常用的java设计模式，他的特征是代理类与委托类有同样的接口，代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类，以及事后处理消息等。代理类与委托类之间通常会存在关联关系，一个代理类的对象与一个委托类的对象关联，代理类的对象本身并不真正实现服务，而是通过调用委托类的对象的相关方法，来提供特定的服务。简单的说就是，我们在访问实际对象时，是通过代理对象来访问的，代理模式就是在访问实际对象时引入一定程度的间接性，因为这种间接性，可以附加多种用途。</p>
</blockquote>

<p><img src="http://qiniu.apkfuns.com/image/2/78/249eff6a6a5ed9d6945d1a46ce07b.jpg" alt=""></p>

<h3 id="example">Example</h3>

<pre><code>// Person.java
public interface Person {  
    void eat(String food);
}

// Main.java
public class Man implements Person {

    private String name;

    public Man(String name) {
        this.name = name;
    }

    @Override
    public void eat(String</code></pre>]]></description><link>http://www.apkfuns.com/introduction-of-java-proxy-mode/</link><guid isPermaLink="false">4f04fe3b-0675-4c4f-a461-89ed87a8c49c</guid><category><![CDATA[java]]></category><category><![CDATA[设计模式]]></category><dc:creator><![CDATA[舞影凌风]]></dc:creator><pubDate>Mon, 21 Jan 2019 14:53:00 GMT</pubDate><content:encoded><![CDATA[<h3 id="">描述</h3>

<blockquote>
  <p>代理模式是常用的java设计模式，他的特征是代理类与委托类有同样的接口，代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类，以及事后处理消息等。代理类与委托类之间通常会存在关联关系，一个代理类的对象与一个委托类的对象关联，代理类的对象本身并不真正实现服务，而是通过调用委托类的对象的相关方法，来提供特定的服务。简单的说就是，我们在访问实际对象时，是通过代理对象来访问的，代理模式就是在访问实际对象时引入一定程度的间接性，因为这种间接性，可以附加多种用途。</p>
</blockquote>

<p><img src="http://qiniu.apkfuns.com/image/2/78/249eff6a6a5ed9d6945d1a46ce07b.jpg" alt=""></p>

<h3 id="example">Example</h3>

<pre><code>// Person.java
public interface Person {  
    void eat(String food);
}

// Main.java
public class Man implements Person {

    private String name;

    public Man(String name) {
        this.name = name;
    }

    @Override
    public void eat(String food) {
        System.out.println(this.name + " eat " + food);
    }
}

// InvokeProxy.java
public class InvokeProxy&lt;T&gt; implements InvocationHandler {

    private T t;

    public InvokeProxy(T t) {
        this.t = t;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        return method.invoke(this.t, args);
    }
}

// Test.java
Person person = new Man("One");  
Object object = Proxy.newProxyInstance(Person.class.getClassLoader(), new Class[]{Person.class},new InvokeProxy&lt;&gt;(person));  
        if (object instanceof Person) {
            ((Person) object).eat("appl");
        }
</code></pre>

<h3 id="">原理分析</h3>

<p>动态代理最关键方法是<code>Proxy.newProxyInstance</code>, 我们先看下这个方法源码</p>

<pre><code>@CallerSensitive
public static Object newProxyInstance(ClassLoader loader,  
                                          Class&lt;?&gt;[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    {
        Objects.requireNonNull(h);

        final Class&lt;?&gt;[] intfs = interfaces.clone();
        final SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
        }

        // #1 关键方法，生成新的class文件
        Class&lt;?&gt; cl = getProxyClass0(loader, intfs);

        try {
            if (sm != null) {
                checkNewProxyPermission(Reflection.getCallerClass(), cl);
            }
              // #2 获取新建class的构造函数，生成的class构造函数参数是InvocationHandler
            final Constructor&lt;?&gt; cons = cl.getConstructor(constructorParams);
            final InvocationHandler ih = h;
            if (!Modifier.isPublic(cl.getModifiers())) {
                AccessController.doPrivileged(new PrivilegedAction&lt;Void&gt;() {
                    public Void run() {
                        cons.setAccessible(true);
                        return null;
                    }
                });
            }
            // #3 调用构造函数创建新对象
            return cons.newInstance(new Object[]{h});
        } catch (IllegalAccessException|InstantiationException e) {
            throw new InternalError(e.toString(), e);
        } catch (InvocationTargetException e) {
            Throwable t = e.getCause();
            if (t instanceof RuntimeException) {
                throw (RuntimeException) t;
            } else {
                throw new InternalError(t.toString(), t);
            }
        } catch (NoSuchMethodException e) {
            throw new InternalError(e.toString(), e);
        }
    }
</code></pre>

<p>然后再来分析下<code>getProxyClass0(loader, intfs)</code></p>

<pre><code>private static Class&lt;?&gt; getProxyClass0(ClassLoader loader,  
                                           Class&lt;?&gt;... interfaces) {
        return proxyClassCache.get(loader, interfaces);
    }

// proxyClassCache 是一个 java.lang.reflect.WeakCache对象, 传递了两个参数KeyFactory, ProxyClassFactory

proxyClassCache = new WeakCache&lt;&gt;(new KeyFactory(), new ProxyClassFactory()); 

// 获取和创建内容都在get方法里面, 注意这里key传的classloader， 第二个参数是接口的class对象，里面包含各种缓存机制，我们主要看下创建方法

...
Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));  
Supplier&lt;V&gt; supplier = valuesMap.get(subKey);  
Factory factory = null;

while (true) {  
if (supplier != null) {  
   // supplier might be a Factory or a CacheValue&lt;V&gt; instance
   V value = supplier.get();
   if (value != null) {
       return value;
   }
}
...
supplier 是之前构造WeakCache构造函数传过来的ProxyClassFactory对象，supplier.get()执行了 ProxyClassFactory#apply(ClassLoader loader, Class&lt;?&gt;[] interfaces) 

// 然后重点看下java.lang.reflect.Proxy.ProxyClassFactory#apply   
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(  
                proxyName, interfaces, accessFlags);
try {  
   return defineClass0(loader, proxyName,
                                    proxyClassFile, 0, proxyClassFile.length);
}
// 主要调用ProxyGenerator.generateProxyClass 生成class文件内容，然后defineClass0写入文件。
private static native Class&lt;?&gt; defineClass0(ClassLoader loader, String name,  
                                                byte[] b, int off, int len);
// defineClass0是一个native方法，没有搜索它的c层的实现。我们来看下ProxyGenerator.generateProxyClass生成的内容到底是咋样的。
</code></pre>

<p>系统生成的class直接在内存，所以看不到class文件，我们可以自己写个demo写入文件</p>

<pre><code>byte[] classFile = ProxyGenerator.generateProxyClass("$Proxy0", Man.class.getInterfaces());  
        String path = "./Man.class";
        try(FileOutputStream fos = new FileOutputStream(path)) {
            fos.write(classFile);
            fos.flush();
            System.out.println("代理类class文件写入成功");
        } catch (Exception e) {
            System.out.println("写文件错误");
        }
</code></pre>

<p>文件内容如下</p>

<pre><code>//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

import com.baidu.searchbox.proxy.Person;  
import java.lang.reflect.InvocationHandler;  
import java.lang.reflect.Method;  
import java.lang.reflect.Proxy;  
import java.lang.reflect.UndeclaredThrowableException;

public final class $Proxy0 extends Proxy implements Person {  
    private static Method m1;
    private static Method m2;
    private static Method m3;
    private static Method m0;

    public $Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }

    public final boolean equals(Object var1) throws  {
        try {
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final String toString() throws  {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final void eat(String var1) throws  {
        try {
            super.h.invoke(this, m3, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final int hashCode() throws  {
        try {
            return (Integer)super.h.invoke(this, m0, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m3 = Class.forName("com.baidu.searchbox.proxy.Person").getMethod("eat", Class.forName("java.lang.String"));
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}
</code></pre>

<p>一个继承自Proxy实现Person接口的类，里面所有的方法都是通过调用构造函数里面InvocationHandler#invoke来实现的，这就回到了之前我们自己创建的InvocationHandler类，第一个参数是生成的对象，第二个是当前Method，第三个调用方法传递参数的数组，到这里动态代理逻辑就比较清楚了。</p>

<h3 id="retrofit">模式实践(实现简单的retrofit)</h3>

<p>首先实现两个简单的注解</p>

<pre><code>@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Get {  
    String value();
}

@Documented
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface Query {  
    String value();
}
</code></pre>

<p>写一个接口来定义方法</p>

<pre><code>public interface ServerAPI {  
    @Get("https://www.baidu.com/")
    String getBaiduHome(@Query("type") String type);

    @Get("https://www.qq.com/")
    String getQQHome(@Query("type") String type);
}
</code></pre>

<p>生成接口的方法， 简单的输出了接口定义的参数</p>

<pre><code>public class APIManager {

    @NotNull
    public static &lt;T&gt; T create(Class&lt;T&gt; tClass) {
        Object result = Proxy.newProxyInstance(tClass.getClassLoader(), new Class[]{tClass}, (proxy, method, args) -&gt; {
            Get getAnnotation = method.getAnnotation(Get.class);
            if (getAnnotation == null) {
                return null;
            }
            String url = getAnnotation.value();
            String type = (String) args[0];
            return "method=" + method.getName() + ", url=" + url + ", type=" + type;
        });
        return (T) result;
    }
}
</code></pre>

<p>调用demo</p>

<pre><code>String response = APIManager.create(ServerAPI.class).getBaiduHome("aaa");  
System.out.println(response);  
response = APIManager.create(ServerAPI.class).getQQHome("bbb");  
System.out.println(response);

// 运行结果
method=getBaiduHome, url=https://www.baidu.com/, type=aaa  
method=getQQHome, url=https://www.qq.com/, type=bbb  
</code></pre>

<h3 id="">小结</h3>

<ul>
<li>方便在不修改原代码的情况下扩展方法 (需要继承同一个接口)</li>
<li>像retrofit一样，动态且批量对接口进行处理</li>
<li>待补充</li>
</ul>

<h3 id="">参考文档</h3>

<ul>
<li><a href="https://www.cnblogs.com/gonjan-blog/p/6685611.html">java动态代理实现与原理详细分析</a></li>
<li><a href="https://juejin.im/post/5c419ee36fb9a04a0e2d7cf8">动态代理+注解(DynamicProxyAndAnnotations)</a></li>
<li><a href="https://blog.csdn.net/lz710117239/article/details/78658168">java动态代理原理源码解析（jdk8)</a></li>
</ul>]]></content:encoded></item><item><title><![CDATA[Git删除单个文件所有记录]]></title><description><![CDATA[<p>Github上经常出现传输了错误文件的问题，如密码手机等隐私内容，需要彻底删除所有提交记录，用下面这段代码就可以了。</p>

<pre><code>git filter-branch --force --index-filter 'git rm --cached --ignore-unmatch 删除文件的相对路径' --prune-empty --tag-name-filter cat -- --all

git push origin master --force

rm -rf .git/refs/original/

git reflog expire --expire=now --all

git gc --prune=now

git gc --aggressive --prune=now  
</code></pre>

<p>不记得这段代码从哪复制了，感谢原作者的无私奉献，(*/ω＼*)~</p>

<p><img width="200px" src="https://ws2.sinaimg.cn/large/9150e4e5ly1fpsu9bgwr8j20go0efaad.jpg"></p>]]></description><link>http://www.apkfuns.com/git-deletes-all-records-from-a-single-file/</link><guid isPermaLink="false">efa19276-d45e-4dfb-8d80-b42913257b68</guid><category><![CDATA[git]]></category><dc:creator><![CDATA[舞影凌风]]></dc:creator><pubDate>Sun, 20 Jan 2019 03:48:10 GMT</pubDate><content:encoded><![CDATA[<p>Github上经常出现传输了错误文件的问题，如密码手机等隐私内容，需要彻底删除所有提交记录，用下面这段代码就可以了。</p>

<pre><code>git filter-branch --force --index-filter 'git rm --cached --ignore-unmatch 删除文件的相对路径' --prune-empty --tag-name-filter cat -- --all

git push origin master --force

rm -rf .git/refs/original/

git reflog expire --expire=now --all

git gc --prune=now

git gc --aggressive --prune=now  
</code></pre>

<p>不记得这段代码从哪复制了，感谢原作者的无私奉献，(*/ω＼*)~</p>

<p><img width="200px" src="https://ws2.sinaimg.cn/large/9150e4e5ly1fpsu9bgwr8j20go0efaad.jpg"></p>]]></content:encoded></item><item><title><![CDATA[[转]Java volatile总结]]></title><description><![CDATA[<h3 id="">内存模型的相关概念</h3>

<blockquote>
  <p>计算机在执行程序时，每条指令都是在CPU中执行的，而执行指令过程中，势必涉及到数据的读取和写入。由于程序运行过程中的临时数据是存放在主存（物理内存）当中的，这时就存在一个问题，由于CPU执行速度很快，而从内存读取数据和向内存写入数据的过程跟CPU执行指令的速度比起来要慢的多，因此如果任何时候对数据的操作都要通过和内存的交互来进行，会大大降低指令执行的速度。因此在CPU里面就有了高速缓存。也就是，当程序在运行过程中，会将运算需要的数据从主存复制一份到CPU的高速缓存当中，那么CPU进行计算时就可以直接从它的高速缓存读取数据和向其中写入数据，当运算结束之后，再将高速缓存中的数据刷新到主存当中。</p>
</blockquote>

<p>举个简单的例子，比如下面的这段代码：<code>i = i + 1;</code>当线程执行这个语句时，会先从主存当中读取i的值，然后复制一份到高速缓存当中，然后CPU执行指令对i进行加1操作，然后将数据写入高速缓存，最后将高速缓存中i最新的值刷新到主存当中。</p>

<p>这个代码在单线程中运行是没有任何问题的，但是在多线程中运行就会有问题了。在多核CPU中，每条线程可能运行于不同的CPU中，因此每个线程运行时有自己的高速缓存（对单核CPU来说，其实也会出现这种问题，只不过是以线程调度的形式来分别执行的）。本文我们以多核CPU为例。比如同时有2个线程执行这段代码，假如初始时i的值为0，那么我们希望两个线程执行完之后i的值变为2。但是事实会是这样吗？</p>

<p>可能存在下面一种情况：初始时，两个线程分别读取i的值存入各自所在的CPU的高速缓存当中，然后线程1进行加1操作，然后把i的最新值1写入到内存。此时线程2的高速缓存当中i的值还是0，</p>]]></description><link>http://www.apkfuns.com/java-volatile-summary/</link><guid isPermaLink="false">65ba9d67-486c-479c-ad52-83480a765a25</guid><category><![CDATA[java]]></category><category><![CDATA[volatile]]></category><dc:creator><![CDATA[舞影凌风]]></dc:creator><pubDate>Fri, 04 Jan 2019 12:26:07 GMT</pubDate><content:encoded><![CDATA[<h3 id="">内存模型的相关概念</h3>

<blockquote>
  <p>计算机在执行程序时，每条指令都是在CPU中执行的，而执行指令过程中，势必涉及到数据的读取和写入。由于程序运行过程中的临时数据是存放在主存（物理内存）当中的，这时就存在一个问题，由于CPU执行速度很快，而从内存读取数据和向内存写入数据的过程跟CPU执行指令的速度比起来要慢的多，因此如果任何时候对数据的操作都要通过和内存的交互来进行，会大大降低指令执行的速度。因此在CPU里面就有了高速缓存。也就是，当程序在运行过程中，会将运算需要的数据从主存复制一份到CPU的高速缓存当中，那么CPU进行计算时就可以直接从它的高速缓存读取数据和向其中写入数据，当运算结束之后，再将高速缓存中的数据刷新到主存当中。</p>
</blockquote>

<p>举个简单的例子，比如下面的这段代码：<code>i = i + 1;</code>当线程执行这个语句时，会先从主存当中读取i的值，然后复制一份到高速缓存当中，然后CPU执行指令对i进行加1操作，然后将数据写入高速缓存，最后将高速缓存中i最新的值刷新到主存当中。</p>

<p>这个代码在单线程中运行是没有任何问题的，但是在多线程中运行就会有问题了。在多核CPU中，每条线程可能运行于不同的CPU中，因此每个线程运行时有自己的高速缓存（对单核CPU来说，其实也会出现这种问题，只不过是以线程调度的形式来分别执行的）。本文我们以多核CPU为例。比如同时有2个线程执行这段代码，假如初始时i的值为0，那么我们希望两个线程执行完之后i的值变为2。但是事实会是这样吗？</p>

<p>可能存在下面一种情况：初始时，两个线程分别读取i的值存入各自所在的CPU的高速缓存当中，然后线程1进行加1操作，然后把i的最新值1写入到内存。此时线程2的高速缓存当中i的值还是0，进行加1操作之后，i的值为1，然后线程2把i的值写入内存。最终结果i的值是1，而不是2。这就是著名的缓存一致性问题。通常称这种被多个线程访问的变量为共享变量。也就是说，如果一个变量在多个CPU中都存在缓存（一般在多线程编程时才会出现），那么就可能存在缓存不一致的问题。</p>

<p>为了解决缓存不一致性问题，通常来说有以下2种解决方法：</p>

<ul>
<li>通过在总线加LOCK#锁的方式</li>
<li>通过缓存一致性协议</li>
</ul>

<p>这2种方式都是硬件层面上提供的方式。</p>

<blockquote>
  <p>在早期的CPU当中，是通过在总线上加LOCK#锁的形式来解决缓存不一致的问题。因为CPU和其他部件进行通信都是通过总线来进行的，如果对总线加LOCK#锁的话，也就是说阻塞了其他CPU对其他部件访问（如内存），从而使得只能有一个CPU能使用这个变量的内存。比如上面例子中 如果一个线程在执行 i = i +1，如果在执行这段代码的过程中，在总线上发出了LCOK#锁的信号，那么只有等待这段代码完全执行完毕之后，其他CPU才能从变量i所在的内存读取变量，然后进行相应的操作。这样就解决了缓存不一致的问题。但是上面的方式会有一个问题，由于在锁住总线期间，其他CPU无法访问内存，导致效率低下。</p>
  
  <p>所以就出现了缓存一致性协议。最出名的就是Intel 的MESI协议，MESI协议保证了每个缓存中使用的共享变量的副本是一致的。它核心的思想是：当CPU写数据时，如果发现操作的变量是共享变量，即在其他CPU中也存在该变量的副本，会发出信号通知其他CPU将该变量的缓存行置为无效状态，因此当其他CPU需要读取这个变量时，发现自己缓存中缓存该变量的缓存行是无效的，那么它就会从内存重新读取。
  <img src="https://images0.cnblogs.com/blog/288799/201408/212219343783699.jpg" alt=""></p>
</blockquote>

<h3 id="">并发编程中的三个概念</h3>

<h4 id="1">1.原子性</h4>

<p><strong>原子性：即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断，要么就都不执行。</strong></p>

<blockquote>
  <p>一个很经典的例子就是银行账户转账问题：比如从账户A向账户B转1000元，那么必然包括2个操作：从账户A减去1000元，往账户B加上1000元。
  试想一下，如果这2个操作不具备原子性，会造成什么样的后果。假如从账户A减去1000元之后，操作突然中止。然后又从B取出了500元，取出500元之后，再执行 往账户B加上1000元 的操作。这样就会导致账户A虽然减去了1000元，但是账户B没有收到这个转过来的1000元。</p>
  
  <p>所以这2个操作必须要具备原子性才能保证不出现一些意外的问题。同样地反映到并发编程中会出现什么结果呢？举个最简单的例子，大家想一下假如为一个32位的变量赋值过程不具备原子性的话，会发生什么后果？<code>i = 9;</code>假若一个线程执行到这个语句时，我暂且假设为一个32位的变量赋值包括两个过程：为低16位赋值，为高16位赋值。
  那么就可能发生一种情况：当将低16位数值写入之后，突然被中断，而此时又有一个线程去读取i的值，那么读取到的就是错误的数据。</p>
</blockquote>

<h4 id="2">2.可见性</h4>

<p><strong>可见性是指当多个线程访问同一个变量时，一个线程修改了这个变量的值，其他线程能够立即看得到修改的值。</strong></p>

<p>举个简单的例子，看下面这段代码：</p>

<pre><code>//线程1执行的代码
int i = 0;  
i = 10;

//线程2执行的代码
j = i;  
</code></pre>

<p>假若执行线程1的是CPU1，执行线程2的是CPU2。由上面的分析可知，当线程1执行 i =10这句时，会先把i的初始值加载到CPU1的高速缓存中，然后赋值为10，那么在CPU1的高速缓存当中i的值变为10了，却没有立即写入到主存当中。</p>

<p>此时线程2执行 j = i，它会先去主存读取i的值并加载到CPU2的缓存当中，注意此时内存当中i的值还是0，那么就会使得j的值为0，而不是10.</p>

<p><strong>这就是可见性问题，线程1对变量i修改了之后，线程2没有立即看到线程1修改的值。</strong></p>

<h4 id="3">3.有序性</h4>

<p><strong>有序性：即程序执行的顺序按照代码的先后顺序执行。</strong></p>

<pre><code>int i = 0;  
boolean flag = false;  
i = 1;                //语句1  
flag = true;          //语句2  
</code></pre>

<p>上面代码定义了一个int型变量，定义了一个boolean类型变量，然后分别对两个变量进行赋值操作。从代码顺序上看，语句1是在语句2前面的，那么JVM在真正执行这段代码的时候会保证语句1一定会在语句2前面执行吗？不一定，为什么呢？这里可能会发生指令重排序（Instruction Reorder）。</p>

<p>下面解释一下什么是指令重排序，一般来说，处理器为了提高程序运行效率，可能会对输入代码进行优化，它不保证程序中各个语句的执行先后顺序同代码中的顺序一致，但是它会保证程序最终执行结果和代码顺序执行的结果是一致的。</p>

<p>比如上面的代码中，语句1和语句2谁先执行对最终的程序结果并没有影响，那么就有可能在执行过程中，语句2先执行而语句1后执行。</p>

<p>但是要注意，虽然处理器会对指令进行重排序，但是它会保证程序最终结果会和代码顺序执行结果相同，那么它靠什么保证的呢？再看下面一个例子：</p>

<pre><code>int a = 10;    //语句1  
int r = 2;    //语句2  
a = a + 3;    //语句3  
r = a*a;     //语句4  
</code></pre>

<p>这段代码有4个语句，那么可能的一个执行顺序是：
<img src="https://images0.cnblogs.com/blog/288799/201408/212305263939989.jpg" alt=""></p>

<p>那么可不可能是这个执行顺序呢： 语句2 -> 语句1 -> 语句4 -> 语句3</p>

<p>不可能，因为处理器在进行重排序时是会考虑指令之间的数据依赖性，如果一个指令Instruction 2必须用到Instruction 1的结果，那么处理器会保证Instruction 1会在Instruction 2之前执行。</p>

<p>虽然重排序不会影响单个线程内程序执行的结果，但是多线程呢？下面看一个例子：</p>

<pre><code>//线程1:
context = loadContext();   //语句1  
inited = true;             //语句2

//线程2:
while(!inited ){  
  sleep()
}
doSomethingwithconfig(context);  
</code></pre>

<p>上面代码中，由于语句1和语句2没有数据依赖性，因此可能会被重排序。假如发生了重排序，在线程1执行过程中先执行语句2，而此是线程2会以为初始化工作已经完成，那么就会跳出while循环，去执行doSomethingwithconfig(context)方法，而此时context并没有被初始化，就会导致程序出错。</p>

<p>从上面可以看出，指令重排序不会影响单个线程的执行，但是会影响到线程并发执行的正确性。</p>

<p>也就是说，要想并发程序正确地执行，必须要保证原子性、可见性以及有序性。只要有一个没有被保证，就有可能会导致程序运行不正确。</p>

<h3 id="volatile">深入剖析volatile关键字</h3>

<p>一旦一个共享变量（类的成员变量、类的静态成员变量）被volatile修饰之后，那么就具备了两层语义：</p>

<ul>
<li>保证了不同线程对这个变量进行操作时的可见性，即一个线程修改了某个变量的值，这新值对其他线程来说是立即可见的。</li>
<li>禁止进行指令重排序。</li>
</ul>

<p><strong>注意：volatile并不能保证原子性</strong></p>

<pre><code>public class Test {  
    public volatile int inc = 0;

    public void increase() {
        inc++;
    }

    public static void main(String[] args) {
        final Test test = new Test();
        for(int i=0;i&lt;10;i++){
            new Thread(){
                public void run() {
                    for(int j=0;j&lt;1000;j++)
                        test.increase();
                };
            }.start();
        }

        while(Thread.activeCount()&gt;1)  //保证前面的线程都执行完
            Thread.yield();
        System.out.println(test.inc);
    }
}
</code></pre>

<blockquote>
  <p>大家想一下这段程序的输出结果是多少？也许有些朋友认为是10000。但是事实上运行它会发现每次运行结果都不一致，都是一个小于10000的数字。</p>
  
  <p>可能有的朋友就会有疑问，不对啊，上面是对变量inc进行自增操作，由于volatile保证了可见性，那么在每个线程中对inc自增完之后，在其他线程中都能看到修改后的值啊，所以有10个线程分别进行了1000次操作，那么最终inc的值应该是1000*10=10000。</p>
  
  <p>这里面就有一个误区了，volatile关键字能保证可见性没有错，但是上面的程序错在没能保证原子性。可见性只能保证每次读取的是最新的值，但是volatile没办法保证对变量的操作的原子性。</p>
  
  <p>在前面已经提到过，自增操作是不具备原子性的，它包括读取变量的原始值、进行加1操作、写入工作内存。那么就是说自增操作的三个子操作可能会分割开执行，就有可能导致下面这种情况出现：</p>
  
  <p>假如某个时刻变量inc的值为10，</p>
  
  <p>线程1对变量进行自增操作，线程1先读取了变量inc的原始值，然后线程1被阻塞了；</p>
  
  <p>然后线程2对变量进行自增操作，线程2也去读取变量inc的原始值，由于线程1只是对变量inc进行读取操作，而没有对变量进行修改操作，所以不会导致线程2的工作内存中缓存变量inc的缓存行无效，所以线程2会直接去主存读取inc的值，发现inc的值时10，然后进行加1操作，并把11写入工作内存，最后写入主存。</p>
  
  <p>然后线程1接着进行加1操作，由于已经读取了inc的值，注意此时在线程1的工作内存中inc的值仍然为10，所以线程1对inc进行加1操作后inc的值为11，然后将11写入工作内存，最后写入主存。</p>
  
  <p>那么两个线程分别进行了一次自增操作后，inc只增加了1。</p>
  
  <p>解释到这里，可能有朋友会有疑问，不对啊，前面不是保证一个变量在修改volatile变量时，会让缓存行无效吗？然后其他线程去读就会读到新的值，对，这个没错。这个就是上面的happens-before规则中的volatile变量规则，但是要注意，线程1对变量进行读取操作之后，被阻塞了的话，并没有对inc值进行修改。然后虽然volatile能保证线程2对变量inc的值读取是从内存中读取的，但是线程1没有进行修改，所以线程2根本就不会看到修改的值。</p>
  
  <p>根源就在这里，自增操作不是原子性操作，而且volatile也无法保证对变量的任何操作都是原子性的。</p>
</blockquote>

<h4 id="doublecheckvolatile">为什么double check单例需要加volatile?</h4>

<p>在多线程环境中，volatile能保证共享变量的可见性以及一定程度的有序性。下面是一个double check的单例写法：</p>

<pre><code>public class Singleton{  
    private static volatile Singleton instance = null;

    private Singleton(){}

    public Singleton getInstance(){
        if(null == instance){
            synchronized(Singleton.class){
                if(null == instance){
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}
</code></pre>

<p>因为volatile的可见性？比如线程1和线程2同时进入外层check，如果没有volatile关键字，因为线程的工作内存(register和CPU的L1、L2级缓存)的关系，导致不同的线程缓存了instance的副本，线程1对线程2的修改不可见？是这样的吗？答案：不是。<strong>因为instance是static变量，存在于方法区，对于线程而言是共享的，即线程不会缓存static变量</strong></p>

<p><strong>因为volatile的禁止指令重排序，在第10步中，初始化instance对象并非原子操作，它包括：1.开辟堆内存 2.调用构造方法初始化对象；如果没有volatile关键字，且在高并发情况下，如果某个线程开辟了堆内存后还未完成构造时，此时另一个线程进入外层判空后后发现instance对象非空，就返回了未构造完全的instance对象；volatile的意义在于能够禁止对当前对象进行指令的重排序，也就是happen-before原则的关于volatile的一条："volatile变量规则：对一个变量的写操作先行发生于后面对这个变量的读操作"，也就是说，无论什么情况，对于volatile变量的写操作必须在完成后才能读取，所以不会出现未完成构造就读取的情况；但是volatile不能保证同时对变量的写操作也是有序的，也就是volatile不能保证原子性</strong></p>

<h3 id="volatile">volatile的原理和实现机制</h3>

<p>下面这段话摘自《深入理解Java虚拟机》：</p>

<p>“观察加入volatile关键字和没有加入volatile关键字时所生成的汇编代码发现，加入volatile关键字时，会多出一个lock前缀指令”</p>

<p>lock前缀指令实际上相当于一个内存屏障（也成内存栅栏），内存屏障会提供3个功能：</p>

<ul>
<li>它确保指令重排序时不会把其后面的指令排到内存屏障之前的位置，也不会把前面的指令排到内存屏障的后面；即在执行到内存屏障这句指令时，在它前面的操作已经全部完成； </li>
<li>它会强制将对缓存的修改操作立即写入主存；</li>
<li>如果是写操作，它会导致其他CPU中对应的缓存行无效。</li>
</ul>

<h3 id="">参考文档</h3>

<ul>
<li><a href="https://www.cnblogs.com/dolphin0520/p/3920373.html">Java并发编程：volatile关键字解析</a></li>
<li><a href="https://www.iteye.com/topic/652440">单例模式与双重检测</a></li>
<li><a href="https://www.cnblogs.com/boboshenqi/p/9403627.html">单例模式的double check写法中的volatile关键字</a></li>
</ul>]]></content:encoded></item><item><title><![CDATA[Mac Intellij IDEA 卡顿解决方案]]></title><description><![CDATA[<h3 id="ideavmoptions">编辑 idea.vmoptions</h3>

<p>文件目录在 <code>应用程序 -&gt; IDEA显示包内容找到 -&gt; Contents -&gt; bin -&gt; idea.vmoptions</code></p>

<p>替换为如下内容:</p>

<pre><code>-Xms1024m
-Xmx2048m
-XX:ReservedCodeCacheSize=1024m
-XX:+UseCompressedOops
-Dfile.encoding=UTF-8
-XX:+UseConcMarkSweepGC
-XX:SoftRefLRUPolicyMSPerMB=50
-ea
-Dsun.io.useCanonCaches=false
-Djava.net.preferIPv4Stack=true
-XX:+HeapDumpOnOutOfMemoryError
-XX:-OmitStackTraceInFastThrow
-Xverify:none
-XX:</code></pre>]]></description><link>http://www.apkfuns.com/memory-optimization-of-intellij-idea/</link><guid isPermaLink="false">12fbe58b-676d-4fd9-a05c-4e87975551b3</guid><category><![CDATA[intellij IDEA]]></category><dc:creator><![CDATA[舞影凌风]]></dc:creator><pubDate>Mon, 19 Nov 2018 08:56:00 GMT</pubDate><content:encoded><![CDATA[<h3 id="ideavmoptions">编辑 idea.vmoptions</h3>

<p>文件目录在 <code>应用程序 -&gt; IDEA显示包内容找到 -&gt; Contents -&gt; bin -&gt; idea.vmoptions</code></p>

<p>替换为如下内容:</p>

<pre><code>-Xms1024m
-Xmx2048m
-XX:ReservedCodeCacheSize=1024m
-XX:+UseCompressedOops
-Dfile.encoding=UTF-8
-XX:+UseConcMarkSweepGC
-XX:SoftRefLRUPolicyMSPerMB=50
-ea
-Dsun.io.useCanonCaches=false
-Djava.net.preferIPv4Stack=true
-XX:+HeapDumpOnOutOfMemoryError
-XX:-OmitStackTraceInFastThrow
-Xverify:none
-XX:ErrorFile=$USER_HOME/java_error_in_idea_%p.log
-XX:HeapDumpPath=$USER_HOME/java_error_in_idea.hprof
</code></pre>

<h3 id="">开启内存提醒</h3>

<p><img src="http://qiniu.apkfuns.com/image/8/a9/31c6db8376a06db0e4a2f37adc3cd.png" alt=""></p>

<h3 id="">参考文档</h3>

<ul>
<li><a href="https://blog.csdn.net/it_0417/article/details/82759103">解决Mac环境下IntelliJ IDEA写代码卡顿情况，修改IntelliJ IDEA内存大小</a></li>
</ul>]]></content:encoded></item><item><title><![CDATA[android VectorDrawable 转png图片]]></title><description><![CDATA[<p>Android上使用svg图片可以利用<code>VectorDrawable</code>,不仅体积小而且是矢量图，目前已经成为了开发app的不二之选。下面是一个<code>VectorDrawable</code>例子:</p>

<pre><code>&lt;?xml version="1.0" encoding="utf-8"?&gt;  
&lt;vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="26dp" android:width="26dp" android:viewportWidth="26" android:viewportHeight="26"&gt;  
    &lt;path android:fillColor="#ee4f4f" android:pathData="M15.746,21.714C15.</code></pre>]]></description><link>http://www.apkfuns.com/android-vectordrawable-to-png-image/</link><guid isPermaLink="false">608d00a5-f403-4ff0-942e-56092f7711c9</guid><dc:creator><![CDATA[舞影凌风]]></dc:creator><pubDate>Mon, 12 Nov 2018 06:20:00 GMT</pubDate><content:encoded><![CDATA[<p>Android上使用svg图片可以利用<code>VectorDrawable</code>,不仅体积小而且是矢量图，目前已经成为了开发app的不二之选。下面是一个<code>VectorDrawable</code>例子:</p>

<pre><code>&lt;?xml version="1.0" encoding="utf-8"?&gt;  
&lt;vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="26dp" android:width="26dp" android:viewportWidth="26" android:viewportHeight="26"&gt;  
    &lt;path android:fillColor="#ee4f4f" android:pathData="M15.746,21.714C15.036,22.541 14.017,23.015 12.95,23.015C11.858,23.015 10.856,22.461 9.86,21.292L5.694,16.932L20.307,16.932L15.746,21.714ZM22.498,10.817C22.498,5.404 18.237,1 12.998,1C7.761,1 3.5,5.404 3.5,10.817L3.5,16.205C3.5,16.266 3.52,16.32 3.534,16.377C3.519,16.58 3.578,16.786 3.727,16.943L8.83,22.283C10.065,23.733 11.451,24.469 12.95,24.469C14.419,24.469 15.822,23.817 16.773,22.708L22.213,17.005C22.336,16.877 22.395,16.711 22.406,16.544C22.459,16.441 22.498,16.329 22.498,16.205L22.498,10.817Z" android:strokeColor="#00000000" android:strokeWidth="1"/&gt;
&lt;/vector&gt;  
</code></pre>

<p>我们这篇文章主角是小程序，在小程序上怎么使用svg呢？只能通过svg转成base64，然后显示出来。还有另一种就是我们这篇要介绍的，直接把svg转png图片。</p>

<p><code>VectorDrawable</code>是Android专用的格式，严格意义上说并不是svg，不过总体上是一致的，可以通过替换标签来转换。</p>

<pre><code>#VectorDrawable2Svg.py

"""
VectorDrawable2Svg  
This script convert your VectorDrawable to a Svg  
Author: Alessandro Lucchet

Usage: drop one or more vector drawable onto this script to convert them to svg format  
"""

from xml.dom.minidom import *  
import sys

# extracts all paths inside vdContainer and add them into svgContainer
def convertPaths(vdContainer,svgContainer,svgXml):  
    vdPaths = vdContainer.getElementsByTagName('path')
    for vdPath in vdPaths:
        # only iterate in the first level
        if vdPath.parentNode == vdContainer:
            svgPath = svgXml.createElement('path')
            svgPath.attributes['d'] = vdPath.attributes['android:pathData'].value
            if vdPath.hasAttribute('android:fillColor'):
                svgPath.attributes['fill'] = vdPath.attributes['android:fillColor'].value
            else:
                svgPath.attributes['fill'] = 'none'
            if vdPath.hasAttribute('android:strokeLineJoin'):
                svgPath.attributes['stroke-linejoin'] = vdPath.attributes['android:strokeLineJoin'].value
            if vdPath.hasAttribute('android:strokeLineCap'):
                svgPath.attributes['stroke-linecap'] = vdPath.attributes['android:strokeLineCap'].value
            if vdPath.hasAttribute('android:strokeMiterLimit'):
                svgPath.attributes['stroke-miterlimit'] = vdPath.attributes['android:strokeMiterLimit'].value
            if vdPath.hasAttribute('android:strokeWidth'):
                svgPath.attributes['stroke-width'] = vdPath.attributes['android:strokeWidth'].value
            if vdPath.hasAttribute('android:strokeColor'):
                svgPath.attributes['stroke'] = vdPath.attributes['android:strokeColor'].value
            svgContainer.appendChild(svgPath);

# define the function which converts a vector drawable to a svg
def convertVd(vdFilePath):

    # create svg xml
    svgXml = Document()
    svgNode = svgXml.createElement('svg')
    svgXml.appendChild(svgNode);

    # open vector drawable
    vdXml = parse(vdFilePath)
    vdNode = vdXml.getElementsByTagName('vector')[0]

    # setup basic svg info
    svgNode.attributes['xmlns'] = 'http://www.w3.org/2000/svg'
    svgNode.attributes['width'] = vdNode.attributes['android:viewportWidth'].value
    svgNode.attributes['height'] = vdNode.attributes['android:viewportHeight'].value
    svgNode.attributes['viewBox'] = '0 0 {} {}'.format(vdNode.attributes['android:viewportWidth'].value, vdNode.attributes['android:viewportHeight'].value)

    # iterate through all groups
    vdGroups = vdXml.getElementsByTagName('group')
    for vdGroup in vdGroups:

        # create the group
        svgGroup = svgXml.createElement('g')

        # setup attributes of the group
        if vdGroup.hasAttribute('android:translateX'):
            svgGroup.attributes['transform'] = 'translate({},{})'.format(vdGroup.attributes['android:translateX'].value,vdGroup.attributes['android:translateY'].value)

        # iterate through all paths inside the group
        convertPaths(vdGroup,svgGroup,svgXml)

        # append the group to the svg node
        svgNode.appendChild(svgGroup);

    # iterate through all svg-level paths
    convertPaths(vdNode,svgNode,svgXml)

    # write xml to file
    svgXml.writexml(open(vdFilePath + '.svg', 'w'),indent="",addindent="  ",newl='\n')

# script begin
if len(sys.argv)&gt;1:  
    iterArgs = iter(sys.argv)
    next(iterArgs) #skip the first entry (it's the name of the script)
    for arg in iterArgs:
        convertVd(arg)
else:  
    print("You have to pass me something")
    sys.exit()
</code></pre>

<p>这是从stackOverFlow下载的脚本，原作者已经找不到，在此致谢。使用方式的话在代码里有说明。<code>python VectorDrawable2Svg.py &lt;需要转换的文件&gt;</code> 下面是转换后的svg代码:</p>

<pre><code>&lt;?xml version="1.0" ?&gt;  
&lt;svg height="26" viewBox="0 0 26 26" width="26" xmlns="http://www.w3.org/2000/svg"&gt;  
  &lt;path d="M15.746,21.714C15.036,22.541 14.017,23.015 12.95,23.015C11.858,23.015 10.856,22.461 9.86,21.292L5.694,16.932L20.307,16.932L15.746,21.714ZM22.498,10.817C22.498,5.404 18.237,1 12.998,1C7.761,1 3.5,5.404 3.5,10.817L3.5,16.205C3.5,16.266 3.52,16.32 3.534,16.377C3.519,16.58 3.578,16.786 3.727,16.943L8.83,22.283C10.065,23.733 11.451,24.469 12.95,24.469C14.419,24.469 15.822,23.817 16.773,22.708L22.213,17.005C22.336,16.877 22.395,16.711 22.406,16.544C22.459,16.441 22.498,16.329 22.498,16.205L22.498,10.817Z" fill="#ee4f4f" stroke="#00000000" stroke-width="1"/&gt;
&lt;/svg&gt;
</code></pre>

<p>通过这个脚本，我们就可以把<code>VectorDrawable</code>转换成SVG了，在mac上也可以预览了。怎么转成图片呢？我们借助一些第三方工具。网上找了很多工具，觉得最好用的还是<code>Boxy SVG</code>。 </p>

<p>软件的用法就不介绍了，都是界面化操作，基本没啥难度。到此就把<code>VectorDrawable</code>转成png了</p>]]></content:encoded></item><item><title><![CDATA[centos关闭root登录]]></title><description><![CDATA[<p>每次用ssh登录服务器都有如下的提示: </p>

<blockquote>
  <p>There were xx failed login attempts since the last successful login.</p>
</blockquote>

<p>有人在用错误的密码在尝试登录服务器，想想就有点方。网上的解决方案是用<code>ssh密钥</code>登录，关闭root的方式登录。下面记录下操作方式</p>

<h3 id="">配置密钥登录</h3>

<p>腾讯云管理后台可以选择加载密钥，不过得先关机。创建密钥后就会下载得到一个密钥文件(后续需要这个登录)</p>

<p><strong>创建新用户</strong></p>

<pre><code>adduser user1  
passwd user1  
</code></pre>

<p><strong>配置服务器关闭ROOT登录</strong></p>

<pre><code>vim /etc/ssh/sshd_config

查找“#PermitRootLogin yes”，将前面的“#”去掉，短尾“yes”改为“no”，并保存文件。
# PermitRootLogin no，千万不要修改成No，linux是严格区分大小写的

# 重启服务</code></pre>]]></description><link>http://www.apkfuns.com/centosguan-bi-rootdeng-lu/</link><guid isPermaLink="false">7d3acbef-ec67-43b0-8a80-e2e979ad957b</guid><category><![CDATA[centos]]></category><dc:creator><![CDATA[舞影凌风]]></dc:creator><pubDate>Tue, 30 Oct 2018 15:40:16 GMT</pubDate><content:encoded><![CDATA[<p>每次用ssh登录服务器都有如下的提示: </p>

<blockquote>
  <p>There were xx failed login attempts since the last successful login.</p>
</blockquote>

<p>有人在用错误的密码在尝试登录服务器，想想就有点方。网上的解决方案是用<code>ssh密钥</code>登录，关闭root的方式登录。下面记录下操作方式</p>

<h3 id="">配置密钥登录</h3>

<p>腾讯云管理后台可以选择加载密钥，不过得先关机。创建密钥后就会下载得到一个密钥文件(后续需要这个登录)</p>

<p><strong>创建新用户</strong></p>

<pre><code>adduser user1  
passwd user1  
</code></pre>

<p><strong>配置服务器关闭ROOT登录</strong></p>

<pre><code>vim /etc/ssh/sshd_config

查找“#PermitRootLogin yes”，将前面的“#”去掉，短尾“yes”改为“no”，并保存文件。
# PermitRootLogin no，千万不要修改成No，linux是严格区分大小写的

# 重启服务
service sshd restart  
</code></pre>

<h3 id="securecrt">secureCRT 登录</h3>

<p><img src="http://qiniu.apkfuns.com/image/6/26/27ae8da60ef0820b2b042e02ee67f.png" alt=""></p>

<p>操作完成后再尝试ssh登录就提示 <code>Permission denied</code></p>

<blockquote>
  <p>ssh root@118.24.5.247
  root@118.24.5.247: Permission denied (publickey,gssapi-keyex,gssapi-with-mic).</p>
</blockquote>

<h3 id="">参考文档</h3>

<ul>
<li><a href="https://blog.csdn.net/zhangzhikaixinya/article/details/44113809">Centos 禁止root帐号直接登录</a></li>
</ul>]]></content:encoded></item><item><title><![CDATA[小程序语音MP3转WAV]]></title><description><![CDATA[<h3 id="">前言</h3>

<p>小程序录制的语音格式是<code>aar和mp3</code>, 如果想语音文本识别，像百度语音等SDK要求的都是无损原声<code>pcm</code>格式(wav仅仅是pcm的封装)，下面提供两种将mp3转换为wav的方法</p>

<h3 id="0x1">0x1通过第三方接口</h3>

<pre><code>&lt;?php  
$url = 'http://server.com/sound.mp3';
$data = json_decode(file_get_contents('http://api.rest7.com/v1/sound_convert.php?url=' . $url . '&amp;format=wav'));

if (@$data-&gt;success !== 1)  
{
    die('Failed');
}
$wave = file_get_contents(</code></pre>]]></description><link>http://www.apkfuns.com/mini-program-voice-mp3-to-wav/</link><guid isPermaLink="false">93a7f8d8-15bb-4b23-a4ea-cb4e4cb2f466</guid><category><![CDATA[小程序]]></category><category><![CDATA[wav]]></category><category><![CDATA[语音]]></category><dc:creator><![CDATA[舞影凌风]]></dc:creator><pubDate>Mon, 10 Sep 2018 12:20:29 GMT</pubDate><content:encoded><![CDATA[<h3 id="">前言</h3>

<p>小程序录制的语音格式是<code>aar和mp3</code>, 如果想语音文本识别，像百度语音等SDK要求的都是无损原声<code>pcm</code>格式(wav仅仅是pcm的封装)，下面提供两种将mp3转换为wav的方法</p>

<h3 id="0x1">0x1通过第三方接口</h3>

<pre><code>&lt;?php  
$url = 'http://server.com/sound.mp3';
$data = json_decode(file_get_contents('http://api.rest7.com/v1/sound_convert.php?url=' . $url . '&amp;format=wav'));

if (@$data-&gt;success !== 1)  
{
    die('Failed');
}
$wave = file_get_contents($data-&gt;file);
file_put_contents('sound.wav', $wave);  
</code></pre>

<h3 id="0x2mpg123">0x2通过mpg123</h3>

<p>服务器上安装mpg123</p>

<pre><code>apt-get install mpg123  
// or
yum install mpg123  
</code></pre>

<p>执行shell转化</p>

<pre><code>mpg123 -w output.wav input.mp3  
</code></pre>

<p>拿到wav格式就可以愉快的语音识别啦~</p>

<p><img src="https://ws3.sinaimg.cn/large/9150e4e5ly1fre0bhn0raj20ci08bq3d.jpg" alt=""></p>

<p>百度的语音识别不限制调用次数，文档请看这里: <a href="http://ai.baidu.com/docs/#/ASR-Online-PHP-SDK/top">百度语音sdk文档</a></p>

<h3 id="">参考文档</h3>

<ul>
<li><a href="https://blog.csdn.net/vasal/article/details/7637546">linux convert mp3 to wav</a></li>
<li><a href="https://stackoverflow.com/questions/11169681/mp3-to-wav-on-linux-via-php">mp3 to wav on linux via PHP</a></li>
<li><a href="https://github.com/rest7/api/wiki/Convert-MP3-to-WAV-in-PHP">Convert MP3 to WAV in PHP</a></li>
</ul>]]></content:encoded></item><item><title><![CDATA[开源小程序: 唯美小姐姐]]></title><description><![CDATA[<h3 id="0x1">0x1小程序介绍</h3>

<p>这是一款看漂亮小姐姐的小程序，开源地址是: <a href="https://github.com/pengwei1024/HiBeauty">https://github.com/pengwei1024/HiBeauty</a>。话不多说，先上个截图各位小主看看合不合胃口。</p>

<p><image width="400px" src="http://tools.apkfuns.com/images/beauty/screenshot01.png"></image></p>

<p><strong>怎么抱走小姐姐呢?</strong><br>
<img src="https://ws2.sinaimg.cn/bmiddle/9150e4e5ly1fjh96sfwnoj208w08wmxg.jpg" alt=""></p>

<p>可以微信小程序搜索 <strong>唯美小姐姐</strong> 或者扫码抱走吧<br>
<image width="250px" src="http://tools.apkfuns.com/images/beauty/qrcode.jpg"></image></p>

<h3 id="0x2">0x2原理分析</h3>

<p>各位小主看完了小姐姐，请把口水擦干，我们来看看怎么<code>new</code>一个小姐姐吧。我觉得技术点主要下面两个(当然第一个才是大家最关心的)：</p>

<ul>
<li>小姐姐数据的抓取和处理</li>
<li>小程序从服务器拉取并展示数据</li>
</ul>

<h4 id="">数据抓取</h4>

<p>小姐姐的数据主要来自<strong>微博</strong>美女帅哥大V, 如<code>x粉大魔王</code>等等，在此向各位辛勤付出的大V致谢。抓取用的方案是<code>Headless Chrome + selenium</code>,简而言之就是用的服务器上web自动化方案。<br></p>

<p>为什么采用这个方案呢，直接抓取网页的方式帐号容易被封，自动登录获取cookie的技术难度都比较大。而web自动化就是模拟网页点击，一切的操作都是so easy。</p>

<p><img src="https://ws4.sinaimg.cn/bmiddle/6af89bc8gw1f8picmhdoag20ak07tkg7.gif" alt="" title=""> <br></p>

<p>不了解 Headless Chrome 和 selenium？请看下面的教程:</p>

<ul>
<li><a href="http://apkfuns.com/centos-install-headless-chrome/">安装Headless</a></li></ul>]]></description><link>http://www.apkfuns.com/open-source-applet-beautiful-little-sister/</link><guid isPermaLink="false">6020f6e2-144c-4508-b786-77acb9417ea7</guid><category><![CDATA[小程序]]></category><category><![CDATA[小姐姐]]></category><dc:creator><![CDATA[舞影凌风]]></dc:creator><pubDate>Mon, 03 Sep 2018 03:12:16 GMT</pubDate><content:encoded><![CDATA[<h3 id="0x1">0x1小程序介绍</h3>

<p>这是一款看漂亮小姐姐的小程序，开源地址是: <a href="https://github.com/pengwei1024/HiBeauty">https://github.com/pengwei1024/HiBeauty</a>。话不多说，先上个截图各位小主看看合不合胃口。</p>

<p><image width="400px" src="http://tools.apkfuns.com/images/beauty/screenshot01.png"></image></p>

<p><strong>怎么抱走小姐姐呢?</strong><br>
<img src="https://ws2.sinaimg.cn/bmiddle/9150e4e5ly1fjh96sfwnoj208w08wmxg.jpg" alt=""></p>

<p>可以微信小程序搜索 <strong>唯美小姐姐</strong> 或者扫码抱走吧<br>
<image width="250px" src="http://tools.apkfuns.com/images/beauty/qrcode.jpg"></image></p>

<h3 id="0x2">0x2原理分析</h3>

<p>各位小主看完了小姐姐，请把口水擦干，我们来看看怎么<code>new</code>一个小姐姐吧。我觉得技术点主要下面两个(当然第一个才是大家最关心的)：</p>

<ul>
<li>小姐姐数据的抓取和处理</li>
<li>小程序从服务器拉取并展示数据</li>
</ul>

<h4 id="">数据抓取</h4>

<p>小姐姐的数据主要来自<strong>微博</strong>美女帅哥大V, 如<code>x粉大魔王</code>等等，在此向各位辛勤付出的大V致谢。抓取用的方案是<code>Headless Chrome + selenium</code>,简而言之就是用的服务器上web自动化方案。<br></p>

<p>为什么采用这个方案呢，直接抓取网页的方式帐号容易被封，自动登录获取cookie的技术难度都比较大。而web自动化就是模拟网页点击，一切的操作都是so easy。</p>

<p><img src="https://ws4.sinaimg.cn/bmiddle/6af89bc8gw1f8picmhdoag20ak07tkg7.gif" alt="" title=""> <br></p>

<p>不了解 Headless Chrome 和 selenium？请看下面的教程:</p>

<ul>
<li><a href="http://apkfuns.com/centos-install-headless-chrome/">安装Headless Chrome</a></li>
<li><a href="https://www.yiibai.com/selenium/selenium_webdriver.html">selenium教程</a></li>
</ul>

<p>linux上配置一个定时任务，实现全自动化的抓取，再来个图片分类器把非小姐姐图片去除，自动更新，开开心心欣赏小姐姐真是美滋滋</p>

<p><img src="https://ws2.sinaimg.cn/bmiddle/9150e4e5ly1fka2chgitoj204m04ft8j.jpg" alt=""></p>

<p>用selenium抓取的代码不会超过50行，这里就不展开了，各位小主自己尝试下吧~</p>

<h4 id="">图片展示</h4>

<p>小程序展示列表实在不是什么技术难点，如果说有什么需要注意的话，那就是图片可以持续下拉加载更多，数据量过大导致OOM崩溃的问题, 主要分两步解决吧。</p>

<ul>
<li>图片不直接加载原图 (微博提供了缩略图和原图两套)</li>
<li>增量刷新列表</li>
</ul>

<p>重点来看下小程序增量刷新列表吧。(会的大哥大姐可以忽略，毕竟我是一个小菜鸟) 更新的时候加上下标就好了。</p>

<pre><code>this.setData({  
    'array[0].text':'changed data'
})
</code></pre>

<p>用了某网友分享的一个类<code>SafeRenderUtil</code>, 自己新增了一个<code>reset</code>方法, 找不到原作者链接了，在此致谢</p>

<pre><code>/**
 * @module SafeRenderUtil
 */

/**
 * 安全渲染的工具类
 * 解决的问题：分页加载时，数组拼接起来在渲染，当数据超过 1M 后，无法再加载
 * @example
 * import SafeRenderUtil from '@xxx/lib/safeRenderUtil';
 * // 初始化
 * this.SafeRenderUtil = new SafeRenderUtil({
 *   arrName: 'arrName',
 *   formatItem: (item) =&gt; {
 *     //裁剪每一项的图片...
 *     return item;
 *   },
 *   setData: this.setData.bind(this)
 * });
 * // 将数组传递进来进行渲染
 * this.SafeRenderUtil.addList(res.data.data);
 */
class SafeRenderUtil {  
  /**
   * @param {String} opts.arrName 数组名称
   * @param {Function} opts.formatItem 处理数组的每一个 item ，并将该 item 返回
   * @param {Function} opts.setData 调用页面的渲染方法
   */
  constructor(opts) {
    this.arrName = opts.arrName;
    this.formatItem = opts.formatItem;
    this.setData = opts.setData;
    this.originLen = 0; //原始数组长度
  }
  /**
   * @param {Array} arr 需要渲染的数组
   */
  addList(arr) {
    if (arr &amp;&amp; arr.length) {
      let newList = {};
      for (let i = 0; i &lt; arr.length; i++) {
        let item = arr[i];
        if (typeof(this.formatItem) === 'function') {
          item = this.formatItem(item);
        }
        newList[`${this.arrName}[${this.originLen}]`] = item;
        this.originLen += 1;
      };
      this.setData(newList);
    }
  }
  /**
   * 清空数组数据
   */
  clearArr() {
    this.setData({
      [`${this.arrName}`]: []
    });
    this.originLen = 0;
  }

  reset(arr) {
    this.originLen = arr.length;
    if (arr &amp;&amp; arr.length) {
      for (let i = 0; i &lt; arr.length; i++) {
        if (typeof(this.formatItem) === 'function') {
          arr[i] = this.formatItem(arr[i]);
        }
      };
      this.setData({
        [`${this.arrName}`]: arr
      });
    }
  }
}
module.exports = SafeRenderUtil;  
</code></pre>

<h3 id="0x3">0x3 最后</h3>

<p>本人代码不精,封装无力,架构松散，欢迎大家提建议、issue、pr。</p>

<p><img src="https://ws4.sinaimg.cn/bmiddle/9150e4e5ly1flcvi82og0g20540547p7.gif" alt=""></p>

<p>微信技术交流群，欢迎交流</p>

<p><img src="https://user-gold-cdn.xitu.io/2018/9/3/1659daf253262446?imageView2/0/w/1280/h/960/format/webp/ignore-error/1" alt=""></p>]]></content:encoded></item><item><title><![CDATA[gradle编译完成后上传apk]]></title><description><![CDATA[<p>项目中有个需求想本地编译完成自动上传release 的apk到服务器，因为功能比较简单，不想借助第三方来实现，这是个我自定义gradle task来上传apk的例子。</p>

<h3 id="task">自定义task</h3>

<pre><code>task apkUpload(type: Exec, dependsOn: ['assembleRelease']) {  
    def version = project.android.defaultConfig.versionName
    def apkPath = "$buildDir/outputs/apk/release/app-release.apk"
    commandLine "curl", "-F", "apk=@$apkPath", "上传地址"
}
</code></pre>

<p>创建了一个<code>Exec</code>类型的task, 依赖<code>assembleRelease</code>任务, 最后执行shell <code>curl -f apk=@{文件路径} {上传路径}</code> 来完成上传</p>

<h3 id="phpcodeigniter">服务器示例 (PHP Codeigniter)</h3>

<pre><code>/**
     * 上传apk</code></pre>]]></description><link>http://www.apkfuns.com/upload-apk-after-gradle-is-compiled/</link><guid isPermaLink="false">50f675d9-c521-4288-bf34-cf0864257c88</guid><category><![CDATA[gradle]]></category><category><![CDATA[apk]]></category><dc:creator><![CDATA[舞影凌风]]></dc:creator><pubDate>Sat, 01 Sep 2018 14:06:40 GMT</pubDate><content:encoded><![CDATA[<p>项目中有个需求想本地编译完成自动上传release 的apk到服务器，因为功能比较简单，不想借助第三方来实现，这是个我自定义gradle task来上传apk的例子。</p>

<h3 id="task">自定义task</h3>

<pre><code>task apkUpload(type: Exec, dependsOn: ['assembleRelease']) {  
    def version = project.android.defaultConfig.versionName
    def apkPath = "$buildDir/outputs/apk/release/app-release.apk"
    commandLine "curl", "-F", "apk=@$apkPath", "上传地址"
}
</code></pre>

<p>创建了一个<code>Exec</code>类型的task, 依赖<code>assembleRelease</code>任务, 最后执行shell <code>curl -f apk=@{文件路径} {上传路径}</code> 来完成上传</p>

<h3 id="phpcodeigniter">服务器示例 (PHP Codeigniter)</h3>

<pre><code>/**
     * 上传apk
     * @param $version
     */
    public function upload($version)
    {
        $config['upload_path'] = FCPATH . 'apks/';
        $config['allowed_types'] = '*';
        $config['file_name'] = "beauty_$version.apk";
        $config['max_size'] = 1024 * 10;
        $config['overwrite'] = true;
        $this-&gt;load-&gt;library('upload', $config);
        $this-&gt;upload-&gt;initialize($config);
        if ($this-&gt;upload-&gt;do_upload('apk')) {
            file_put_contents(FCPATH . 'apks/version', $version);
            echo "upload success!!";
        } else {
            echo $this-&gt;upload-&gt;display_errors();
            echo "upload failure!!";
        }
    }
</code></pre>]]></content:encoded></item><item><title><![CDATA[centos安装headless chrome]]></title><description><![CDATA[<h3 id="centos7">Centos 7 安装</h3>

<p>直接运行安装脚本</p>

<pre><code>curl https://intoli.com/install-google-chrome.sh | bash  
</code></pre>

<p>
安装完成后会提示:</p>

<blockquote>
  <p>Successfully installed google-chrome-stable, Google Chrome 65.0.3325.146 .</p>
</blockquote>

<p><strong>运行chrome</strong><br></p>

<pre><code>google-chrome-stable --no-sandbox --headless --disable-gpu --screenshot     https://www.baidu.com  
</code></pre>

<p>正常的话会在当前目录生成<code>screenshot.png</code>的文件</p>

<p><strong>驱动</strong><br>
下载chrome驱动并放到某个目录下: <a href="http://npm.taobao.org/mirrors/chromedriver/2.36/chromedriver_linux64.zip">http://npm.taobao.org/mirrors/chromedriver/2.36/chromedriver_linux64.zip </a> , 运行<code>./chromedriver</code></p>]]></description><link>http://www.apkfuns.com/centos-install-headless-chrome/</link><guid isPermaLink="false">462317e6-ad63-4561-9b45-f31f0d0f5047</guid><category><![CDATA[centos]]></category><category><![CDATA[headless]]></category><category><![CDATA[chrome]]></category><category><![CDATA[firefox]]></category><dc:creator><![CDATA[舞影凌风]]></dc:creator><pubDate>Tue, 28 Aug 2018 04:52:59 GMT</pubDate><content:encoded><![CDATA[<h3 id="centos7">Centos 7 安装</h3>

<p>直接运行安装脚本</p>

<pre><code>curl https://intoli.com/install-google-chrome.sh | bash  
</code></pre>

<p>
安装完成后会提示:</p>

<blockquote>
  <p>Successfully installed google-chrome-stable, Google Chrome 65.0.3325.146 .</p>
</blockquote>

<p><strong>运行chrome</strong><br></p>

<pre><code>google-chrome-stable --no-sandbox --headless --disable-gpu --screenshot     https://www.baidu.com  
</code></pre>

<p>正常的话会在当前目录生成<code>screenshot.png</code>的文件</p>

<p><strong>驱动</strong><br>
下载chrome驱动并放到某个目录下: <a href="http://npm.taobao.org/mirrors/chromedriver/2.36/chromedriver_linux64.zip">http://npm.taobao.org/mirrors/chromedriver/2.36/chromedriver_linux64.zip </a> , 运行<code>./chromedriver</code>, 如果能返回下面内容说明驱动安装正常</p>

<blockquote>
  <p>Starting ChromeDriver 2.36.540471 (9c759b81a907e70363c6312294d30b6ccccc2752) on port 9515
  Only local connections are allowed.</p>
</blockquote>

<h4 id="selenium">selenium 使用</h4>

<pre><code>from selenium import webdriver

options = webdriver.ChromeOptions()  
options.binary_location = '/usr/bin/google-chrome-stable'  
options.add_argument('--headless')  
options.add_argument('--disable-gpu')  
options.add_argument('--no-sandbox')  
options.add_argument('window-size=1200x600')  
driver = webdriver.Chrome(chrome_options=options, executable_path='/opt/drivers/chromedriver')  
driver.get('https://facebook.com')  
print driver.title  
driver.quit()  
</code></pre>

<h3 id="centos6">centos 6 安装</h3>

<p>貌似是因为chrome 不支持centos 6，所以没有找到好的安装教程，那就试试firefox吧，整体上也不错</p>

<pre><code># 安装Xvfb和pyvirtualdisplay
yum install xorg-x11-server-Xvfb  
pip install pyvirtualdisplay

# 安装firefox和selenium
yum install firefox  
pip install selenium

# 注意selenium 3+ 只支持python3， 如果本地是python2 需要安装selenium 2.x
pip install selenium==2.44.0  
</code></pre>

<p>测试代码</p>

<pre><code>from pyvirtualdisplay import Display  
from selenium import webdriver

display = Display(visible=0, size=(800, 600))  
display.start()

browser = webdriver.Firefox()  
browser.get('http://www.google.com')  
print browser.title  
browser.quit()

display.stop()  
</code></pre>

<p>我按这个教程安装一直报错：</p>

<blockquote>
  <p>raise WebDriverException("The browser appears to have exited " selenium.common.exceptions.WebDriverException: Message: The browser appears to have exited before we could connect. If you specified a log_file in the FirefoxBinary constructor, check it for details. </p>
</blockquote>

<p>看教程说要手动启动 <code>Xvfb</code>, <a href="https://gist.github.com/UKNC/f51c8fdf3232e4aba39ce87b59cc9b7b">请参考这里</a></p>

<pre><code>Xvfb :19 -screen 0 1024x768x16 &amp;

DISPLAY=:19 firefox "http://www.yahoo.com"  
</code></pre>

<p>这样错误变成了 <code>/var/lib/dbus/machine-id找不到</code></p>

<pre><code>yum install dbus

dbus-uuidgen &gt; /var/lib/dbus/machine-id  
</code></pre>

<h3 id="">参考文档</h3>

<ul>
<li><a href="https://blog.csdn.net/zhuyiquan/article/details/79537623">CentOS 7.x环境下搭建: Headless chrome + Selenium + ChromeDriver 实现自动化测试</a></li>
<li><a href="https://blog.phpgao.com/headless_selenium.html">linux无界面(headless)使用selenium抓取数据</a></li>
<li><a href="https://gist.github.com/UKNC/f51c8fdf3232e4aba39ce87b59cc9b7b">Install headless Firefox on CentOS 6 for Selenium automation.md</a></li>
<li><a href="https://intoli.com/blog/running-selenium-with-headless-chrome/">RUNNING SELENIUM WITH HEADLESS CHROME</a></li>
<li><a href="https://www.programcreek.com/python/example/100025/selenium.webdriver.ChromeOptions">Python selenium.webdriver.ChromeOptions() Examples</a></li>
</ul>]]></content:encoded></item><item><title><![CDATA[PHP使用上一些问题笔记]]></title><description><![CDATA[<h3 id="json_encode">json_encode失败切没有任何输出</h3>

<pre><code># 使用json_last_error输出错误信息
var_dump(json_last_error());

# 发现错误码是5 JSON_ERROR_UTF8, 转成uft-8就好了
mb_convert_encoding($value, "UTF-8", "auto");  
</code></pre>

<h3 id="">参考文档</h3>

<ul>
<li><a href="https://blog.csdn.net/ityang521/article/details/55225290">php json_encode输出空白问题</a></li>
<li><a href="https://stackoverflow.com/questions/10199017/how-to-solve-json-error-utf8-error-in-php-json-decode">How to solve JSON<em>ERROR</em>UTF8 error in php json_decode?
</a></li>
</ul>]]></description><link>http://www.apkfuns.com/php-develop-note/</link><guid isPermaLink="false">9670dba0-ad13-4f25-b8a1-939951c56039</guid><category><![CDATA[php]]></category><dc:creator><![CDATA[舞影凌风]]></dc:creator><pubDate>Sat, 25 Aug 2018 07:15:05 GMT</pubDate><content:encoded><![CDATA[<h3 id="json_encode">json_encode失败切没有任何输出</h3>

<pre><code># 使用json_last_error输出错误信息
var_dump(json_last_error());

# 发现错误码是5 JSON_ERROR_UTF8, 转成uft-8就好了
mb_convert_encoding($value, "UTF-8", "auto");  
</code></pre>

<h3 id="">参考文档</h3>

<ul>
<li><a href="https://blog.csdn.net/ityang521/article/details/55225290">php json_encode输出空白问题</a></li>
<li><a href="https://stackoverflow.com/questions/10199017/how-to-solve-json-error-utf8-error-in-php-json-decode">How to solve JSON<em>ERROR</em>UTF8 error in php json_decode?
</a></li>
</ul>]]></content:encoded></item><item><title><![CDATA[搬瓦工替换ip后无法连接站点解决方案]]></title><description><![CDATA[<p>前段时间因为<code>某些特殊原因</code>搬瓦工服务器在国内不能访问了(测试是否被X可以用<a href="http://ping.pe">ping.pe</a>)，还好搬瓦工提供了换ip的服务，替换ip后访问是没有任何问题了，但是不能访问任何其他网页的服务，比如用脚本安装lnmp都提示找不到服务器。我自己在服务器上测试 ping所有的服务返回都是<code>ping: unknown host</code></p>

<pre><code>[root@localhost ~]# ping google.com
ping: unknown host google.com

[root@localhost ~]# ping github.com
ping: unknown host github.com

[root@localhost ~]# ping baidu.com
ping: unknown host baidu.com  
</code></pre>

<p>一般这种情况都是DNS解析问题，但是不可能这些知名企业</p>]]></description><link>http://www.apkfuns.com/banwagong-unable-to-access-chinese-domain/</link><guid isPermaLink="false">2769ee83-398a-48f2-b2fc-1b56fa1246a4</guid><category><![CDATA[搬瓦工]]></category><category><![CDATA[unknown host]]></category><dc:creator><![CDATA[舞影凌风]]></dc:creator><pubDate>Wed, 15 Aug 2018 05:34:00 GMT</pubDate><content:encoded><![CDATA[<p>前段时间因为<code>某些特殊原因</code>搬瓦工服务器在国内不能访问了(测试是否被X可以用<a href="http://ping.pe">ping.pe</a>)，还好搬瓦工提供了换ip的服务，替换ip后访问是没有任何问题了，但是不能访问任何其他网页的服务，比如用脚本安装lnmp都提示找不到服务器。我自己在服务器上测试 ping所有的服务返回都是<code>ping: unknown host</code></p>

<pre><code>[root@localhost ~]# ping google.com
ping: unknown host google.com

[root@localhost ~]# ping github.com
ping: unknown host github.com

[root@localhost ~]# ping baidu.com
ping: unknown host baidu.com  
</code></pre>

<p>一般这种情况都是DNS解析问题，但是不可能这些知名企业解析会出问题，所以联系搬瓦工客服咨询这个问题，以下是他们给的解决方案，成功解决了我的问题：</p>

<blockquote>
  <p>Hello,</p>
  
  <p>The most common reason for broken network is an old NAT iptables rule. Whenever your VPS IP changes due to migration performed via KiwiVM, you need to either modify the old rules or delete them.</p>
  
  <p>You can find more information about this by visiting the following article:
  <a href="https://bandwagonhost.com/knowledgebase.php?action=displayarticle&amp;id=24">https://bandwagonhost.com/knowledgebase.php?action=displayarticle&amp;id=24</a></p>
  
  <p>Thank you.</p>
</blockquote>

<p>解决方案就是在shell执行一句话重置iptables：</p>

<pre><code>iptables -F; iptables -t nat -F; iptables-save &gt; /etc/sysconfig/iptables  
</code></pre>

<p>不需要重启任何东西就可以马上生效~</p>

<h3 id="ip">附: 替换IP方案</h3>

<p>If you need to replace a banned IP with a clean IP, there are two options available to you:</p>

<p><strong>* Option 1 *</strong> Free IP replacement. We allow you to replace a blacklisted IP with a clean IP for free, once every 10 weeks. To access this feature, first login to KiwiVM as usual, then access the following hidden page (copy+paste link into the browser's address bar):</p>

<p><a href="https://kiwivm.64clouds.com/main-exec.php?mode=blacklistcheck">https://kiwivm.64clouds.com/main-exec.php?mode=blacklistcheck</a></p>

<p><strong>* Option 2 *</strong> Manual IP replacement (fees apply) available at the following link:</p>

<p><a href="https://bandwagonhost.com/ipchange.php">https://bandwagonhost.com/ipchange.php</a></p>

<p>mirror:</p>

<p><a href="https://bwh1.net/ipchange.php">https://bwh1.net/ipchange.php</a></p>

<p>This is an automatic reply. If your request is not properly addressed, please reply in this ticket and one of our engineers will take a look.</p>

<p>注意：搬瓦工每10个星期(2.5个月)可以免费更换一次ip。</p>]]></content:encoded></item></channel></rss>